写在我即将毕业旅行前

忙碌的一天,又要到下班的时候了,一天好似很忙,其实回过头来仔细想想,一天好像并没有做太多的事情,然而确实是这样子度过一天了,就像我们的青春岁月,看样子并没有经历什么,但它就这样匆匆过去了,亦忧伤,亦憧憬。

还有两周就要回校参加毕业典礼了,向公司请了十天假,算上端午以及周末,大概有半个多月属于自己的日子吧,也算是大学最后一点还是以学生身份存在的时间了。早在几个月前我都在筹划这次出去游玩了,因为我想出去走走,想出去看看。纵观整个大学生涯,自己也算是去了不少地方了,屋子里车票、机票已经收纳了一沓了。看着每一张车票的起点与终点以及行程时间,还能想起那时候发生过的事,或许有些事还在记忆里面,或许可能存在我的日记里面,或许我什么也记不得了。

2015年8月15日 绵阳-上海:距离大学开学还有一个月了,父亲陪我度过了整个高三,待我拿到通知书后,我与父亲来到上海。父亲是一个节俭的人,买了绿皮车,将近两千多公里的路程,三十多个小时的行程,脚没有办法舒展开,时间长了别的难受。当看到那些没有座位的那些人要不停地给过路人让路的时候,我觉得自己也挺幸运的,至少我还有一个属于自己的位置,不需要为别人让路。在这煎熬的行程中,我并没有去抱怨,我只是一直在想,我以后一定要努力,一定不会让我和我父亲再受这样的遭遇。

2015年9月14日 上海-温州:马上就要大学开学了。离开了父母,将一个人去面对新的生活了,路过杭州的时候,车窗外一片开阔的平原,让我看到极具江南特色的景象:鳞次栉比的房屋伴着小河流,来来往往的车辆川流不息,很憧憬那样“小桥流水人家”的生活,这大概是对杭州的第一印象吧。

2016年1月22日 温州-成都:大学寒假第一次回家,运气很好,我和同学抢到了卧铺票,有了一个较舒服的乘车环境。与我通行的还有很多大学川籍同学,因此一路上也变得并不孤独,时而和他们“摆龙门阵”,累了就回床铺上面去休息一下,我记得我当时带了一本《浪潮之巅》。

2017年7月18日 温州-太原:第一次坐飞机,带着许多憧憬出发,但让人失望的是我错过了那一天的飞机,原本安排的好好的行程,一下子把我的计划打乱了。这时候我真的很迷茫,我不知道该去哪里,我也不知道该做什么,整个脑子是一片空白,在街上走走,最后回学校见到了精神支柱。然后去了太原平遥古城,喜欢太原老大爷那种街边悠然下着象棋的生活。

2017年7月10日 嘉兴-福州:又是一次暑假,我去嘉兴看望我爸,然后我爸他们提议去福州找我的大姑爷家玩,这一次我和我的父亲快吵了一架,因为我想让他买一张动车票,可父亲执着的只买绿皮车票,他觉得能节约钱,可是我觉得时间才是最重要的,那时候我没有一点收入,我说服不了他,绿皮车从晚上八点开到第二天早上八点,整个一晚我没有怎么睡,感慨太多,我也告诉我自己要努力。当时也写过一篇随笔:2017年710随笔 于嘉兴-福州列车

2017年9月02日 温州-杭州:这一次算是第一次以异地恋的身份去见了那时喜欢的人,一路忐忑。记得那时候带的是一本《小王子》,但是感觉没有看太懂,好像过了天真的年纪,已经看不太明白小王子的天真了。

2018年8月23日 杭州-太原: 或许真的是和太原有缘分,时隔一年再次去太原,这次我并没有错过,过了一年,变得成熟些了,这时候我已经在参加实习了。这次是代表整个学校唯一一只队伍进电子商务全国总决赛,压力与动力并存吧。

2019年2月14日 德阳-成都:这是最近一次从家出发去杭州在成都中转,在德阳站中因为高铁晚点,我遇到了一个女孩子,或许这是读大学以后认识的第一个非本大学的四川女孩子吧,在她回头的那一下,我感觉我好像心动了一下。或许是缘分,我和她是同一辆车,在德阳到成都只有短短半个小时的行车时间,居然车晚点将近两个小时,于是和她就聊啊,聊啊,从小学说到初中高中,从高中说到大学,再从大学说到实习。原来她也和我一样是大四的学生,原来我和她大学同学是初中高中同学,真的是缘分吧。第一次是多么的希望列车晚点的时间能够再长一点。只可惜,我本将心照明月,奈何明月照沟渠。

……

2019年6月10日 杭州-青岛、烟台、威海、大连? 或许是在被公司同事的鼓励下:现在有时间多出去玩玩吧,实习一天也没有多少钱,等以后正式工作了,有钱也没有时间了,趁现在,多出去走走吧。感谢芳姐姐对我的支助,让我有机会去计划这次旅行。

这一次,我是第一次一个人的旅行,我想在这炎热的夏天里,走出屋子,踏上行程。去坐一次轮渡、我还想再使用学生证享受一次学生特权、然后去看看海、去吹吹风、去看看更多的天空、去认识的人并说:“很高兴认识你”。也算是对整个大学青春岁月画上一个句号吧。
那接下来的一路上又会发生什么故事呢?

最后以王小波的《黄金时代》结尾吧:那一天我二十一岁,在我一生的黄金时代,我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云,后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消逝,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。

我还年轻,我还可以到处走走,我还有很多想要去憧憬的、盼望的……

–于2019.06.04 7:24 即将下班回家

95后,毕业半年,你过得好吗?

原帖:https://www.zhihu.com/question/307640916/answer/686748493
95后,毕业半年,你过得好吗?
亲爱的95后,毕业两年,你过得怎么样了?充实或是空虚,甜蜜还是苦涩,热爱生活还是生无可恋,期待写下你的故事。

96年 刚参加完学校的毕业答辩回来,离毕业也不远了吧。从去年暑假开始就在杭州实习了,最开始是做php后端,后来转到了安卓开发。实习了差不多快一年了,就要成“资深实习生”了。说实话我感觉我过得很充实吧,依然热爱生活。实习期间租房+伙食费+路费,如果不干别的什么事,一个月请假天数在两三天的话,每个月的生活是过得还算是比较舒服的,不太愁。实习之后就第一个月问家里要了一下房租和押金,以及驾校的学费。可以说完全独立了吧。拿到公司的offer是10几k,还是蛮期待拿毕业证转正的时候吧:
96年 刚参加完学校的毕业答辩回来,离毕业也不远了吧。从去年暑假开始就在杭州实习了,最开始是做php后端,后来转到了安卓开发。实习了差不多快一年了,就要成“资深实习生”了。说实话我感觉我过得很充实吧,依然热爱生活。实习期间租房+伙食费+路费,如果不干别的什么事,一个月请假天数在两三天的话,每个月的生活是过得还算是比较舒服的,不太愁。实习之后就第一个月问家里要了一下房租和押金,以及驾校的学费。可以说完全独立了吧。拿到公司的offer是10几k,还是蛮期待拿毕业证转正的时候吧。

在这里我好想讲讲近一年的实习生活,过得日子也算是充满酸甜苦辣吧。从刚开始从公司实习开始讲吧:当时的我好像什么都会,php也会、python、java、安卓啥都会一样,经过被阿里面试打击后,可以看此帖子:https://www.zhihu.com/question/268713348/answer/352195054

我稍微收敛了一点了吧,最后进入了杭州一家互联网公司,刚开始是干php的,干了一个多月一点,感觉并没有学到多少东西,做的业务比较多,因为我属于“实习”的状态吧,安排的任务也比较少,所以每天的日子过得也很“悠闲”,每天干完就回家了,大概下午六点多就走了……这前面的php实习期间算是伏笔吧。之后由于刚好公司内部有个安卓实习的位置,好像是没有招到合适的吧,最开始面试的时候说了我什么都会,所以有幸被调到了安卓组里面。我发现好像每天的需求做起来还是那么简单,所以每天依然走得特别早吧,还有点沾沾自喜的样子,然而回到家并没有继续学习相关知识,要不就和女朋友( broken up.)漫步钱塘江边,要么就是回到家里写写字看看其他类型的书,反正过得很舒服吧。

直到有一天我被“伤自尊”了,一度自我感觉良好的我被组内的同事叫去看一个问题,我看了很久也看不出来,因为很多Java基础我都有些快忘了吧,基础不太扎实,我对代码的深度理解也有问题,我只停留在用的阶段,我不懂其原理,我只会使用,我连他们的源码都没有看过。

后来被他鞭策道:你现在确实很厉害,比我当时刚出大学的时候厉害多了,但是我觉得我一点比你做的好,那就是我一直坚持在学习。你每天回去那么早没有学习,你在做什么?你现在对你自己的定位有些问题,现在你虽然觉得这些需求能做,但是你知道这些需求都是最简单的,工作难度都是最低的,我们为什么不把那些高难的任务给你?是因为你现在能力还不够,现在给你简单任务就是想让你多一些时间去学习,让你尽早能够跟上团队的步伐。实话给你说:你很菜!你真的很菜……你师傅可能不太好给你讲所以我才给你讲这些,如果你现在的状态,你永远只能做那些最简单的任务,可能连之后的校招offer都拿不到,就算侥幸8月份能拿到校招offer,也许你之后能侥幸转正,但是你不坚持学习的话迟早会被行业淘汰……

这位前辈的话一直在我脑海里面印象深刻,我是一个不服输的人,在被鞭策后,我真的不服气,我也不认输,当时他叫我周末会去看一看”EventBus”的源码,然后周一向他汇报一下看的结果。周末连续两天都在看其源码,说实话看起来真的很痛苦,因为自己以前都是直接用,不会去关心它内部的逻辑,但是依然坚持看了两天,做了很多笔记,把它内部实现逻辑也搞明白了。忐忑不安地等到了周一,我向他汇报,我给他讲述了内部原理以及源码解读,他给我提了几个问题,我答上了一半多一点,我正沾沾自喜时,他给我说:如果给你这次评价满分一百分的话,我最多给你打20分。

!!!!20分,我的天!我当时一下子整个人就不好了,甚至都想去反驳他了,可是他之后给我讲得东西,让我打消了这个念头,我确实菜。我确实认识到了自己的不足,对源代码的解读真的还不够,Java基础也不太扎实,包括我的师傅也这样认为,我是一个不服输的人,不服气。我向我师傅请教了,大概给自己定了一个短期的学习计划:先把Java基础过一遍,再过一遍Android基础过一把,再开始去理解安卓深度的东西。从此开始,我感觉我开始暴走了。

我每天都背一两本书回去学习,在地铁上有时候位置空我也会拿出来继续学习,偶尔还是会找女朋友去玩,但是当我11点钟回到家,我还是会拿出书来继续学习。然后第二天我会和我的师傅讲我昨天学了什么,然后讲出我的疑问。我的师傅真的是一个很耐心的人,他会给我仔细地讲解,包括平时的问题,如果我向组内请教问:1+1等于几? 他们会告诉我等于2。然而我的师傅会告诉我 加法口诀表,甚至是乘法口诀表。那时候我每天都不曾忘记学习,每天都会去研究,那一段时间我真的压力很大,经常性的失眠,心跳特别快,后面也去看过一次医生说是“心悸”。当然那也是在压力下人做出的一些极端反应,所幸的是我坚持了过来。八月中,HR把我叫了过去,我还以为她又要批判我了,因为最开始被团队的人给鞭策了之后,她也找过我谈话了:你如果还继续现在的状态,可能之后的校招offer都不会发给你。这一次不一样了,她给我讲了我转正后的待遇,什么期权还要公积金啥啥啥的,然后讲了工资多少多少,其实工资是有点超乎我预期的。她讲完之后,我真的笑了,真的开心了,我忍不住笑了起来。她问:开心吗?我一直点头。或许这是对努力之后最好的回报吧!那一刻真的,心里有太多说不出来,打心地的开心。

拿到校招offer我还是坚持学习吧,只是强度没有之前那样强了,但是依然坚持每天要学习。现在我觉得每天每周过得生活都特别的充实吧,我们不是996。公司9点上班,弹性打卡9个小时,也就是说早上9点钟打卡,下午6点钟就可以走了,但是大部分人并不是6点钟走。现在自己每天早上都会比较期盼去公司,因为每次做需求,我都并不把他当做我的“工作”在做,而我认为那是我的兴趣爱好。我喜欢敲代码的感觉,我喜欢那种自己去设计思路想法,最后打磨出来产品的过程。而我们做出的成果会是被几百万用户所使用,肩上也有一种责任感吧。然后也带来了成就感,比如公司的宣传片或者展示公告大牌上面印着app的应用界面图,会看到里面有一部分是我做得,我是多么的开心呀。一次室友的朋友过来了,我们聊着天,后来聊到它妈妈也在用我们做得app,它给我说它妈妈怎么怎么这个App,我说啊,这里就是我做的呀。哇!厉害咯。我对工作保持乐观态度,因为我可以向厉害的前辈们请教问题,向他们学习,我每天都感觉自己有收获,所以在公司让我感觉过得很开心,没有太大的压力,每天都在进步。

当然回到家了,我也有该做的事,我也有我的兴趣,大概每天7.30左右下班回家,回到家快8.30了。我也有自己追求,我在学日语,打算能在明年去一趟日本,感受一下不同的文化。日语学累了,我会继续学习相关专业的书籍,或者写写字,我比较喜欢写字,虽然没有很飘逸炫酷,但是我觉得写完之后看起来特别的舒服,这也算是一种成就感把,大概学到10.30收拾洗漱,大概11点左右上床了,我会继续看一些相关的书籍,比如最近在看《红楼梦》,厚厚的一本,渐渐地书签也跑到了中间的位置吧。

至于周末,我可能比较放松吧,周末我会学着去做做菜,不再想吃外卖了,真的难吃且贵。

有时候也会去绿城主场看看绿城踢球吧,虽然心里支持的四川队,但是远在他乡没有办法了。

最后附上自己毕业答辩后拍的照片,算是对自己充实的大学生活的怀念吧。

总之,我觉得自己每天都过得很充实吧,很热爱自己的生活。

愿每一个你们的生活都过得幸福。

基于Volley框架的返回数据的范型处理

在平时最普通的Volley的网络请求中,我们StringRequest是这样请求网络数据的:

StringRequest stringRequest = new StringRequest("http://www.baidu.com",
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("TAG", response);
            }
        }, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
        Log.e("TAG", error.getMessage(), error);
    }

注意在onResponse的时候是拿到的string类型,拿到string后对其再进行相关的解析,我们是否是可以对其直接封装然后拿到具体想要类型的model数据结构呢?所以对其网络请求架构进行一次封装,到达如下效果:

GetGoodDetailByGidRequest getGoodDetailByGidRequest = new GetGoodDetailByGidRequest(mCouponId,
           new RequestListener<List<CouponModel>>() {
               @Override
               public void onSuccess(List<CouponModel> result) {
               }
               @Override
               public void onError(Exception e) {
                   e.printStackTrace();
                   stopLoadingDialog();
               }
           });

这里我们在构造Request的时候指定了返回数据的类型,这样的话就方便了我们在写业务的时候直接使用解析好的数据结构,具体如何做到的呢?

一、让每个Request基于一个带有范型请求类

public abstract class BaseApiRequest<T>  

这里的T就是目标请求期望的model类
在具体实现的时候继承基类,并指定返回类型,下面是一个例子:

public class GetGoodDetailByGidRequest extends BaseApiRequest<List<CouponModel>> {
    public static final String url = CURL.GoodDetailURL;
    public GetGoodDetailByGidRequest(String goodId, RequestListener<List<CouponModel>> requestListener) {
            super(requestListener);
            this.mUrlParams.put("id", goodId);
    }
    @Override
    public String getBaseUrl() {
            return url;
        }
}

二、在基类中构造网络请求

  protected StringRequest getStringRequest() {
    return new StringRequest(requestMethod, getRequestUrl(),
            response -> parseJson(response),
            error -> requestListener.onError(error)) {
        @Override
        protected Map<String, String> getParams() {
            return mEntityParams;
        }
    };
}

在此处实现可以看到Request在基类中进行,然后分别处理返回结果

三、对返回结果进行解析

    private void parseJson(String response) {
    int responseCode = 0;
    int errorCode = 400;
    try {
        JSONObject jsonObject = new JSONObject(response);

        String resultString = jsonObject.getString("data");

        if (jsonObject.has("code")) {
            responseCode = jsonObject.getInt("code");
        }
        if (jsonObject.has("error")) {
            errorCode = jsonObject.getInt("error");
        }

        if (responseCode == 200 || errorCode == 0) {
            if (!TextUtils.isEmpty(response)) {
                Type type = getTType(requestListener.getClass());
                //泛型是实体或者List等类型
                T t = JsonUtils.fromJson(resultString, type);
                requestListener.onSuccess(t);
                return;
            }
            ToastUtils.showToast("Data is empty!");
        }
        ToastUtils.showToast("Response code is error.");
        requestListener.onError(new ParseError());
    } catch (JSONException e) {
        ToastUtils.showToast(e.toString());
        e.printStackTrace();
    }
}

这里是最关键的一步,由于和后端约定好相关返回字段,那么只需要解析字段中目标model的数据,其中比较重要的是这段代码

Type type = getTType(requestListener.getClass());
//泛型是实体或者List等类型
 T t = JsonUtils.fromJson(resultString, type);
 requestListener.onSuccess(t);

通过封装好的 JsonUtils将String转化为对应的model类型,我们知道json转实体对象的时候,需要指明其类type,那这里的type是如何获取到的呢?

其中getTType ()的具体实现为:

 public static Type getTType(Class<?> clazz) {
    //以Type的形式返回本类直接实现的接口.
    Type[] types = clazz.getGenericInterfaces();
    clazz.getInterfaces();
    if (types.length > 0) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        Type[] interfacesTypes = ((ParameterizedType) types[0]).getActualTypeArguments();
        return interfacesTypes[0];
    }
    return null;
}

通过次方法能够获取到请求实现中所指明的请求类型,其中getGenericInterfaces等相关原理可以阅读:https://my.oschina.net/617669559/blog/3012228

所以对于

public class GetGoodDetailByGidRequest extends BaseApiRequest<List<CouponModel>>

那么获取到的就是List类型

四、通过Listener回调相关解析结果

拿到解析好的result并回调给构造Request方法中的listener使用

T t = JsonUtils.fromJson(resultString, type);
requestListener.onSuccess(t);

这样对整个网络请求后的返回数据直接进行解析方便多了。

总结:

1、本文最主要是对基本Request类进行改造,以达到不需要每次重复写解析返回的String数据

2、在获取目标的类的类型的时候,主要是去获取基类中的“T”类型

3、设计不仅适用用Volley同样适用于其他类似的网络请求框架

小弟不才,如有问题,欢迎指出。

Java反射中getGenericInterfaces和getInterfaces的解读

今天在做解析网络请求后得到的数据的转化的时候用到了:getGenericInterfaces这个方法。

 /**
  * 获取回调接口中 T 的具体类型
  *
  * @param clazz
  * @return
  */
   public static Type getTType(Class clazz) {
    //以Type的形式返回本类直接实现的接口.
    Type[] types = clazz.getGenericInterfaces();
    if (types.length > 0) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        Type[] interfacesTypes = ((ParameterizedType) types[0]).getActualTypeArguments();
        return interfacesTypes[0];
    }
    return null;
}

其中回调接口为:

new RequestListener>() {
   @Override
    public void onSuccess(List result) {}

在解析数据的时候这样操作,目的是为了对所有返回的数据进行数据转化为所指定的类型:

Type type = getTType(requestListener.getClass());
     //泛型是实体或者List等类型
     T t = JsonUtils.fromJson(resultString, type);
     requestListener.onSuccess(t);`

类RequestListener为:

public interface RequestListener {
    void onSuccess(T result);
    void onError(Exception e);
}

使用Gson进行json的解析,T fromJson(String json, Type typeOfT);那么怎么才能获取到RequestListener中的的类型呢?
于是我们从接口获取参数化类型处理。

官方文档解释

getGenericInterfaces:

Returns the {@code Type}s representing the interfaces directly implemented by the class or interface represented by this object.释意:返回表示由此对象表示的类或接口直接实现的接口的{@code Type}。

getInterfaces:

Determines the interfaces implemented by the class or interface represented by this object.
释意:返回由此对象表示的类或接口实现的接口。

从解释上面来看出来了,差异在于“接口实现的接口的Type”,接下来用具体示例来解释区别

private class Food{
    String foodName;
}
private interface Eat{
    void eat(String things);
}
private interface Run{
    void run();
}
private class Dog implements Eat,Run{
    @Override
    public void run() { }
    @Override
    public void eat(String things) { }
}
private void main() {
    Class clazz = Dog.class;
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    Class[] interfaces = clazz.getInterfaces();
}
运行结果
![](https://oscimg.oschina.net/oscnet/245442107557694aef0f07c25be0740187c.jpg)

我们可以看到,clazz.getGenericInterfaces()与clazz.getInterfaces()并没有任何差异。因为 并没有:“实现的接口的Type”

接下来看另一段代码,我们对Eat接口改造一下,增加一个参数化类型

private class Food{
    String foodName;
}
private interface Eat{
    void eat(T things);
}
private interface Run{
    void run();
}

private class Dog implements Eat,Run{
    @Override
    public void run() { }
    @Override
    public void eat(Food things) { }
}
private void main() {
    Class clazz = Dog.class;
    Type[] genericInterfaces = clazz.getGenericInterfaces();
    Class[] interfaces = clazz.getInterfaces();
}
运行结果:

关于位运算和HashMap中一个求最小2次幂的算法

今天在HashMap的内部源码的时候,看到这样一个算法:

/**
 * Returns a power of two size for the given target capacity.
 * 返回大于或等于 cap 的最小2次幂
 */
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

第一眼看起来确实是一脸懵逼,通过只知道这是一个获取该数的大于或等于 cap 的最小2次幂,这么厉害,咋实现的呀?

1、数据在内存中如何存储?

我们知道”<<” 和 “>>” 分别代表 左移和右移位运算符号,表示 乘以2 和除以2(大多数时候适用),”>>>”还是第一次见,这是代表什么意思呢?说到这里我们不得不去了解一下数据是如何存储在内存中的:

在32位的计算机系统中,int型数据占几个字节? 4字节。其中每个字节有8个比特位,表示二进制位,位是计算机内部数据储存的最小单位。这是所有编程语言学习者都知道的。也就是说 int类型在内存中有4*8 == 32个比特为 所以如果以整形数10为例,那么它在内存中完整存储的形式为:

00000000 00000000 00000000 00001010 ->对应 1x2^3+0x2^2+1x2^1+0x2^0 =10

那么int类型表示最大的数是不是就是:

11111111 11111111 11111111 11111111 ->对应 1x2^31+1x2^30…1x2^1+1x2^0

但为我们知道int类型的最大值为:2^31-1,显然上面的答案不是正确的。

这是因为在所有被int类型占用的比特位中,左起第一个位(即最高位)就是符号位。int类型的符号位上,0表示正数,1表示负数。在32位操作系统下,其余后面31位是数值位。也就是说:

11111111 11111111 11111111 11111111 所代表的数字为:1x2^30+1x2^29…1x2^1+1x2^0 的相反数为:-(2^31-1)

这里需要注意的是,按原先的逻辑去理解的话

00000000 00000000 00000000 00000000 为+0

10000000 00000000 00000000 00000000 为-0

那他们表示的意义是一样的么?
实际上,在32位系统下int类型中,我们计算机已经强行规定了这种情况,数字0采用“+0”的表示方法,即 00000000 00000000 00000000 00000000;而“-0”这个特殊的数字被定义为了-2^31。

因此我们看到32位系统下int类型的取值范围中,负数部分比正数部分多了一个数字,正数的最大取值是2^31-1,而负数的最小取值是-2^31。正数部分之所以要减去1,是因为被数字0占用了“+0”,而负数部分不需要用来表示0,因此原本的“-0”就用来表示-2^31这个数字。

2、位运算如何进行?

至此我们明白了数据在计算机中的存储形式,那位运算具体怎么运行的呢?
以10和-10为例,其二进制完整表示为:00000000 00000000 00000000 00001010 和 10000000 00000000 00000000 00001010 为了便于观察,我们取后面8位:00001010

  • 对于符号位移

例如将10的二进制向左移1位:那么变成 0001010 0 == 20 原先二进制数的第一位被移除,而最后一位被舍弃。将10的二进制向右移1位 原先二进制数最后一位被移除,第一位补0,则变成 000101 ==5

如将-10的二进制向左移1位, 10000000 00000000 00000000 00001010则变成:

10000000 00000000 00000000 0010100 为-20

如将-10的二进制向右移1位, 10000000 00000000 00000000 00001010则变成 :

注意这里多了一个0-> 1 00000000 00000000 00000000 0000101 <-注意这里少了位

也就是说符号移动,会保留原来的符号位,不会因为右移左移而带走符号位。

  • 对于无符号位移

相反无符号位移会不关注符号位。
例如将-10向右无符号右移就会变成:

010000000 00000000 00000000 0000101 变成了一个很大的正数了!!

如果将-10无符号左移,则变成:

00000000 00000000 00000000 00001010 = 20

但是!!并没有无符号左移动这样一件事情!
跟右移运算不同的是,无符号左移和左移是一样的。因此java没有无符号左移运算。(<<<和<<<=将报错)

因为无符号右移运算需要考虑符号位的右移,而符号位只存在于二进制表示的最左边,最右边没有。所以不用区分无符号左移和左移运算。

3、关于返回大于或等于 cap 的最小2次幂的算法

我们以传入10为例子

由这张图看起来,算法很容易懂了,其实最主要的是为了去让各个位从高到低 从0变成1或者维持1不变,这样就能找到该数最小的2次幂

另外,需要注意一下的是,第一步 int n = cap - 1; 这个操作,执行这个操作的主要原因是为了防止在cap已经是2的n次幂的情况下,经过运算后得到的结果是cap的二倍的结果,例如如果n为l6,经过一系列运算之后,得到的结果是0001 1111,此时最后一步n+1 执行之后,就会返回32,有兴趣的可以自己进行尝试;

小弟不才,如有问题,欢迎指出。

参考来源:

https://blog.csdn.net/c10WTiybQ1Ye3/article/details/89411471
https://www.jianshu.com/p/927009730809

Java虚拟机类的加载机制

什么是虚拟机类的加载机制?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类从加载到虚拟机的内存中开始,直到卸载出内存为止,整个生命周期为:

  • 加载(loading)

  • 验证(verification)

  • 准备(preparation)

  • 解析(resolution)

  • 初始化(initialization)

  • 使用(using)

  • 卸载(unloading)

其中 验证、准备、解析部分统称为连接

接下来依次讲解,各个步骤所做的事

第一部分 加载


“加载”是“类加载”的一个阶段,注意区分概念。类的加载由类加载器(后面介绍)加载主要完成三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

加载阶段完成后,虚拟机外部的二进制字节流将按照虚拟机所需的格式存储在方法区中,同时在内存中实例化一个java.lang.Class的实例对象。相对于HotSpot,这个实例对象比较特殊,虽然是一个对象,但并没有放置在堆中,而是放置在方法区中。这个对象将作为程序访问方法区中这些类数据的外部接口。

第二部分 验证


这一步主要是确保Class文件的字节流符合虚拟机的规范

主要验证以下几个部分:

1、文件格式验证
验证是否以魔数开头、主次版本号是否在当前虚拟机处理范围内…

这一验证阶段主要是保证输入的字节流能正确地解析并存储与方法区内,格式上符合Java类型信息的要求。只有通过这个阶段,字节流才会进入内存的方法区中存储,后面的三个验证方式也都是给予方法区中的数据验证,不再会操作字节流。

2、元数据验证 验证这个类是否有父类、这个类是否继承了不允许继承的类…

该阶段主要对类的元数据进行语义校验,保证符合java语言规范的元数据信息。

3、字节码验证

最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是否合法、符合逻辑。

4、符号引用验证

这个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作发生在连接的解析阶段。目的是确保解析动作正常执行,如果无法通过验证,将抛出 IllegalAccessError、NoSuchFieldError、NoSuchMethodError等异常。

第三部分 准备


准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所实用的内存将在方法区中进行分配。

这时候的分配仅仅是类变量(staic修饰的变量),而实例变量将会在对象实例化时随对象一起分配在Java对中。

假设一个类变量为:public static int count = 10;这时候会分配0,而不是10,分配10是在程序编译后。

第四部分 解析


解析阶段是虚拟机将常量池的符号引用替换为直接引用的阶段

1、类或者接口的的解析

2、字段解析

3、类方法解析

4、接口方法解析

符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

第五部分 初始化


在准备阶段,变量已经被分配赋值过初始值,在初始化阶段根据代码的逻辑初始化真实的变量和其他资源。

关于类加载器


什么是类加载器?

在“加载”阶段中,通过一个类的全限定名来获取其定义的二进制字节流。这一动作是放到了Java虚拟机外部去实现的,是为了方便让应用自己去决定如何获取所需要的类,实现这个动作的功能是常说的“类加载器(ClassLoader)”

类加载器主要有三种:

1.启动类加载器(Bootstrap ClassLoader)

负责加载<JAVA_HOME>\lib

2.扩展类加载器(Exension ClassLoader)

负责加载<JAVA_HOME>\lib\ext

3.应用程序类加载器(Applicaion ClassLoader)

负责加载ClassPath上指定的类库

类加载器工作原理

介绍类加载器原理之前,必须得了解双亲委派模型(Parents Delegation Model)

双亲委派模式的工作原理的是:如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载。

如图所示,这种层次结构关系被称为双亲委派模型
以下为其实现代码,集中在java.lang.ClassLoader中的loadClass()方法中

    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
    //首先检查类是否被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
        //如果有父加载器,则先委托父加载,否则由启动类加载器加载,如果启动类加载器没有找到,则返回null
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
        //这里的ClassNotFoundException来自父加载器
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
        //在父类Classloader还没办法加载的时候
        //再调用本身的findclass方法来加载类
                long t1 = System.nanoTime();
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

代码的逻辑很清楚:

先加载类是否已经被加载过,若没有则调用父的loadClass()方法,如果父 类加载器为空,则使用启动类加载器作为父加载器,如果父 类加载器加载失败,再调用自己的findClass()方法进行加载

Android中Handler使用导致的内存泄漏

1.什么是内存泄漏

用动态存储分配函数动态开辟的空间,在使用完毕后未被得到释放,结果一直占据该用内存单元,直到程序结束,即所谓的内存泄漏。

2.是内存泄漏与内存溢出的区别

内存溢出 Out of Memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

用一个很形象的例子来说明:一个仓库,被无用的物资所占据,而得不到管理员的清理,这里的无用货物占用仓库空间的行为被叫做”内存泄漏“,而某一天仓库由于所存储的物品太多,而无法继续存放物资,这个时候就被叫做“内存溢出”。

3.内存泄漏导致的问题

相关内存无法被系统给回收,随着程序运行可以用的内存会越来越少,机子越来越卡,直到内存溢出。(这也是为什么手机电脑很卡之后重启一下后会好很多,主要是相关未被系统回收的内存被回收)

4、安卓中的内存泄漏

典型的可能产生内存泄漏的代码:

public class MemoryLeakActivity extends MyActivity {
//可能会导致内存泄漏的代码
private Handler handler = new Handler() {
    [@Override](https://my.oschina.net/u/1162528)
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};}

产生内存泄漏可能的原因:Handler的工作机制中Handler与Looper以及MessageQueue一起工作的,App启动之后,系统会默认创建一个为主线程服务的Looper对象,负责处理主线程中所有的Message对象,它的生命周期则为整个应用的生命周期。在主线程使用Handler都会默认绑定到这个Looper上面,主线程创建Handler对象,会立即关联Looper对象的MessageQueue,这时发送MessageQueue重的Message会持有Handler的引用, 这样在Looper处理Message时候才会回调到Handler的handleMessage方法。因此,如果Message没有被处理完成,那么Handler对象就不会被垃圾回收。

上面的代码,将Handler的实例声明为MemoryLeakActivity类的内部类,在Java中:非静态内部匿名类会持有外部类的一个隐式引用,这样就可能导致外部类无法被垃圾回收。

最终由于MessageQueue中的Message 没有处理完成,就会持有Handler对象的引用,而非静态的Handler对象会持有外部类Activity的引用,这个activity无法被回收,从而导致内存泄漏。

5、解决方案

1、将Handler声明为静态内部类,这样就不会持有对外部类的引用。

2、创建一个Looper与一般Java对象一样的生命周期

private static InnerHandler extends Handler{       
      // 声明一个静态Handler类,并持有外部类引用
    private final WeakReference<MemoryLeakActivity> mActivity;
        public InnerHandler(MemoryLeakActivity activity){
                this.mActivity = new WeakReference<MemoryLeakActivity>(activity);
    }
}

LayoutInflater源码解析

我们经常实用的LayoutInflater这样用:

View view = LayoutInflater.from(context).inflate(R.layout.resource,root,flase);

进入inflate进行源码解析

   public View inflate(@LayoutRes int resource, [@Nullable](https://my.oschina.net/u/2896689) ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

主要做了一件事:
建立XmlResourceParser为后面解析xml文件做准备
XmlResourceParser的解析原理可以去这里看看

继续进入inflate(为方便阅读删除一些调试代码和异常捕获代码,只保留了核心代码)

public View inflate(XmlPullParser parser, [@Nullable](https://my.oschina.net/u/2896689) ViewGroup root, boolean attachToRoot) {
  synchronized (mConstructorArgs) {
      final Context inflaterContext = mContext;
      final AttributeSet attrs = Xml.asAttributeSet(parser);
      Context lastContext = (Context) mConstructorArgs[0];
      mConstructorArgs[0] = inflaterContext;
      View result = root;
      try {
          if (TAG_MERGE.equals(name)) {
              if (root == null || !attachToRoot) {
                  throw new InflateException("<merge /> can be used only with a valid "
                          + "ViewGroup root and attachToRoot=true");
              }
              rInflate(parser, root, inflaterContext, attrs, false);
          } else {
              // Temp is the root view that was found in the xml
              final View temp = createViewFromTag(root, name, inflaterContext, attrs);
              ViewGroup.LayoutParams params = null;
              if (root != null) {
                  // Create layout params that match root, if supplied
                  params = root.generateLayoutParams(attrs);
                  if (!attachToRoot) {
                      // Set the layout params for temp if we are not
                      // attaching. (If we are, we use addView, below)
                      temp.setLayoutParams(params);
                  }
              }
              rInflateChildren(parser, temp, attrs, true);
              // We are supposed to attach all the views we found (int temp)
              // to root. Do that now.
              if (root != null && attachToRoot) {
                  root.addView(temp, params);
              }
              // Decide whether to return the root that was passed in or the
              // top view found in xml.
              if (root == null || !attachToRoot) {
                  result = temp;
              }
          }
      } catch (XmlPullParserException e) {...}

`

这段源码中看出主要逻辑为:

1、判断xml局中标签是否为merge,如果是则走rInflate直接去遍历创建xml所有的View对象

2、进入非merge的逻辑里面,会创建根View,主要的过程在createViewFromTag创建View

3、rInflateChildren创建子View

3、接下来如果传入的root不为null,并且attachToRoot==false,则对创建好的View的ViewGroup.LayoutParams是通过generateLayoutParams生成的。

4、如果root不为null,attachToRoot==true,那么则将整个View作为一个子View加入到父布局中,否则直接返回这个View

继续进入createViewFromTag源码(为方便理解去除异常彩蛋和ignoreThemeAttr属性的代码)
`

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }
    try {
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        ……

这段源码中看出主要逻辑为:

1、标签为view时获取view的class属性作为要创建的View的name(注意View和view的区别)

2、主要通过不同的Factory通过createView() 去创建View

3、其中有个逻辑需要在onCreateView执行之前判断是否存在“.”,存在点则表示不是系统的View,需要单独处理,在后面createView的代码可以看到有这样一段加入了“android.view.”,后面会讲这句的用途。

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
}

继续进入onCreateView源码

public final View createView(String name, String prefix, AttributeSet attrs)
      throws ClassNotFoundException, InflateException {
  Constructor<? extends View> constructor = sConstructorMap.get(name);
  if (constructor != null && !verifyClassLoader(constructor)) {
      constructor = null;
      sConstructorMap.remove(name);
  }
  Class<? extends View> clazz = null;
  try {
      if (constructor == null) {
          // Class not found in the cache, see if it's real, and try to add it
          clazz = mContext.getClassLoader().loadClass(
                  prefix != null ? (prefix + name) : name).asSubclass(View.class);

          if (mFilter != null && clazz != null) {
              boolean allowed = mFilter.onLoadClass(clazz);
              if (!allowed) {
                  failNotAllowed(name, prefix, attrs);
              }
          }
          constructor = clazz.getConstructor(mConstructorSignature);
          constructor.setAccessible(true);
          sConstructorMap.put(name, constructor);
      } else {
          // If we have a filter, apply it to cached constructor
          if (mFilter != null) {
              // Have we seen this name before?
              Boolean allowedState = mFilterMap.get(name);
              if (allowedState == null) {
                  // New class -- remember whether it is allowed
                  clazz = mContext.getClassLoader().loadClass(
                          prefix != null ? (prefix + name) : name).asSubclass(View.class);

                  boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                  mFilterMap.put(name, allowed);
                  if (!allowed) {
                      failNotAllowed(name, prefix, attrs);
                  }
              } else if (allowedState.equals(Boolean.FALSE)) {
                  failNotAllowed(name, prefix, attrs);
              }
          }
      }
      Object lastContext = mConstructorArgs[0];
      if (mConstructorArgs[0] == null) {
          // Fill in the context if not already within inflation.
          mConstructorArgs[0] = mContext;
      }
      Object[] args = mConstructorArgs;
      args[1] = attrs;
      final View view = constructor.newInstance(args);
      if (view instanceof ViewStub) {
          // Use the same context when inflating ViewStub later.
          final ViewStub viewStub = (ViewStub) view;
          viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
      }
      mConstructorArgs[0] = lastContext;
      return view;
  } 

这段源码虽然很复杂,但主要做的事就是 通过反射的方式去加载一个View类

这段代码就能解释上面为什么要加“android.view.”,这段代码会将系统的View的路径拼起来,把类加载进来;

clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class) 

到现在,我们讲完了对于xml根view的创建逻辑,还有个很重要的流程没有讲:

子View创建 的逻辑在inflate中的rInflateChildren

进入rInflateChildren,这里依然会进入到rInflate()

   void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        final String name = parser.getName();
        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }
    if (pendingRequestFocus) {
        parent.restoreDefaultFocus();
    }
    if (finishInflate) {
        parent.onFinishInflate();
    }
}

这段源码的大致可以总结为:

1、总的逻辑为获取xml文档的层级数,解析每一层级的数据

2、解析过程首先进行View的合理性校验,include、merge等标签;

3、最后还是会走到createViewFromTag 创建出 View 对象,如果是 ViewGroup则递归调用rInflateChildren

到这里基本上所有的流程讲完了,这里总结一下加载流程:

1、拿到Xml解析对象,为后续解析做准备

2、对整个Xml中的布局控制处理由 root、attachToRoot这两个参数控制

3、解析子View,通过createViewFromTag创建实例对象**

从一则知乎看到的一段视频说起

今晚躺在床上,刷刷知乎,看到一则回答:https://www.zhihu.com/question/278030511/answer/453342056

让我陷入一些深思
回答中中有这样一段视频,自己反复看了几遍,每一遍好像都有不同的感触。第一遍,感受到大叔打着的节拍是多么的有节奏感,让人听了之后恨不得跟着他一起拍起来。第二遍,我发现大叔后面的节奏相比于紧促了起来,大叔内心有了更多的投入,而且貌似最后还有不好听的字眼,貌似在发泄了。第三遍,大叔一定是一个有故事的人,大叔右手旁边的朋友都已经湿了眼角。。。

《假行僧》一首能把人唱得热泪盈眶的歌曲,我在想大叔是不是想起了他自己的过去,亦或是有太多的不满此刻想要发泄出来。我想前者会更多一点的吧。

是呀,生活在这样一个忙忙碌碌的人心浮躁社会里,人的压力自然而然就增加了,再也没有儿时那样的无牵无挂。今天和好朋友去了一趟西塘古镇,主要还是因为周末了,最近在一些地方也遇到了小挫折,周末了没有任何人来打扰自己,比起待在家里更想的还是出去走走比较好。

今天的西塘下着雨,没有了燥热的天气,人心还是挺平静的,或许是因为雨的缘故,景区并不是人满为患,自然也有了更多的空间去慢慢欣赏“江南水乡”。

因为最近刚看完《边城》,脑子里总是将其场景带入西塘,可是看着被商业气息冲刺着的古镇,自然也带入不了了。

走在河边的烟雨长廊上,朋友说道 十年后再来这里。啊!到现在这个年龄,自己真的对于时间太敏感了,真的有点害怕了!消逝的是那时间,留下的却是那一串串回忆。

那时间 永远不再回来。自己上个月刚过了二十二周岁的生日,还记得以前Taylor有一首歌叫做《22》,有一句:But i am feeling 22,evething will be alright(我感觉我回到了22岁,所有事都如此美好),所以我现在就处在这样一个霉霉很想回到的一个年龄?可是她回不到了,我自然也回不到了我的18了。

那回忆 有美好的不美好的。回忆里面有对酒当歌,有秉烛夜游,也有蒲苇磐石,也有来日方长,当然还有曲终人散。还记得那本《挪威的森林》,或许现在它还在某处静静的躺着吧,因为它回忆里也多了好多的故事。也还记得那一回因为一些压力,在酒吧里和朋友唱了首《挪威的森林》。

一切都已经过去了,也许明天就回来了,也许不会再回来。

就这样吧,晚安。
2018/08/26 01:35

十年祭·512汶川大地震

今晚不知在哪突然看到有关512汶川地震的相关报道,汶川地震已发生了十年了。十年呀!人们总是对于十年、二十之类的整数年比较在意,当然我也不例外。作为一个在外漂泊对人来说,提起故乡往事也总是诸多感慨与回忆。

还记得在十年前自己还是一副稚嫩对模样,一米五不到,留着一个乖乖头的小孩子。记得那天在发生地震之前一切都很普通,普通得我都忘了那之前有发生过什么事情。那时候我们正趴在课桌上睡午觉,突然桌子一阵摇晃,我也不知道发生了什么。我只是抬头一看天花板上面的风扇在摇晃,那时候的天真我以为外面吹大风了,把风扇吹摇晃起来了,把房子吹摇晃起来了。可是十几秒钟之后,整个楼层嘈杂了起来,我也不知道发生了什么,只看到外面一群人都在往外边跑,我也跟着往外边跑,我们的教室在三楼,从三楼走到操场上,我感觉整个地面像是海绵做的一样,踩上去特别特别的软(现在想起来,当时房屋震动起伏的高度应该有个二三十厘米),整个人走在上面都在摇摇晃晃,依稀的听得一些窗户玻璃碎了的声音。不一会全校的人都站在了操场上面,此时地面依然在震动,当时大家都不知道发生了什么,可是脚都在不由自主地颤抖,可能是先天的一种对大自然的畏惧吧,站在操场上地面震动的时间持续了二三十秒钟,到后来渐渐的停止了震动,大家也才反应了过来。

有老师在大吼大喊组织大家在操场上站着,不让大家再回教室,地面停止震动了,其中我印象很深的一个情节:班上一个人由于刚睡了午觉口很渴,想回教室去拿水,校长看到他向教室里面走去了,直接过去就是两耳光,然后一顿骂。同学很是不开心,还暗地里骂校长,现在想想真的能理解当时校长的心情。从那之后“地震”这个词才深深的印在我们的脑海里,原来呀,这就是地震。

全校都站在操场上无目的等(现在想来当时应该在等上级的通知,是否是叫我们放假吧),也不记得等了多久,然后学校突然说放假了!!!当时那叫一个兴奋(现在去查了查日历,当时是星期一)这可了得,刚放完周末又马上要回去了,我记得好多人都高兴得跺脚了。。。可是大家往家走的路上,看到有些东西有点不对劲了,路上有一些比较老一点的房子直接全部垮塌了,然后途中遇到一个学生他妈来接他,我也不知道具体说了些什么了,大致记得“余震”这个事,说的是之后还会有余震,当时那叫一个怕,以为余震和刚才的大地震一样,还会再来一次,真的是很害怕了。

越往家走心里越害怕,万一自己家的房子垮了怎么办呢?带着担心回到了家,发现还好还好,房子还没有垮,悬着的心一下子放松了下来。作为小孩子的我们,又聚集了一堆小伙伴玩呀,好像一切都与我们无关。记得当时已经停电了,电视没法看了,也没有手机电脑,家里的电话也大不出去了,不知道谁弄来了一个小随身听,听广播播报说:2008年5月12日啥啥啥一大堆的。现在想想当时有个很搞笑的场景:我们从新闻里面听说了7.8级,也听到了8.0级之类的,大家以为是 成都还是哪里发生了8.0级地震,某某地方发生了7.8级地震。于是有个人赶紧问在听FM的小伙伴说:“绵阳多少级?”

我们小孩子依然像往常一样,在整个村里面到处蹿,发现整个村都在忙,从家里搬重要的东西、搭帐篷等等,甚是热闹,小孩子好像就喜欢热闹的场景,记得我们那天玩到了比平时晚很久很久才回家。晚上一家人住在了帐篷里面,当时我也不知道为什么不住家里呀,房子应该够结实的呀?一家人都围在一个小小的帐篷里面,我拿着我小小的手电筒躲在被窝里,好像还有自己的一些小玩意儿,然后又把手电筒挂在帐篷最上面,照着我们,那时候真的一点都不害怕,甚至是感觉到自己是有多么的幸福。

晚上,下起了大雨,很大的雨听到了雨水和帐篷上一直发出哒哒哒的声音,外面的大雨并没有破坏帐篷内的宁静。现在想想爷爷真是伟大,帐篷是他搭的,我还记得他在下午的时候,就已经在帐篷周围挖好了排水道,这样使得帐篷周围不会有积水,也使帐篷内地面保持干燥。我就安然地睡着了,睡得很香,啥事没有一样。记得半夜,大概两三点的时候吧,整个村又吵起来了,原来是在刚刚发生了一场余震,而我全然不知。记得爷爷当时给我们讲了下当年唐山大地震的一些事,我也记不清楚太多了,只记得唐山离我们很远,但是好像这边震感还是很强烈(没有汶川这次强烈),但是每家每户也都搭起了帐篷,然后讲了很多,我记不清了。。。

第二天,好像消息很多很多就传来了,我也是听爷爷给我讲的,他在茶馆听谁谁谁说了啥啥啥,大家都在讨论啥啥啥。现在想想当时镇上的茶馆里面大家都讨论这些的时候是有多热闹,等我到初中的时候,我甚至将想象中爷爷当时在茶馆的场景和老舍的三幕剧《茶馆》做对比,《茶馆》里写着“莫谈国事”的标语,一大群人都安安静静地端着茶杯各个喝着自己的茶,而爷爷的“茶馆”这边却是大家对“国事”各抒己见的热闹场景,想想真的是有趣。

记得过了一两天来电了,终于能看到电视了,打开电视发现所有的频道都放的是一个画面!!里面的场景记得大多与担架、救援人员、救灾物质、废墟等等相关。我清晰的记得有个场景是一位父亲举着一叠奖状向镜头展示,然后哭着说道:全是三好学生。。。父亲一边哭一边看着已经永远离开了自己的女儿。还有那位母亲的那条短信:“亲爱的宝贝,如果你能活着,一定要记住我爱你”。看惯了生离死别的医生却在这一刻落泪了,手机传递着,每个看到短信的人都落泪了。。。看到了太多太多的感动,在感动的同时,也感慨生命的脆弱。
活着真好。

太多太多的感动一时间无法全部诉说。。。

这次地震无疑给自己留下了很多阴影,初中的时候一台压路机从教室外边路过,整个房子微微颤抖都特别特别的害怕、有时候谁在桌子边抖抖腿也感觉特别的害怕。自己从来不敢去做 类似蹦极、过山车之类的事,上次去走玻璃栈道也是战战兢兢的。有的同学说我怕死呀?我并不怕死,只是对生命敬畏。

十年过去了,岁月变迁太快,爷爷和他撑起了家庭的帐篷已不再,心里留下的只有诸多的怀念。上天很眷顾我,我也很珍惜,珍惜自己的家人,珍惜每一份情。

最后也感谢各位以前对我们四川人民的帮助,滴水之恩,永远铭记!

愿逝者安息,生者如斯🙏

愿逝者安息,生者如斯

---2018/05/12 00:28
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×