返乡

南都讯 春节期间,一个上海大学博士生的返乡笔记《一位博士生的返乡笔记:近年情更怯,春节回家看什么》在微信朋友圈及微博等社交媒体疯传,截至昨晚9点,该文在微信公众号“市政厅”上阅读量已超过4.6万多次。近似“疯狂”的转发量让作者王磊光感到不安和不理解,在他看来这只是一个媒体事件,自己不过是“不小心卷入了其中”。

王磊光

##作者否认美化绿皮火车

这篇文章的作者是80后博士生王磊光,如今在上海大学文化研究系上学。他平时喜好文学,对乡土问题比较关心,本科毕业后曾在某中学担任语文老师,随后继续考研深造。在这篇笔记中,他描述了自己返乡的见闻:交通没有以前那么拥挤,但家乡人与人之间联系渐渐疏远,而农村里年轻人的婚姻受到了物质的压迫,知识的无力感也十分强烈。

没想到,就是这些在他眼里看似普通的乡村生活见闻,却触动了一大批读者,在春节返乡之际引发了人们对乡土的思考。许多网友都用“感同身受”四字评价,但也人指出返乡笔记有“美化”嫌疑,网友“Walter”评论道:“什么素质,绿皮火车脏乱差,打牌外放音乐严重影响他人,居然还好意思美化……”

对此,王磊光感到很奇怪:“为什么有人读出了美化,我身边的朋友都为我担心,觉得我将家乡不光彩的一面说出来了。至于我提到慢车及其生活状况,从来都没有赞美—我讲述记忆中的交通时,已经说到了慢车上的那种糟糕的情况。我这里提到慢车,目的在于说明它在当下存在的必要性;同时,指出慢车上的那种人与人的联系,是人间应该有的自然联系。”

##“知识的无力感”观点引发论争

此文也让中国农村的现状,在春节期间成为社交媒体上的热门话题,并收获诸多回应,有肯定的声音,也有争议之声。前日,一篇署名为“古鱼”的《又一篇博士生返乡笔记:从一而终的稳定生活更可怕》在澎湃新闻发布,以另一位来自乡村的文科博士生视角看乡村。古鱼对农村大学生“近年情更怯”的现象表示不认同,在他看来读大学的观念应该改一改:“无论乡村还是城市,读书不会无用,因为知识是有用的,读过大学的人相对而言会有更高的成长空间,以后贡献越大,拿钱也就越多,而不是一毕业就能兑现很多钱或一毕业就加入体制内获得某种‘人上人’的身份优越感。”

对于返乡笔记引发的争议,王磊光回应道,自己从未说过读书无用,只是强调知识的无力感。“本来读书出来是应该有所为的,但是回到家乡却不能做什么。”

##这篇笔记其实是应邀写的演讲稿

除夕之际,王磊光不断接到朋友、以前学生的电话,他才知道自己的返乡笔记在这个春节火了。这是他没有预料到的,他感到不安,也不能理解:“我很少去看别人的评论,因为在我看来,这只是一个媒体事件。”

事实上,这是王磊光应邀为2014年2月举办的“我们的城市论坛”所写的一篇演讲稿。他反问南都记者:“你有没有发现这篇文章就像一个提纲?很多内容和细节没有展开。”春节前,上大现代文学研究所副研究员罗小茗告诉他,媒体有意发表其返乡手记,王磊光答应发表。当时的题目仅是《近年情更怯》,最终见报标题为《一位博士生的返乡笔记:近年情更怯,春节回家看什么》。因此,他把返乡手记爆红的原因归于媒体的传播。

王磊光这篇笔记也获得了自己的导师—上海大学文化研究系教授、主任王晓明的称赞:“分析很真切,文化研究的视野开始有点形成了,好!”博导王晓明表示,读了返乡手记最后一部分“知识的无力感”,也同样感到心情沉重:“从现代早期到上世纪90年代,从农村出来到城市求学的人,总体上是能够以自身的生活和精神状态让其他没有这个机会的人信任‘求学是人生正道’的,有这个信任在,城市里的进步力量反哺乡村的可能就存在。但现在的情况,似乎越来越像80年代晚期90年代初的上海:出租车司机每月赚1400元,当得知我一个大学副教授每月才600元的时候,很同情地看着我:‘算了,下海吧!

##“突然出名”让他感觉很不真实

王磊光觉得,自己这篇文章没什么了不起,也不是他理想之作,这种“突然的出名”让他感觉很不真实。网友们不断在网上评论博士生返乡手记,王磊光在手记发布的第一天看了看评论之后,便很少关注。他知道,不管自己说什么,都会有各种评论出现。

更何况,真正生活在文中所述乡村的人们并不知晓博士生返乡手记的走红,王磊光也从来没有向生活其中的长辈们提及文章的事情。“他们都是很本分的人,如果看到我将身边的事情写了出来,一定会为我担心。

对于自己的文章引发的有关农村大学生出路问题的讨论,王磊光则表示,“我所说的,是80后大学生出路难的问题,这里有一个背景,即与上世纪八九十年代大学毕业生相比,当时他们大学毕业之后是能够改变自己甚至家庭的命运,而如今的80后大学生承载着家庭的希望,但绝大部分人的出路是艰难的。”如今,媒体陆续找到他,但王磊光希望人们不要关注他本人,而去关注现实的中国。

##附全文

###博士春节返乡手记:越看越对乡村的未来越迷茫

“有故乡的人回到故乡,没有故乡的人走向远方。”我很庆幸我有故乡,可以随时回去,尤其可以回家乡过年。因为我的根在那里,我的亲人在那里,我的生活经验和记忆在那里。

我的家乡在湖北的大别山区,L县。我导师王晓明教授在2004年写过一篇著名的文章《L县见闻》,写的就是这个地方。王老师以我家乡为对象,揭示了当时农村的破产状况,人的精神的颓败,以及乡村文明的没落。我家所在的那个村子,是一个东西两座大山夹住的狭长谷地。一个村子由十来个“塆子”组成,一个塆子有几十户人家,我家那里叫王家塆。

直到现在,我每到一个地方,凡是碰见两山相夹或两排高大的建筑物相夹,我的第一意识就是,这两座山或两排建筑物,一个在东,一个在西,所以我在外面经常迷路,尤其在城市里。上海7号线有两个靠得比较近的站:“长寿路”和“常熟路”。我好几次下错站,以致现在每到这两个站就紧张,怕弄错了。为什么呢?因为在我家乡的方言里,“长寿”和“常熟”是完全一样的读法。人要靠语言来思维,这个事情让我意识到:对有家乡的人来说,是用方言来思维的。

我有一个初中同学群,群里90%的同学只读到初中就出去打工。经过十七八年的积累,很多同学在城市里有房有车,有的还有了自己的事业。平时在群里,他们交流的最多的是工作问题,车子问题等,言谈中总少不了炫耀。

但有一次,有个同学忽然在群里说,他已经三年没回家过年了,另一个同学紧跟着说,他五年没回家了,接着很多人说起回家的情况。有一个说:不管怎么样,今年过年一定要回一次家!另一个说:如果能在家乡找一个两千块钱的工作,就回去算了。还有一个说:能找个一千块的工作,我这边什么都不要,也愿意回家。

我有一个从小学到初中的同学,已经十年没有回家。有一天他在qq里突然对我说,我的父母是很好的人,因为小学四年级的时候他去我家玩,我爸妈用腊肉下面给他吃。这都是陈芝麻烂谷子的小事,他还记得,其实我知道,这是因为他太想家了。

上海大学文化研究系有位老师主持来沪青年工人的社会调查,最近在访谈工人。有一个打工者说:我真希望邓小平没有搞改革开放,我也愿意日子苦些,因为这样我就可以每天跟父母和孩子在一起。

回家过年,其实是没有道理可讲的一件事。套用贾平凹的话来说:家乡对我们的影响,就像乌鸡的乌,那是乌到了骨头里面。

###回家的交通

十多年前我上本科的时候,从大西北到武汉,坐的是那种编码没有字母只有四个数字的绿皮火车,22小时,通常要晚点两三个小时。西北往武汉的路线,不是人流最多的,但春运那个挤啊,大大超出了今天90后的想象。好在那个时候,学生一般都可以提前集体订票,买得到座位。而站着回家的,几乎全都是农民工。每次上车的时候,无论有票的还是没票的,都一窝蜂往车上挤。

我对过年回家的第一印象就是:我背着一个包,提着一个包,与同学一起,从第一节车厢狂奔到第十几节车厢,然后被后面的人推着挤上了车。上车后一分钟,车就开动了。我记得火车广播里号召大家发扬风格,让站着的乘客挤一挤。大家真的很友好,四个人的坐位,挤了五六个。火车过道里人贴着人,想蹲下来都没有办法,连厕所里也挤着好几个人。

男乘客还可以想办法,可苦了女乘客。记得有一次我身边坐着一个在西安读书的大学生,他要小便,就脱下外套让我给他挡住身体,想把尿撒在矿泉瓶里,但他很紧张,用了十几分钟才勉强撒出来。我还记得有一次身旁坐着一个从西北打工回家的河南妇女,尽管有位子,但她实在太困,太想睡觉了,就把位子让给别人坐,自己钻到座位底下睡觉去了。

应该要肯定,我们国家这十年间的铁路建设取得了巨大成就,铁路线路的增加,尤其是动车和高铁的开通,极大缓解了交通压力。火车站、火车上,起码不会像过去那么拥挤了。

过年回家那种路途的遥远、时间的漫长、竞争的激烈、拥塞以及不安全感,让我对“男儿有志在四方”的观念产生了极大厌倦。所以,本科毕业时,我找工作坚决要回到湖北。后来我就在家乡隔壁的县城一中当老师。自2004年到2011年来上海读研之前,我再也没有遭受春节回家难的痛苦。尽管从隔壁县回家的汽车在过年时依然被塞得满满的,但毕竟只有两个多小时,实在挤不下,还可以花两百多块钱请出租。我在上海读研的这几年,其实也没有遭受回家难的痛苦,因为上海到武汉的高铁和动车很多,普通车也有几趟,买票很方便。

今天各位出行,如果坐火车,不是高铁就是动车吧?但是,不知道大家有没有想过:那种速度慢、见站停的普通列车是否还有存在的必要?

大家有没有想过:到底是谁在乘坐普通列车?

我想大家肯定一下子就能给出答案:除非没有其他更好的交通工具,学生不会坐,城市人不会坐,主要是那些底层的老百姓,比如农民、农民工在坐。

去年暑假和寒假回家,我特意选择坐慢车,16个多小时的硬座。就是要看看是哪些人在坐慢车,看看慢车上还是不是过去那个样子。的确,主要是农民、农民工在坐慢车。对农民工来说,选择坐慢车,比动车起码节约一半的钱,比高铁节约三分之二以上的钱。从深圳到武汉,高铁一等座要八百多,二等座五百多,但慢车硬座不到两百。尤其对于全家在外打工的人,从深圳到武汉,可能要节约一千多元,这对农民家庭来说不是小数目。

不过,慢车也没有过去那么挤了,因为农民工虽多,但很多都被动车和高铁分流了——既有主动的分流,也有被动的分流,因为价格便宜的慢车越来越少了。

大家可以注意到,今年12306网站通告的春运期间的加班车,三分之二以上的是非动车高铁。这个安排还是挺人性的,因为说到底,加班车就是为了农民工而加,低价位的车符合他们的需求。

而且,你会发现,普通火车与动车的氛围完全不同。

在动车上,相对比较安静,大家不是玩电子产品就是睡觉,相互间很少交流。但是,在普通火车上,熟悉的、不熟悉的,都在热烈地交流,还有打牌、吃东西的,做什么的都有,也有用劣质手机放歌曲的,大家都不担心打扰到别人,也没有人认为别人的做法对自己是一种干扰。慢车上的风格是粗犷的,是人间生活的那种氛围。

对比动车高铁与普通火车,很容易就能发现这里的阶层差别、生活方式的差别。而且你还能感觉到,底层人的心理,比我们想象的要乐观得多,健康得多。底层的状况虽然普遍很糟糕,但大家还是很听话地活着,这里面的一个重要原因,就是他们如果在外面活不下去,还有家园可以退守。

开私家车回家过年,在青年打工者中越来越普遍。我待会进一步讲这个事情,因为它的意义大大超出了交通工具本身。

骑摩托车回家的情况,大家可能在新闻里看到了。每年春节,总有摩托大军回家过年。我的一个表哥,每到过年时就让他的儿子坐汽车回家,而自己骑摩托车带老婆回家,路上要两天一夜。另一个表哥也是骑摩托带老婆回家,有一年在途中撞了人,不知是真撞还是被讹诈了,反正被人家扣了一天多,赔了一万多块才放人,半年的收入就这样没有了。

###人与人之间联系的失落

我觉得,当前农村的亲情关系,很大程度上是靠老一辈建立的关系维系着。在老一辈那里,这种关系处在一种相对稳定的时空里,但对年轻一代来说,大家的关系早已被现实割裂了。比如,我和我的众多表哥,小时候一起上山捉鸟,下河摸鱼,关系好得不得了,但这一二十年来,他们一直在外打工,我一直在外读书和工作,一年最多在过年时见一次,平均下来每年还没有一次,因为他们不是年年都回家。拜年的时候,大家也不再像过去那样,在亲戚家吃饭喝酒聊天,甚至留宿一晚,现在大家都骑着摩托车拜年,去亲戚家匆匆走一遭,放下东西,客套几句,就要离开了。平时的生活啊情感啊什么的,都没有来得及交流。大家拜年,不再是为了亲戚间互相走动,馈赠礼物,交流感情,而只是为了完成传统和长辈交代的一项任务

悲哀的是:如果老一辈都不在世了,新一辈的联系也就慢慢断了。

更让人悲哀的是:农村的日常生活充满着深刻的悲剧。自打工潮于九十年代兴起以来,很多农村人一直在外打工,二十多年来与父母团聚的时间,平均到每一年可能就十来天。很多农村老人倒毙在田间地头,病死在床上,儿女都不在身边。没有来得及为父母养老送终,成为许多人终身的悔恨。

每次回家,看到我身边的老人摇摇欲坠的样子,我就觉得心里难受得很。

如果一个人为了生存,连爱父母爱子女的机会都被剥夺了,你怎么可能指望他去爱别人,爱社会,爱自然?你怎么可能指望他能用超出金钱的标准来衡量别人的价值?所以我想说:现代生活是一种让人心肠变硬的生活。

###在农村,还有什么可以将农民动员起来?

自从2006年免收农业税之后,中国农村的基层组织主要起着上传下达的作用,不再与农民的根本利益发生关系,也不再能将农民组织起来,农民处于“个人自治”的状态。

(1)春节的力量。亲人团聚,过年拜年。过年的力量,亲情的力量,是当下动员中国人最有效的力量。这也是过年最让人感觉温暖的东西。当然,以前过年时的各种集体活动,都已消失殆尽了。

(2)祭祀。中国农村还是保持着过年、过十五给祖宗上坟“送亮”的习俗——家家户户都要去祖宗的墓地给祖先点蜡烛,烧纸钱,放鞭炮,与祖先交流。很多已经在城市安家的人,也会赶在大家三十这一天开车回老家给祖宗上坟。许多曾被废弃的祠堂,这些年也逐渐恢复起来了。

(3)葬礼。很多老人没有挨过冬天。过年前后,是老人逝世的高峰时段。丧葬在中国文化和中国人的生活中有着非常重要的地位,尤其对今天的社会来说,有着特别重要的意义。媒体上动不动就喜欢报道某某地方为举办葬礼大肆挥霍,让大家误以为这是普遍现象。其实恰恰相反。相比古代,今天的丧葬已是在最大程度上简化了。“贵生重死”的观念早已失衡了——大家越来越贵生,对于死,不再有敬重,不再让死者享受哀荣;对于天地,不再有敬畏。

但丧礼,在现实中依然起着不可替代的作用。去年快过年的时候,本家一个叔叔亡故——本家人和四面八方的亲戚来给他守丧,守丧的时候大家聚在一起交流,像过节一般,交流一年的生活情况、见闻和感想,称赞中央的政策,谴责干部的腐败……深夜里交谈的声音传得很远很远。守丧完毕,大家集体出力,将他抬到山上,让他入土为安。

社会学者经常用“原子化”来形容今天农村的现状,说白了就是,农村原有的那种共同体已经消失了,人与人之间不再像原来那样有着密切的关系和交往,不再像过去那样每到过年时相互串门,集体上街玩等等。为死者守丧和送葬,在农村反而成了村里人团聚和交流的一个契机。这也是我在家乡看到的唯一能够让大家团聚的方式。

###妻子?房子?车子

(1)妻子。这一点主要是针对农村的男青年来说的。在今天的社会,农村男青年在本地找媳妇越来越难。一来,这是由中国男多女少的现状决定的。而且,农村稍微长得好看点的女孩子,基本都嫁到城里去了,愿意嫁在农村的女孩子越来越少。二来,农村青年讨媳妇,要具备的物质条件很高,现在普遍的一个情况是:彩礼六到八万,房子两套:在老家一栋楼,在县城一套房。这个压力,并不比城市青年讨老婆的压力小。

过年的时候,打工的青年男女都回来了。只要哪一家有适龄女孩子,去她家的媒人可谓络绎不绝。这在乡村已成了一门生意,农村说亲,几乎到了“抢”的地步。如果初步说定一个,男方至少要给媒人五百块,最终结婚时,还要给上千的报酬,有的甚至要给到两三千。

传统的农村婚姻,从相亲到定亲到结婚,要三四年时间,男女双方有一个了解和熟悉的过程。现在却不同,年里看对的,过了年,马上定亲,然后女青年跟着男青年出去打工,等到半年过去,女方怀孕了,立刻奉子成婚。

曾听过一个搞量化统计的学者对农民工的调查报告,得出的结论之一是:农村孩子结婚越来越迟。但我看到的情况恰恰相反:因为女孩子难找,男孩子一到二十岁,父母就张罗着给儿子物色对象,物色好对象之后,既怕女孩子变心,又考虑要到城市讨生活的现实情况,就催着孩子赶快结婚。可以想象:在现代社会这种动荡不安的生活中,这样的婚姻会出现多少问题!事实上,农村离婚的情况,也是与日俱增的。

(2)房子。刚才已经说了,现在农村人娶老婆要房子两套:一套在家里,一套在县城。其实县城的那套房,平时都空着,只是过年时回来住,但对年轻人来说,那就是城市生活的一种代表。过年时,有的也会把父母接到县城过年,但父母住不惯,在县城过了大年,初一就赶回来了。在老家的生活是“老米酒,蔸子火,除了神仙就是我”,而在县城除了那套房,什么都没有。

但是,为了添置这两套房,将来给儿子娶媳妇,很多家庭是举全家之力在外打工。

下面给大家看我在去年过年的时候写的一则笔记,涉及到房子和婚姻的问题,但还有其他的含义在其中。

2013年冬天的一则笔记

跟大哥、细哥到二郎庙水库捕鱼。(细哥承包的这座小水库只有三十亩的水面,在海拔八九百米的山上,水很纯净,可直接饮用,鱼放在里面长得非常慢,一年下来甚至还要瘦。每年腊月底或者年初,细哥就要从外面进鱼秧,虽说是鱼秧,其实有三斤多一条——这种鱼是在平原地带的池塘里用饲料喂养的,进价是两块多,但是鱼在纯净水里清洗了一年之后,肉味大大改善,可以卖到五块多一斤。)

一个拉砖的师傅把车停在坝上。我们问他,从山下往山上拉一趟要多少钱。他骂了一句话粗话,然后说:“两百块,划不来!”又说,就是这样的生意,也越来越少了。山里的楼房基本都做完了,没有做的也都在县城里买了房。大哥说:“在县城买房又么样,到时住在那里做什么呢?”司机说:“只要是人,总有个生存的法子。”又来了一个人,是细哥的同学,他的摩托车上带着老婆和还在读初中的儿子。得知他在这山里做了楼房,还在县城买了一套房。细哥问:“你要买那么多房做么事!”他叹了一口气:“我们这时代不叫人过的时代!没办法!”“做了一栋楼,买了一套房,还叫没办法!明年还去打工吗?”“不去打工,在家里做么事?”而这座水库上头的两个塆子的人家,基本上都在这里做了一栋楼,在县城买了一套房。

其间来了一人,开小车,戴墨镜,手腕上戴着很粗的黄金链子。老远就用粗嗓子喊正在水上下网的细哥,问有鱼没有。细哥正划着独木船,一只手划,一只手下网,笑着答:“你又不买,问着做么事!”同我们说话时,他的墨镜始终没有摘下来,神气得不得了。墨镜又对细哥喊:“别扑了麻雀(翻船)哈,我是秤砣,到水里就沉了,帮不了你。”说完就独自哈哈地笑。他同我们说起晚上要陪开挖机的斗地主。说是挖山种天麻,规模很大,已经买了十五万斤树。从言谈中得知,他平时在县城住。细哥的同学也说,他准备将家里几面山的树都卖了——分田到户后交了几十年的税,没有沾过任何光。后来听细哥讲,墨镜小学没读完,就在外面混,替人讨债,拿斧头砍人,就这样起家的。

一会儿又来了母女三人,带着一个三岁的小孩。她们是来买鱼的。跟墨镜是亲戚。墨镜却不认识那个年纪最小的女孩。“跟以前长得不一样了呀!”墨镜说,“在哪里打工?”她说在温州。“属什么?”“属鸡。(刚满20)”

墨镜说:“还没有说人家吧?我帮你介绍个。”女孩的母亲说:“她回来这几天,已经有好几个人来介绍。”“某某某正为儿子找媳妇急得哼,我把你说到他家。”(说,替人说亲的意思)女孩母亲连忙说:“那怕是不行,她想嫁到县城里。”墨镜说:“他家在县城有套房子。那男孩的娘脾气不好,但你们又不跟她过,你们到县里住,做点小生意。他家也有钱,你叫他们现在拿个四五十万,轻而易举就拿出来了。”墨镜走的时候,表示过两天,要带那男孩上门相亲。

(3)车子。近些年来,对在外打工五年以上的农村青年来说,对一种东西的渴求,可能比对房子和妻子更为强烈,那就是车子。车子不一定要多么好,五万,八万,二十万,各种档次的都有。老百姓不认识车子的牌子,不知道车子的价位,只知道这些车叫“小车”。不管什么小车,关键是要有!

在农村,房子是一个媒介,车子更是一个媒介——是你在外面混得好,有身份的代表,房子不能移动,车子却可以四处招摇,表示衣锦还乡。很多二代、三代农民工,当下最大的期待就是买一个车子。尤其对那些好些年没回家的人来说,他再次回家,必须要有辆车,否则他怎么证明自己?

春节的县城,到了水泄不通的地步,这些车子绝大部分都是从外面回来的,与此同步的情况是:物价飞涨。

###知识的无力感

这十多年来,外界对于农村的关注主要集中于农民工身上。众所周知,他们在城市打工的日子很苦,而家里的老人和孩子往往无人照料。其中酸甜苦辣自不待言。但从另一个角度来看,现在农村日子过得较为殷实的,也恰恰是这些有几个成员在外务工的家庭。(仅仅只有一个成员务工,通常不足以改变家庭的经济状况。)应该说,他们的辛劳和泪水还是得到了适当的回报。

倒是有两类家庭,他们处于最困难的境地,却往往被忽视。一类是孤寡老人。一类是举全家之力,把子女培养成大学生的家庭。

在第一类家庭中,这些老人的年纪一天比一天大,身体一天比一天衰败,没有任何经济来源,日子过得异常艰难。有人会问:国家不是有低保吗?是的,他们中的确有部分人吃上了低保。在我的家乡,低保的额度是每年八百。但是,绝大部分这样的老人,仍在低保的福利之外。因为他们处在农村的最底层,没有人替他们说话。低保名额通常被身强体壮者拿走。甚至,有些村干部为了堵住所谓“刁民”的嘴,不让他们到镇上或县里反映村里的问题,就把这些人变成低保户,有的甚至全家吃上低保。“有钱人吃低保”,早已成为农村公认的一桩怪事。过年的时候,大家也不再像传统社会那样,家家户户给这些孤寡老人送点东西。

这里所谓第二类家庭,主要是指有孩子在1980年代出生的家庭。这些孩子,从小学读到大学,一直都在经受教育收费的最高峰,没有哪一坎能够躲过。并且,二十多年来,农村税费多如牛毛,家里一年的收入,不够交税。大人内外应付,心力交瘁。最要命的是,作为满载家庭希望的大学生,毕业之后勉强找到一份饿不死的工作时,又面临结婚、买房等种种压力。可以说,几乎每一个农村的80后大学生,都是以牺牲整个家庭的幸福为代价来读大学的。但他们中的绝大部分,毕业后没有希望收回成本,倒是让年迈的父母继续陷入困顿。

最近一个博士师兄请吃饭,他说他现在最害怕的就是回家,感觉很难融入到村子的生活,所以他每年过年他都回去得很迟,来学校很早。为什么呢?因为当你一出现在村子里,村里人其他的不问,就问一个问题:“你现在能拿多高的工资?”所以,他过年回家,基本不出门。这个体验跟我是一样的。你要问我过年在家乡看什么,其实我没看什么,因为一大半时间是呆在家里看书,看电视,写东西。

作为农村大学生,当你回到家乡的时候,你童年那些伙伴都衣锦还乡了,而你连自己的问题都不能解决,你还能做什么呢?没有人信任你的知识!

说了上面这些,相信大家能够理解,对于我这样漂在外的农村大学生,回家过年既是一件非常急迫的事情,也是一件情怯的事情。

回家究竟看什么?其实真的没有刻意去观察,但是很多事情却不停地往你心里撞,也就有了很多感受。越看,对乡村的未来越迷茫。

转载自:chinadigitaltimes


2015-03-01 思考 , 感悟 , 转载

Java 查漏补缺之: GC 垃圾回收

说到 Java 很多人脑海会直接蹦出内存自动回收,会经常听到 GC 这些词,GC 指的是 Garbage Collection 也就是垃圾回收。说到垃圾回收就不可避免的去看下 Java 的内存管理机制。

内存管理

提到内存管理可能很多人都会联想起 C/C++ 的手动内存管理,以及 Java/Python 的自动管理,但实际上这都是指的堆内存管理。常规的内存管理可以分成两个部分,栈内存管理和堆内存管理。

栈的发明解决了部分内存的自动回收,但是栈的局限在于只能自动管理固定长度的内存,而对于堆内存,不同语言有不同的管理方式:

  • 纯手动管理 C/C++
  • 自动管理 Java/Python/PHP/C# 等
  • 半自动 C++ 智能指针 Swift/Rust 等

什么是 GC 以及为何要有 GC

对于通常含义上的 GC 可以参考维基词条, John McCarthy 在发明 Lisp 时一并发明了内存自动管理机制。

什么是内存垃圾,也就是程序在执行过程中再无法访问的对象,这些对象所占用的内存空间可以收回来重新使用。

优点:

  • 编码容易
  • 减少因内存管理而导致的 bug,野指针,内存泄露等等

GC 的缺点:

  • 需要消耗额外的 CPU / Memory 资源
  • 代码执行时间无法估计

GC 实现方式

Reference counting

引用计数,最简单也最容易实现的一种,原理是在每个对象中保存该对象的引用计数,当引用发生增减时对计数进行更改。

优点:

  • 当对象不再被引用立即就会被释放,算法运行快
  • 空间释放是针对个别执行,和其他算法相比,GC 中断时间比较短

问题:

  • 额外的内存占用,每个对象需要一个 counter
  • 引用发生增减时需要对计数做出正确的增减,如果发生计数错误可能会导致难以发现的内存错误
  • 循环引用,两个对象互相引用,能解决但需要大量计算
  • 引用计数不适合并行处理,多线程同时对引用计数进行增减时,引用计数可能会产生不一致的问题,而如果采用加锁方式,带来的开销也非常大

引用计数的例子:

  • Python
  • PHP
  • Swift/ OC

Mark and Sweep

标记清除,是最早开发出来的算法(1960 年),原理,从根开始可能被引用的对象用递归的方式进行标记,然后将没有标记的对象作为垃圾回收。

缺点:

  • 在分配了大量对象,而只有一小部分存活的情况下,算法消耗的时间多
  • 执行时间不可控

Copy and Collection

复制收集,为了克服标记清除的问题,在算法中将根开始被引用的对象复制到另外的空间中,然后将复制的对象所能够引用的对象用递归方式复制下去。

复制收集方式的过程相当于标记清除算法中的标记阶段,但由于清除阶段依然要对所有对象进行扫描,如果存在大量对象,而且大量对象已经死亡的情况,开销会加大。

优点:

  • 没有内存碎片
  • 复制收集过程中,按照对象被引用的顺序将对象复制到新空间,关系较近的对象被放到较近空间的可能性提高,局部性能提升,内存缓存可能更容易命中

缺点:

  • 和标记清除相比,复制对象的开销加大,当存活对象较多的情况下,性能损耗

JVM 如何实现 GC

现代版的 GC 使用分代收集,按照对象存活时间长短来使用不同的垃圾回收算法。

heap 分为:

  • Young Generation: 新创建的对象,Young Generation 又被分成 Eden space(所有新对象开始的地方),两个 Survivor spaces(在存活一个 gc 之后移动到这里),当对象在 Young Generation 被回收,这是 minor garbage collection event
  • Old Generation: 当对象存活足够长时间,会从 Young Generation 移动到 Old Generation。当对象在 Old Generation 被回收,这是一次 major garbage collection event.
  • Permanent Generation: 类,方法等 Metadata 会保存在 Permanent Generation。不再被使用的 Classes (ClassLoader 回收后)会被回收。

所以一个对象在不同分区的流程可能是:

  • 新对象在 Eden 中创建
  • Eden 满时进入 Survivor spaces
  • 两个 Survivor 空间的对象相互交换
  • 在 Survivor 存活一定时间后进入 Old

一种 JVM 的实现,由 1999 年引入,HotSpot 为代表,HotSpot JVM 有四种 Garbage Collector:

  • Serial GC : 所有的 garbage collection events 通过一个线程连续管理, Compaction 在每一次 garbage collection 之后执行
  • Parallel GC : 并行进行 minor garbage collection,一个线程用来 major garbage collection 和 Old Generation compaction
  • Concurrent-Mark-and-Sweep GC : 简称 CMS,多个线程用来 minor garbage collection ,使用和 Parallel 相同的算法。CMS 在应用程序之外运行,GC 和 应用程序并行,减少中断。不会执行 compaction
  • G1 GC(1.7+) : Garbage First,新的 garbage collector,用来替换 CMS,同样是并行并发的,但是原理和工作方式完全不一样

Java 的 GC 是不确定的,没有方法来预测何时会发生 gc。在代码中可以使用 System.gc() 或者 Runtime.gc() 方法来暗示 gc,但是 Java 不能保证 gc 一定会执行。

GC 的调优可以从 JVM 的参数调节:

  • -Xms 初始堆大小,可以设置和 Xmx 相同,避免每次垃圾回收后 JVM 重新分配内存
  • -Xmx 最大堆大小
  • -XX:NewRatio=n Young 和 old 区的大小比 1:n
  • -XX:MaxPermSize Permanent 大小
  • -XX:+UseG1GC 使用 G1
  • -XX:MaxGCPauseMillis=n 最大希望暂停时间
  • -XX:InitiatingHeapOccupancyPercent=n 堆使用到多少百分比时开始 CMS 过程
  • -XX:+PrintGC 输出 GC 日志
  • -XX:+PrintGCDetails 输出 GC 的详细日志
  • -XX:+PrintGCTimeStamps 输出 GC 时间戳
  • -XX:+PrintGCDateStamps 输出 GC 时间戳(日期形式,2019-01-01T01:01:02.212+0800)
  • -XX:+PrintHeapAtGC 进行 GC 的前后打印堆信息
  • -verbose:gc
  • -XX:+PrintReferenceGC 年轻代各个引用的数量以及时长
  • -Xloggc:../logs/gc.log 日志文件输出路径

JVM 内存区域划分

Heap 区

  • Eden
  • Survivor
  • Old gen

非 Heap 区:

  • Code Cache, 代码缓存区,它主要用于存放 JIT 所编译的代码
  • Perm Gen,Permanent Generation space,是指内存的永久保存区域
  • Jvm Stack
  • Local Method Stack

为什么需要 Survivor 区

前提知识,新生代内存中除了 Eden 区外还有两个 Survivor 区,Eden 占 80%,两块 Survivor 区占比 20%。

如果没有 Survivor ,Eden 区每进行一次 Minor GC,存活的对象都会被送到老年区,老年代很快被填满。

每进行一次 Minor GC,存活下来的对象会被计数 +1,当对象在 Minor GC 下存活多次,达到一个阈值后会被移动到老年代。

Survivor 存在的意义,减少被送到老年代的对象,减少 Full GC 发生,只有经历 16 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

JVM 参数设置和调优

生产环境 Xms 和 Xmx 设置相同的值

In a production environment, if you monitor the GC data, you will notice that is a relatively short period of time (usually less than an hour), the JVM will eventually increase the heap size to the -Xmx setting. Each time the JVM increases the heap size it must ask the OS for additional memory, which takes time (and thus adds to the response time of any requests that were is process when the GC hit). And usually the JVM will never let go of that memeory. Therefore, since the JVM will eventually grab the -Xmx memory, you might as well set it to that at the beginning.

Another point is that with a smaller heap size (starting with -Xms), GCs will happen more often. So by starting with a larger heap initially the GCs will happen not as often.

Finally, in a production environment, you usually run only one app server per OS (or per VM). So since the app server is not competing for memory with other apps you might as well give it the memory up front.

Note that the above is for production. It applies also to the syatem test environment since the system test environment should mimic production as close as possible.

For development, make -Xms and -Xmx different. Usually, you are not doing much work with the app server in development, so it will often stay with the -Xms heap setting. Also, since in development the app server will share the machine with lots of other apps (mail client, word processors, IDEs, databases, browsers, etc), setting the -Xms to a smaller size lets the app server play more nicely with the other software that is also competing for the same resources.

gc log

enable gc log

通过如下任意一个开启 gc log:

-XX:+PrintGC
-XX:+PrintGCDetails
-Xloggc:gc.log

开启 -XX:+PrintGC 后,打印日志:

[GC (Allocation Failure)  61905K->9848K(256000K), 0.0040139 secs]

说明:

  • GC 表示是一次 YGC(Young GC)
  • Allocation Failure 表示是失败
  • 61905KK->9848K 表示年轻代从 61905KK 降为 9848K
  • 256000K 表示整个堆的大小
  • 0.0040139 secs 表示这次 GC 总计所用的时间

开启 -XX:+PrintGCDetails 后,日志:

2020-03-28T08:55:24.916+0800: 229805.169: [GC (Allocation Failure) 2020-03-28T08:55:24.916+0800: 229805.170: [ParNew: 584336K->24291K(629120K), 0.0145141 secs] 1849190K->1289986K(2027264K), 0.0155393 secs] [Times: user=0.08 sys=0.02, real=0.02 secs]

说明:

  • 第一个时间戳 2020-03-28T08:55:24.916+0800 是 Mirror GC 发生的时间
  • 229805.169 GC 开始的时间,这里是相对 JVM 启动时间,单位秒
  • GC 用来区分 GC 类型,Minor GC 或者 Full GC
  • Allocation Failure 分配内存失败
  • ParNew 收集器名称
  • 584336K->24291K 前后年轻代使用
  • 629120K 整个年轻代的容量
  • 跟随的时间是 gc 发生的时间
  • 1849190K->1289986K 堆前后使用情况
  • 后接的时间是 ParNew 收集器标记和复制年轻代存活的对象的时间

    最后出现的 [Times] 中三个时间是:

  • user: GC 线程在垃圾收集中使用 CPU 时间
  • sys: 系统调用时间
  • real: 应用被暂停的时钟时间,GC 是多线程的所以, real < (user + sys)

Example

public class GCLogDemo {

	public static void main(String[] args) {
		int _1m = 1024 * 1024;
		byte[] data = new byte[_1m];
		// 将 data 置为 null 即让它成为垃圾
		data = null;
		// 通知垃圾回收器回收垃圾
		System.gc();
	}
}

相关命令

jstat

jstat 可以用来查看 JVM 数据信息:

jstat [options] vmid [interval] [count]
  • options 使用 -gc 或者 -gcutil
  • 这里的 vmid 是 vm 的进程号,当前运行的 java 进程号

比如查看 gc 情况

jstat -gc [PID]

每 5 秒打印一次特定 PID 的 GC 情况

jstat -gc [PID] 5000
jstat -gc [PID] 5s

结果说明:

前提知识,新生代内存中除了 Eden 区外还有两个 Survivor 区,Eden 占 80%,两块 Survivor 区占比 20%。

S0C:当前年轻代中第一个 survivor(幸存区)的容量 (字节) ,简记成 Survivor 0 Current
S1C:当前年轻代中第二个 survivor(幸存区)的容量 (字节) 
S0U:年轻代中第一个 survivor(幸存区)目前已使用空间 (字节) 
S1U:年轻代中第二个 survivor(幸存区)目前已使用空间 (字节) 
EC:当前年轻代中 Eden(伊甸园)的容量 (字节) 
EU:年轻代中 Eden(伊甸园)目前已使用空间 (字节) 
OC:当前 Old 代的容量 (字节) 
OU:Old 代目前已使用空间 (字节) 
PC:当前 Perm(持久代)的容量 (字节) 
PU:Perm(持久代)目前已使用空间 (字节) 
YGC:从应用程序启动到采样时年轻代中 gc 次数 
YGCT:从应用程序启动到采样时年轻代中 gc 所用时间 (s) 
FGC:从应用程序启动到采样时 old 代(全 gc)gc 次数 
FGCT:从应用程序启动到采样时 old 代(全 gc)gc 所用时间 (s) 
GCT:从应用程序启动到采样时 gc 用的总时间 (s) 
NGCMN:年轻代 (young) 中初始化(最小)的大小 (字节) 
NGCMX:年轻代 (young) 的最大容量 (字节) 
NGC:年轻代 (young) 中当前的容量 (字节) 
OGCMN:old 代中初始化(最小)的大小 (字节) 
OGCMX:old 代的最大容量 (字节) 
OGC:old 代当前新生成的容量 (字节) 
PGCMN:perm 代中初始化(最小)的大小 (字节) 
PGCMX:perm 代的最大容量 (字节)   
PGC:perm 代当前新生成的容量 (字节) 
S0:年轻代中第一个 survivor(幸存区)已使用的占当前容量百分比 
S1:年轻代中第二个 survivor(幸存区)已使用的占当前容量百分比 
E:年轻代中 Eden(伊甸园)已使用的占当前容量百分比 
O:old 代已使用的占当前容量百分比 
P:perm 代已使用的占当前容量百分比 
S0CMX:年轻代中第一个 survivor(幸存区)的最大容量 (字节) 
S1CMX :年轻代中第二个 survivor(幸存区)的最大容量 (字节) 
ECMX:年轻代中 Eden(伊甸园)的最大容量 (字节) 
DSS:当前需要 survivor(幸存区)的容量 (字节)(Eden 区已满) 
TT: 持有次数限制 
MTT : 最大持有次数限制 

如果使用 -gcutil 则是打印 GC 的使用率:

jstat -gcutil -h 10 [PID] 1000

jmap

使用 jmap 打印堆相关信息,更多细节可以参考这篇文章

jhat

更多关于 jhat 的用法可以参考这篇

reference


2015-02-27 java , gc , garbage-collection

每天学习一个命令:xclip 与剪贴板交互

xclip 命令可以从 stdin,或者文件读入数据到剪贴板,或者将剪贴板内容粘贴到目的应用中。xclip 命令建立了终端和剪切板之间通道,可以用命令的方式将终端输出或文件的内容保存到剪切板中,也可以将剪切板的内容输出到终端或文件

安装

sudo apt-get xclip

命令格式

xclip [OPTION] [FILE] ...

常用参数:

-i      从 stdin 读入
-o      打印到标准输出

使用实例

不加选项时只在保存在 X PRIMARY(终端剪切板),加上选项 -selection c 后保存在 X CLIPBOARD(外部程序剪切板)

为了区分这二者的区别,可以简单的做一个试验。

echo "Hello World" | xclip

此时 Hello World 字符只是在终端的剪贴板中,可以尝试在终端鼠标中键粘贴,发现终端的粘贴板是已经被修改的,此时用 Ctrl + v 粘贴到其他 GUI 应用程序(比如 Chrome 地址栏)发现粘贴板并不是 Hello World。

echo "TEST OUTSIDE CLIPBOARD" | xclip -sel c

此时会发现 Chrome 中可以粘贴 TEST 这行文本,而鼠标中键粘贴到终端的还是上面的 Hello World。

终端输出保存到剪切板中

ls -al | xclip
echo "SOME" | xclip
xclip /etc/passwd
xclip < /etc/passwd

此时 ls -al 的输出内容已经保存在剪切板中了,此时 xclip -o 可以看到剪切板的内容。

但此时还不可以粘贴到终端以外的程序中,此时需要用到: xclip -selection c

ls -al | xclip -selection c
xclip -sel c /etc/passwd
xclip -sel c < /etc/passwd

剪切板内容输出到终端

xclip -o
xclip -selection c -o

剪切板内容输出到文件

xclip -o > ~/test.txt
xclip -selection c -o > ~/test.txt

reference

  • man xclip

2015-02-26 linux , command , xclip

Java 的 IO 操作 java.io 包

InputStream 和 Reader 的区别

  • InputStream 是 byte 导向
  • Reader Writer 是字符导向

2015-02-20 java , java-io , io

每天学习一个命令:ffprobe 查看多媒体信息

在 ffmpeg package 中有一个 ffprobe 工具,主要用来查看多媒体文件或者流媒体信息,在线的视频信息也能够快速获取。大部分情况下个人比较喜欢使用 ffmpeg -i input.mp4 来快速查看,这种时候在终端上比较快速,而如果有些时候想要分析一下媒体文件,需要编程获取得到的媒体文件结果,显然 ffmpeg 的输出结果简直无法忍受,而 ffprobe 提供非常清晰的输出格式,非常方便的可以提供给编程软件解析使用。

官网说明:http://ffmpeg.org/ffprobe.html

命令格式

ffprobe [OPTION] file

常用的参数

-show_format            显示输入多媒体流的容器格式信息
-show_streams           显示输入多媒体流中每一个流的信息
-i input_file           指定输入文件
-print_format json      json 形式输出
-of 或者 -print_format  default/compact/csv/flat/ini/json/xml

命令行:

./ffprobe -print_format json -show_format -show_streams -i ./video/c.ts

其中:  

-print_format json 以 json 格式输出 , 
-show_format 输出封装格式信息 ,
-show_streams 输出流信息,
-i ./video/c.ts 输入文件

使用实例

基本用法

ffprobe -v error -show_format -show_streams input.mp4

输出该视频的基本信息,如果上面的命令输出结果过多,而只想要比如 size 可以

ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1 input.mp4

如果只想要结果可以

ffprobe -v error -show_entries format=size -of default=noprint_wrappers=1:nokey=1 input.mp4

上面的命令中:

  • -v 参数是日志输出级别
  • error 则略去了 build 和 generic 信息,暴露 error 错误
  • -print_format 则是输出结果格式

获取视频时长

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4

直接输出视频时长。

下面是一段 shell 脚本,之前遇到过有一批视频地址,想要获取这批视频的市场,用 ffprobe 就能够完成。

while IFS='' read -r line || [[ -n "$line" ]]; do
    lineArray=($line)
    echo ${lineArray[0]}
    duration=$(ffprobe -i ${lineArray[1]} -show_entries format=duration -v quiet -of csv="p=0")
    echo $duration
    echo "${lineArray[0]} ${duration}" >> duration.txt
done < "$1"

以 json 格式输出

ffprobe -show_streams -show_entries format=bit_rate,filename,start_time:stream=duration,width,height,display_aspect_ratio,r_frame_rate,bit_rate -of json -v quiet -i 98a74a06741a091b8a42aaa31b4edc66.mp4

输出:

{
    "programs": [

    ],
    "streams": [
        {
            "width": 720,
            "height": 1280,
            "display_aspect_ratio": "0:1",
            "r_frame_rate": "30/1",
            "duration": "40.833333",
            "bit_rate": "1710937",
            "disposition": {
                "default": 1,
                "dub": 0,
                "original": 0,
                "comment": 0,
                "lyrics": 0,
                "karaoke": 0,
                "forced": 0,
                "hearing_impaired": 0,
                "visual_impaired": 0,
                "clean_effects": 0,
                "attached_pic": 0
            },
            "tags": {
                "language": "und",
                "handler_name": "VideoHandler"
            }
        },
        {
            "r_frame_rate": "0/0",
            "duration": "40.890431",
            "bit_rate": "128102",
            "disposition": {
                "default": 1,
                "dub": 0,
                "original": 0,
                "comment": 0,
                "lyrics": 0,
                "karaoke": 0,
                "forced": 0,
                "hearing_impaired": 0,
                "visual_impaired": 0,
                "clean_effects": 0,
                "attached_pic": 0
            },
            "tags": {
                "language": "und",
                "handler_name": "SoundHandler"
            }
        }
    ],
    "format": {
        "filename": "98a74a06741a091b8a42aaa31b4edc66.mp4",
        "start_time": "-0.046440",
        "bit_rate": "1065995"
    }
}

外延

mediainfo 也是一个用来获取音频视频信息的工具,比如封装格式、音视频编码格式、码率等信息。

mediainfo 可以获取的信息包括

  • General: title, author, director, album, track number, date, duration…
  • Video: codec, aspect, fps, bitrate…
  • Audio: codec, sample rate, channels, language, bitrate…
  • Text: language of subtitle
  • Chapters: number of chapters, list of chapters

mediainfo 支持的格式

  • Video: MKV, OGM, AVI, DivX, WMV, QuickTime, Real, MPEG-1, MPEG-2, MPEG-4, DVD (VOB)…
  • Video Codecs: DivX, XviD, MSMPEG4, ASP, H.264, AVC…
  • Audio: OGG, MP3, WAV, RA, AC3, DTS, AAC, M4A, AU, AIFF…
  • Subtitles: SRT, SSA, ASS, SAMI…

mediainfo 输出的字段不容易被解析,表述方法不统一。例如,对于 h264 这种编码格式,mediainfo 可能输出的表述为 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10;还比如,对于 mp3 这样的音频格式,居然会分两个字段进行描述,分别说明 mpeg 和 layer3.

reference


2015-02-09 linux , ffmpeg , ffplay , ffprobe , command

每天学习一个命令:sed 流式字符编辑器

sed 全名叫 stream editor,是字符流编辑器,一次处理一行内容,能够完美地配合正则表达式使用。 处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出。sed 主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。 Sed 主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

awk 的典型示例是将数据转化为格式化报表。

行编辑器 ed

awk 的起源追溯到 sed 和 grep,再往前追溯就到了 ed,最初的 unix 行编辑器。关于 ed 编辑器可以参考之前的『文章』

sed 使用参数

sed [-neifr] [ 命令 ]

选项与参数:

  • -n :只有经过 sed 特殊处理的那一行(或者命令)才会被列出来。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。
  • -e :直接在命令列模式上进行 sed 的命令编辑;
  • -f :从文件执行 sed 命令,-f filename 则可以运行 filename 内的 sed 命令;
  • -r :sed 默认支持正则表达式,使用 -r 开启扩展的正则表达式
  • -i :直接修改读取的文件内容,而不是输出到终端。

命令说明: [n1[,n2]]command

n1, n2 :在 n1 到 n2 行之间使用命令,举例来说,如果我的命令是需要在 10 到 20 行之间进行的,则 10,20[ 命令行为 ]

command:

a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d :删除
i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s :替换,通常这个 s 的命令可以搭配正则 `1,20s/old/new/g`

实例

行删除及增加

以行为单位的新增 / 删除

将 /etc/passwd 的内容列出并且列印行号,同时,请将第 2~5 行删除,这里的删除是指在输出结果中删除,并不是真正去删除文件中的内容,如果要直接对文件进行修改,可以参考后文中的 -i 参数。

nl /etc/passwd | sed '2,5d'
1 root:x:0:0:root:/root:/bin/bash
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

说明:

  • sed 的命令为 ‘2,5d’ ,d 就是删除
  • sed 后面接的命令,请务必以 ‘’ 两个单引号括住

只要删除第 2 行

nl /etc/passwd | sed '2d'

要删除第 3 到最后一行

nl /etc/passwd | sed '3,$d'

删除空白行

sed '/^$/d' file.txt

在第二行后(即是加在第三行)添加内容

nl /etc/passwd | sed '2a drink tea'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
drink tea
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

那如果是要在第二行前

nl /etc/passwd | sed '2i drink tea'

如果是要增加两行以上,在第二行后面加入两行字

nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
Drink tea or ......
drink beer ?
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

以行为单位替换

以行为单位的替换与显示

将第 2-5 行的内容替换为自己的内容

nl /etc/passwd | sed '2,5c No 2-5 number'
1 root:x:0:0:root:/root:/bin/bash
No 2-5 number
6 sync:x:5:0:sync:/sbin:/bin/sync

显示特定行

仅列出 /etc/passwd 文件内的第 5-7 行

nl /etc/passwd | sed -n '5,7p'
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6 sync:x:5:0:sync:/sbin:/bin/sync
7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

这个 sed 的以行为单位的显示功能,就能够将某一个文件内的某些行号选择出来显示。

数据的搜寻并显示

搜索 /etc/passwd 有 root 关键字的行

nl /etc/passwd | sed '/root/p'
1  root:x:0:0:root:/root:/bin/bash
1  root:x:0:0:root:/root:/bin/bash
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh
4  sys:x:3:3:sys:/dev:/bin/sh
5  sync:x:4:65534:sync:/bin:/bin/sync

如果 root 找到,除了输出所有行,还会输出匹配行。

使用-n的时候将只打印包含正则的行。

nl /etc/passwd | sed -n '/root/p'
1  root:x:0:0:root:/root:/bin/bash

输出指定的行数 (输出 2-5 行的数据)

sed -n '2,5p' file

数据搜寻删除

删除 /etc/passwd 所有包含 root 的行,其他行输出

nl /etc/passwd | sed  '/root/d'
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
3  bin:x:2:2:bin:/bin:/bin/sh

搜索执行命令

搜索 /etc/passwd, 找到 root 对应的行,执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把 bash 替换为 blueshell,再输出这行:

nl /etc/passwd | sed -n '/root/{s/bash/blueshell/;p}'
1  root:x:0:0:root:/root:/bin/blueshell

如果只替换 /etc/passwd 的第一个 bash 关键字为 blueshell,就退出

nl /etc/passwd | sed -n '/bash/{s/bash/blueshell/;p;q}'
1  root:x:0:0:root:/root:/bin/blueshell

最后的 q 是退出。

数据的搜寻并替换

结尾的 g 表示匹配所有的

sed 's/regex/replace/g' file.txt

假如没有结尾的 g,比如

sed 's/book/books/' file.txt

则表示匹配一个 book,并替换为 books。

多点编辑

一条 sed 命令,删除 /etc/passwd 第三行到末尾的数据,并把 bash 替换为 blueshell

nl /etc/passwd | sed -e '3,$d' -e 's/bash/blueshell/'
1  root:x:0:0:root:/root:/bin/blueshell
2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh

-e表示多点编辑,第一个编辑命令删除 /etc/passwd 第三行到末尾的数据,第二条命令搜索 bash 替换为 blueshell。

直接修改文件内容

sed 可以启用 -i 选项直接修改文件的内容,不必使用管道命令或者重定向。

sed -i 's/\.$/\!/g' filename.txt         # 将文件每一行最后的 `.` 替换为 `!`
sed -i '$a # add to last' filename.txt   # 每一行后面 ($) 增加 (a) 后面的内容

sed 可以直接修改文件内容,这样对于大文本,可以不需要使用 vim 打开在进行编辑,直接使用 sed 行读取编辑就能够实现行修改和替换的作用。

过滤部分内容

利用替换可以将不需要的内容替换成空

sed -n -e 's/^.*id=//p'

可以打印 id= 后面的内容,然后再做处理。

Sed 处理 Tab

在 sed 的语法中,比如替换一行中的 Tab 到逗号,会发现

sed -i 's/\\t/,/g' some.txt

\t 其实并没有用,而是需要按下 Ctrl+v 然后输入 Tab 才有效。

sed -i 's/	/,/g' some.txt

这样才有效。

reference


2015-01-15 linux , command , sed , editor

Vim 插件之: vim-surrounding

vim-surrounding 插件可以轻松的一次性修改成对出现的,比如 (), [], {}, 双引号,XML 标签等等。提供了

  • 增加
  • 删除
  • 修改

包围内容的方法。

首先放上链接:

Installation

Plugin 'tpope/vim-surrounding'

Usage

用下面的例子做 demo

print("hello world")

光标定位在 hello world 包括引号,那么使用如下的命令可以实现双引号替换成单引号:

cs"'

change surrounding

Change surroundings is cs. 接受两个参数,目标,和替换内容

cs"'            # change " to '
cs"<q>          # change " to <q>
cs)]            # change ) to ]

如果要替换标签的内容,比如说将 h1 替换为 h2,则需要用到 t

<h1>Title</h1>

则需要 cst<h2>,同理要将 <h1> 替换成双引号,则 cst"

假如有一行内容

<h1>This is a title</h1>

cs 还有一个变种 cS,效果则是将变化的内容放到新行中。

add surrounding

给 hello 增加 <h2>

hello

那么可以使用 csw<h2>,简单记忆成 change surrounding of word <h2> ,给 word 增加 <h2> 标记

可以看到 cs 接受两个参数,会用后一个参数替换前一个。

delete surrounding

比如删除双引号,delete + surrounding + “

ds"             # delete surrounding "
ds(             # delete surrounding (
dst             # delete surrounding tags

dscs 都将 target 作为第一个参数,所有的 target (text-objects) 目前都是一个字符。

(), [], {}, <>
b, r, B, a 分别对应上面括号
', ", `
t 表示 HTML 或者 XML 标签
w, W, s 分别是 word, WORD, sentence
p 表示 paragraph

ys 给 surrounding 增加标记

给 hello 增加 <h2>

hello

使用更加复杂一点的 you surrounding inside word with <h2>

ysiw<h2>

ys 接受 vim motion 或者 text object 作为一个 object

如果要对整行操作可以使用 yss 后接修改的内容,比如给整行增加花括号

yssB

cS 一样,ys 也有变种版本 ySySS,会在新行添加内容,比如给 paragraph 添加双引号

ySS"

Visual mode

在选择模式下可以使用 S + 需要添加的内容,来看快速对选择的内容增加 surroundings。

比如我想要给下面这一行中的一部分内容,比如说 main title 增加一个 <h1> 标记。

This is the main title  sub title`

那么只需要将 This is the main title 使用 v 选中,然后按下 S<h1>Enter 回车之后前后就加上了 h1 标签。

在单行选择模式下, surroundings 会添加在行中,在 blockwise 选择模式,每一行都会 surround。

a = testa
b = testb
c = testc

加入上面的三行内容,想要给后面的内容增加双引号,那么可以使用列选选择然后 S + " 就可以快速添加。

Conclusion

Normal mode
-----------
ds  - delete a surrounding
cs  - change a surrounding
ys  - add a surrounding
yS  - add a surrounding and place the surrounded text on a new line + indent it
yss - add a surrounding to the whole line
ySs - add a surrounding to the whole line, place it on a new line + indent it
ySS - same as ySs

Visual mode
-----------
s   - in visual mode, add a surrounding
S   - in visual mode, add a surrounding but place text on new line + indent it

2015-01-04 vim , vim-plugin , vim-surrounding , tpope

jhat 使用

jhat 是 Java 的堆分析工具(Java heap Analyzes Tool),在 JDK 6u7 之后成为 JDK 标配。

用法

jhat [options] heap-dump-file

说明:

  • options 参数
  • heap-dump-file 二进制 Java 堆文件,可以使用 jmap 导出

可选参数

-stack false|true

关闭对象分配调用栈跟踪 (tracking object allocation call stack)。 如果分配位置信息在堆转储中不可用,则必须将此标志设置为 false. 默认值为 true.

-refs false|true

关闭对象引用跟踪 (tracking of references to objects)。 默认值为 true. 默认情况下,返回的指针是指向其他特定对象的对象,如反向链接或输入引用 (referrers or incoming references), 会统计 / 计算堆中的所有对象。

-port port-number

设置 jhat HTTP server 的端口号。默认值 7000.

-exclude exclude-file

指定对象查询时需要排除的数据成员列表文件 (a file that lists data members that should be excluded from the reachable objects query)。 例如,如果文件列列出了 java.lang.String.value , 那么当从某个特定对象 Object o 计算可达的对象列表时,引用路径涉及 java.lang.String.value 的都会被排除。

-baseline exclude-file

指定一个基准堆转储 (baseline heap dump)。 在两个 heap dumps 中有相同 object ID 的对象会被标记为不是新的 (marked as not being new). 其他对象被标记为新的 (new). 在比较两个不同的堆转储时很有用。

-debug int

设置 debug 级别。0 表示不输出调试信息。 值越大则表示输出更详细的 debug 信息。

-J< flag >

因为 jhat 命令实际上会启动一个 JVM 来执行,通过 -J 可以在启动 JVM 时传入一些启动参数。例如,-J-Xmx512m 则指定运行 jhat 的 Java 虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个 JVM 启动参数,则传入多个 -Jxxxxxx.

实例

使用如下命令获取二进制堆转储文件

jmap -dump:format=b,file=heap-dump.hprof pid

然后使用

jhat -J-Xmx1024m heap-dump.hprof

来查看和分析堆信息,然后访问本地 7000 端口即可。

jhat 中可以使用 OQL(对象查询语言)来查询,这个 OQL 也是非常庞大,如果要展开说就很多了,这里举一个例子,比如要查找字符串对象中,保存了长度大于 100 的字符串可以使用

select s from java.lang.String s where s.count > 100

关于 OQL 更多的使用方法可以网上查询。

reference

  • map jhat

2015-01-03 java , jvm , jhat , heap , tool

jmap 命令使用

jdk 自带的命令用来 dump heap info,或者查看 ClassLoader info,等等。

命令格式

jmap [OPTION] PID

使用实例

不加任何参数

命令

jmap pid

查看 pid 内存信息。

查看堆信息

jmap -heap pid

查看堆对象信息

统计对象 count ,live 表示在使用

jamp -histo pid
jmap -histo:live pid

查看 classLoader

jmap -clstats pid

生成堆快照

jmap -dump:format=b,file=heapdump.phrof pid

hprof 二进制格式转储 Java 堆到指定 filename 的文件中,live 选项将堆中活动的对象转存。

执行的过程中为了保证 dump 的信息是可靠的,所以会暂停应用, 线上系统慎用

文件可以用 jhat 分析。

错误

在运行 jmap 的时候可能遇到如下错误:

Attaching to process ID 18078, please wait...
Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.131-b11. Target VM is 25.152-b38
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.131-b11. Target VM is 25.152-b38
    at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:435)
    at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:305)
    at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
    at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
    at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
    at sun.jvm.hotspot.tools.PMap.main(PMap.java:72)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.tools.jmap.JMap.runTool(JMap.java:201)
    at sun.tools.jmap.JMap.main(JMap.java:130)

解决办法就是保证 jmap 的版本 也就是 JDK 的版本和运行的 JVM 的版本,也就是 JRE 的版本一致。

我使用 Java VisualVM GUI 来查看当前进程使用的 Java 版本,或者直接 ps 查看进程,然后再使用对应的 jmap 的版本。

要保证 jmap 运行的版本和运行的 java 进程程序使用同一个的 JRE(JDK) 的方法就是在 Linux 下使用

sudo update-alternatives --config java

来配置保证使用相同的 Java 程序。

reference


2015-01-02 jmap , jstack , jdk , jvm , java

网件 WNDR3800 刷机

进入 U-boot

路由先断电,然后按住复位键或者 WPS 键开机,保持 10S 钟左右,然后用网线连接 LAN 口和电脑,打开浏览器进 192.168.1.1,就可以进入 U-boot 控制台,进去刷写固件

操作路径 :固件更新 -> 固件 -> 选择固件文件 -> 上传 -> 更新,刷完后机器会自动重启。

固件

自行编译

或者下载他人编译好的固件。


2014-11-25 openwrt , wndr3800 , router , linux , tutorial

电子书

最近文章

  • 配置 Rime 在 Vim 下退出编辑模式时自动切换成英文输入法 半年以前在 Obisidian 的文章下面有人曾经问过我一个问题,如何在 Vim 或者其他使用 Vim 模式的编辑器,比如 IntelliJ,或者 Obisidian 开启 Vim 模式后方便地切换中英文输入法,因为在编辑模式和普通模式下,需要经常切换输入法,使得体验变得非常槽糕。
  • Asus RT-AC86U 设置 前些天给家里买手机正好凑单了一个 Asus RT-AC86U,正好可以代替出了两次故障的小米 3G。
  • 扩展 Proxmox 系统分区以及 Proxmox 文件系统初识 昨天想要扩展一下之前安装的 Proxmox 容量,对系统进行了一次关机,然而关机之后就悲剧的发现在 U 盘中的系统启动不了了,将 U 盘拔下检测之后发现 U 盘可能挂了,一个全新的 U 盘,在连续 192 天运行之后挂掉了。无奈之下只能想办法先恢复一下 Proxmox 系统以及安装在系统之上的 OpenMediaVault 了。
  • 『译』我最喜欢的命令行工具 偶然间看到一篇介绍 cli 的文章,感觉写得不错,正好借此机会也整理一下我之前使用过,以及觉得非常值得推荐的 CLI 工具。
  • 使用 Clonezilla 将硬盘中系统恢复到虚拟机中 今年陆陆续续将工作的环境迁移到了 macOS,虽然已经把日常的资料迁移到了 macOS,但是之前的 Linux 上还有一些配置,以及可以的一些测试还需要用到 Linux 虚拟机,所以我就想能不能用 Clonezilla 将磁盘中的系统备份然后恢复到虚拟机里面。因为我发现 macOS 下的 Fusion 还是很强大的。