Ericson's blog Time is limited, To be a better better man

Java序列化

对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。

序列化机制使对象可以脱离程序的运行而独立存在。对象的序列化(Serialize)是指将一个java对象写入IO流中,对象的反序列化(Deserialize)则是从IO流中恢复java对象。

java对象实现序列化必须让该类实现如下两个接口之一:

  • Serializable
  • Externalizable

当一个对象实现了Serializable接口时,通过一个写输出流就可以把对象存在磁盘上。示例如下:

ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("object.txt"));
Person per=new Person("ericson",25);
oos.writeObject(per);

而反序列化则是相反的过程,通过一个输入流来恢复对象。如下所示:

ObjectInputStream ois=new ObjectInputStream(new FileInputStream("object.txt"));
Person per=(Person)ois.readObject();
反序列化只序列化java对象,而不是java类,因此采用反序列化java对象时,必须提供该java对象所属的类的class文件,否则会抛出ClassNotFoundException异常;还有一点,Person类只有一个有参数构造器,反序列化Person对象时,并没有调用构造器,表明反序列化无须通过构造器来初始化java对象。

当一个可序列化类有多个父类(直接父类或间接父类)时,这些父类要么有无参构造器,要么也是可序列化的,否则抛出InvalidClassException异常;如果父类不可序列化,只有带有无参构造器,则父类中定义的Field值不会被序列化到二进制流中。

序列化引用对象,先来看下面代码:

Person per=new Person("ericson",25);
Teacher t1=new Teacher("xiong",per);
Teacher t2=new Teacher("yue",per);

从上面代码可以看出:如果序列化上面三个对象,会对per对象序列化三次,当再反序列化时,就会生成三个Person对象,不符合引用关系。因此java采用了特殊的序列化算法:

  • 所有保存到磁盘的序列化对象都有一个序列化编号
  • 当程序试图序列化一个对象时,会先检查该对象有没有被序列化过,只有该对象没有被序列化过,才会将该对象转换字节序列输出
  • 如果某个对象序列化过了,程序将只是直接输出一个序列化编号,而不是重新序列化该对象

在序列化对象代码后改变对象的值不会改变序列化的内容,反序列化还是那个对象。

当不需要序列化某个Field时,可以在该Field前面使用transient关键字,指定java序列化无需理会该Field。transient只能用于修饰Field。

自定义序列化,需要自己重写writeObject方法和readObject方法。还可以重写writeReplace方法把对象转成其他对象,如下所示:

private Object writeReplace() throws ObjectStreamException{
	ArrayList<Object> list=new ArrayList<Object>();
	list.add(name);
	list.add(age);
	return list;
}

上面的代码就把Person对象转变为ArrayList对象,java在序列化时总会先调用writeReplace方法,所以就变成了对ArrayList的序列化;反序列化的结果也是ArrayList的对象。与writeReplace对应的方法是readResolve方法,该方法在readObject方法之后执行,该方法返回值会替换掉原来的反序列化对象。readResolve在序列化单例类、枚举类时尤其有用。因为反序列化不需要通过构造函数来创建对象,所以对于单例类,需要重写readResolve来屏蔽错误,该方法要为private,不然在继承关系上容易出错。

Java Annotation(注释)详解

Annotation其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。

Annotation必须使用工具APT(Annotation Processing Tool)来处理,工具负责提前Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。

Java提供了4个基本的Annotation:

  • @Override:可以强制一个子类必须覆盖父类的方法,该标记只能用作方法,不能用作其他内容
  • @Deprecated:表示已过时,程序调用已过时的东西会导致编译警告
  • @SuppressWarnings:抑制编译器警告
  • @SafeVarargs:是Java7专门为抑制“堆污染”编译警告,

除了java.lang下的4个基本Annotation外,在java.lang.annotation包下还提供了4个Meta Annotation,这4个Meta Annotation都用于修饰其他Annotation定义。

@Retention只能用于修饰一个Annotation定义,用于指定被修饰的Annotation可以保留多长时间。@Retention包含一个RetentionPolicy类型的成员变量,所以使用时需赋值。有三个值:

  • RetentionPolicy.CLASS:编译器将Annotation保持在class文件中,当运行时,JVM不再保留Annotation
  • RetentionPolicy.RUNTIME:编译器将Annotation保存在class文件中,当运行java程序时,JVM会保留Annotation
  • RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃Annotation

示例:

@Retention(RetentionPolicy.CLASS) or
@Retention(RetentionPolicy.RUNTIME)

@Target也只能修饰一个Annotation定义,它用于指定被修饰的Annotation能用于哪些程序单元。@Target也有一个成员变量,变量如下所示:

  • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation
  • ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器
  • ElementType.FIELD:指定该策略Annotation只能修饰成员变量
  • ElementType.LOCAL_VARIABLE:指定该策略Annotation只能修饰局部变量
  • 略(网上查)

@Documented用于指定被该Meta Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。

@Inherited指定被它修饰的Annotation将具有继承性——如果某个类使用了@Annotation修饰,则其子类将自动被@Annotation修饰。

自定义Annotation,使用@interface来自定义新的Annotation,代码如下:

public @interface Test{
	
}

自定义Annotation中还可以声明成员变量,Annotation中的成员变量是用无参数的方法来声明的,示例如下:

public @interface Test{
	String name() default "ericson";
	int age() default 25;
}

Ping命令详解

ping命令是查网络故障的常用命令,一下做一个总结。使用ping命令可以测试计算机名和计算机的ip地址,验证与远程计算机网络连接是否通畅。该命令只有在安装了tcp/ip协议后才可以使用。注:防火墙等网络数据包过滤工具可能会导致ping命令测试失败,导致屏结果出现Request timed out异常提示。

ping命令使用

在windows系统下,使用ping /?命令可以查看ping命令详解,如下图所示:

在Linux系统下,可以使用man ping命令来查看ping命令的使用。

ping命令原理

源主机(输入ping命令的主机)向目标主机发送一个ICMP协议中的echo包;如目标主机存活,就向源主机返回一个ICMP协议的echo-reply包。

ping命令幕后过程,举例说明

首先,我们以下面一个网络为例:有A、B、C、D四台主机,一台路由器RA连接以下两个子网。

All ip mask
A 192.168.0.4 255.255.255.0
B 192.168.0.5 255.255.255.0
C 192.168.1.3 255.255.255.0
D 192.168.1.4 255.255.255.0

(1)在同一网段中:
在主机A上运行“ping 192.168.0.5”后,首先ping命令会构建一个固定格式的ICMP请求数据包,然后由ICMP协议将这个数据包连同地址“192.168.0.5”一起交给ip层协议(和ICMP一样,其实是一组后台运行的进程),ip层协议将以地址“192.168.0.5”作为目的地址,本地ip地址作为源地址,加上一些其他的控制信息,构建一个ip数据包,并想办法得到192.168.0.5的mac地址(物理地址,这是数据链路层协议构建数据链路层的传输单元——帧所必需的),以便交给数据链路层构建一个数据帧。关键就在这里,ip层协议通过对比机器B的ip地址、自己的ip地址和自己的子网掩码,发现它跟自己属于同一网络,就直接在本网络内查找这台机器的mac。如果以前两台主机有过通信,在A机的ARP缓存表中应该有B机ip与其mac的映射关系;如果没有,就发一个ARP请求广播看,得到B机的mac,一并交给数据链路层。后者构建一个数据帧,目的地址是ip层传过来的物理地址,源地址则是本机的物理地址,还要附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。

主机B收到这个数据帧后,先检查它的目的地址,并和本机的物理地址对比,如符合,则接收;否则丢弃。接收后检查该数据帧,将ip数据包从帧中提取出来,交给本机的ip层协议。同样,ip层检查后,将有用的信息提取后交给ICMP协议,后者处理后,马上构建一个ICMP应答包,发送给主机A,器过程和主机A发送的ICMP请求包到主机B一模一样。

(2)不在同一网段
在主机A上运行“ping 192.168.1.4”后,开始和上面一样,到获取mac地址时,ip协议通过计算发现D机与自己不在同一网段内,就直接将数据包交由路由器处理,也就是将路由器的mac取过来(获得路由器的mac过程和上面一样),路由得到这个数据帧后,再跟主机D进行联系,如果找不到,就像主机A返回一个超时的信息。

ping失败结果分析

(1)Request timed out

  • 对方已关机,或者网络上根本没有这个地址
  • 对方与自己不在同一个网段内,通过路由也无法找到对方,但对方确实是存在的
  • 对方确实存在,但设置了ICMP数据包过滤(比如防火墙)
  • 错误设置ip地址

(2)Destination host Unreachable

  • 对方与自己不在同一网段内,而自己又未设置默认的路由
  • 网线出了故障

(3)Bad IP address

  • 表示可能没有连接到DNS服务器,所以无法解析这个IP地址,也可能是IP地址不存在

(4)Source quench received

  • 表示对方或中途的服务器繁忙无法回应

(5)Unknown host

  • 该远程主机的名字不能被DNS解析

(6)No answer

  • 本地系统有一条通向中心主机的路由,但却接收不到它发给该中心主机的任何信息

(7)no route to host

  • 网卡工作不正常

Git简易使用指南

Git是一个版本管理工具,可以帮助你妥善管理你的代码。本篇文章为Git的简易使用指南:

1.首先是安装Git,请参考Git安装教程。
2.创建新仓库,创建新文件夹,打开,然后执行git init,以创建新的git仓库。
3.检出仓库,执行如下命令以创建一个本地仓库的克隆版本:git clone /path/to/repository;如果是远端服务器上的仓库,你的命令会是这个样子:git clone username@host:/path/to/repository(其实可以直接复制github网页上的网址,如下图所示)

4.工作流,你的本地仓库由git维护的三颗“树”组成。第一个是你的工作目录,它持有实际文件;第二个是缓存区(Index),它像个缓存区域,临时保存你的改动;最后是HEAD,指向你最近一次提交后的结果。示意图如下所示:stage 5.添加与提交,你可以计划改动(把它们添加到缓存区),使用如下命令:

git add <filename>, git add *,

这是git基本工作流程的第一步;使用如下命令以实际提交改动:

git commit -m “代码提交信息”,

现在,你的改动已经提交到了HEAD,但是还没到你的远端仓库。
6.推送改动,你的改动现在已经在本地仓库的HEAD中了。执行如下命令以将这些改动提交到远端仓库:

git push origin master,

可以把master换成你想要推送的任何分支。如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:

git remote add origin <server>,

如此你就能够将你的改动推送到所添加的服务器上去了。
7.分支,分支是用来将特性开发绝缘开来的。在你创建仓库的时候,master是“默认的”。在其他分支上进行开发,完成后再将它们合并到主分支上。示意图如下所示:
创建一个叫做“feature_x”的分支,并切换过去:

git checkout -b feature_x,

切换回主分支:

git checkout master,

再把新建的分支删掉:

git branch -d feature_x,

除非你将分支推送到远端仓库,不然该分支就是不为他人所见的:

git push origin <branch>
8.更新与合并,要更新你的本地仓库至最新改动,执行:

git pull,

以在你的工作目录中获取(fetch)并合并(merge)远端的改动。要合并其他分支到你的当前分支(例如master),执行:

git merge <branch>, 两种情况下,git都会尝试去自动合并改动。不幸的是,自动合并并非次次都能成功,并可能导致冲突(conflicts)。这时候就需要你修改这些文件来人肉合并这些冲突了。改完之后,你需要执行如下命令以将它们标记为合并成功:

git add <filename>

在合并改动之前,也可以使用如下命令查看:

git diff <source_branch> <target_branch>
9.标签,在软件发布时创建标签,是被推荐的。这是个旧有的概念,在SVN中野有,可以执行如下命令以创建一个叫做1.0.0的标签:

git tag 1.0.0 1b2e1d63ff

1b2e1d63ff是你想要标记的提交ID的前10位字符。使用如下命令获取提交ID:

git log

你也可以用该提交ID的少一些的前几位,只要它是唯一的。
10.替换本地改动,加入你做错事,你可以使用如下命令替换掉本地改动:

git checkout –<filename>

此命令会使用HEAD中的最新内容替换掉你的工作目录中的文件。已添加到缓存区的改动,以及新文件,都不受影响。假如你想要丢弃你所有的本地改动与提交,可以到服务器上获取最新的版本并将你本地主分支指向到它:

git fetch origin, git reset –hard origin/master

要详细了解git,请参考Git资料。

Linux修改终端显示

在Linux终端显示符中,如果文件夹层次过多,会出现很长的前缀,这时修改终端显示符会是一个需求,或者需要显示其他信息,也需要修改终端显示符。

PS1是Linux终端用户的一个环境变量,用来说明命令提示符的设置。可以使用man bash命令查看bash手册,找到该变量支持的特殊字符,以及这些特殊字符的意义。如下图所示: PS1

修改终端提示符

查看默认提示符设置:echo $PS1
修改默认设置,将如下命令添加到当前登录用户的~/.bashrc文件中,然后使用source使其生效,操作如下:

vim ~/.bashrc
export PS1="[\u@\h \W]\$"
source ~/.bashrc
颜色设置

在PS1中设置字符序列颜色的格式为:[\e[F;Bm], 其中“F”为字体颜色,编号30~37;“B”为背景色,编号40~47。

下面是颜色表:

效果控制代码: