1 处理io文本最好直接用reader和writer,否则不同的字符集编码,byte转成char会转出翔,比如unicode,两个字节表示一个字符,而每次输入流只读一个字节,就要自己做特殊转换。
int b1 = in.read();
int b2 = in.read();
char c = (char) (b1*256 + b2);
2 java io的关键抽象是流,流就如同流水一般,是流动的是byte,未确定的byte序列。一个个读入,一个个写出。输入流就好比是一杯水,用吸管逐渐吸干;输出流就好比是一杯水,杯子下面开了个洞,接了根导管,逐渐的流出。
3 最常用简单的字符集是ASCII, the American Standard Code,几乎是各种计算机的标准字符集,他能表示英文世界的几乎所有的字符,一个byte,7个位表示字符集,可以表示127个;ISO Latin-1是ASCII的超集,他用一个byte,8个位表示字符集,可以表示256个,前面127个字符和ASCII一一对应,后面增加了一些拉丁文、罗马文之类的东东,用于满足欧洲大陆的字符表示要求;但是远不能满足中文和日文,中文有80000个字,于是就有了unicode,用两个byte来表示一个字符,支持表示65,536个字符,前面256个字符和ISO La tin-1一样,只不过都占用了两个字节;当使用的大部分字符都是在前256个的时候,unicode的效率比较低,因为他要读取两个byte才能解码成一个字符,
int b1 = in.read();
int b2 = in.read();
char c = (char) (b1*256 + b2);
于是就出现了UTF-8 ,当大部分的字符都是前256个的时候,使用UTF-8效率比较高,因为在ASCII这个范围内的字符,只用一个字节来表示。后面的1,919 个字符编码成两个字节,剩下的uicode字符编码成3个字节。尽管编码成3个字节,但由于后面的字符使用得较少,所以根据2,8原则,大部分的编码解码只需要读取一个字节就能完成,性能更佳。
除了这些,还有EBCDIC. EBCDIC等等,java的 Reader, Writer, String能够理解如何把这些字符集和unicode相互转换。
4 在java里,面向字符的数据由char所组成,每个char用两个byte表示,属于unsigned的整数【0,65,535】。每个char表示unicode里面的一个字符。
java能理解的字符集并不能完全等价于所有的unicode字符。如果要编码的字符不存在于我所用的字符集里,那么可以使用unicode转义,例如
char copyright = '\u00A9';
unicode转义在线转换链接http://javawind.net/tools/native2ascii.jsp?action=transform
编译器在对源码进行编译时会先把unicode转义字符翻译成他所表达的字符,然后再进行编译,如http://blog.csdn.net/jiqikewang/article/details/7052688
unicode转义的意义??
5 outputStream和writer的区别在于操作的对象分别为byte和char,当write byte参数为int的时候,实际上会write 参数mod256,因为byte的取值范围为【0,256】,当write char参数为int的时候,实际会write参数mod 65,536,因为char的取值范围为【0,65,536】。
6 使用public void write(byte[] data) throws IOException 比write一个byte的效率高;但是byte array的大小需要控制,否则也会影响性能,一般而言写file的byte array一般控制在512, 1024, or 2048 bytes,写网络一般控制在128 or 256 bytes,同时也要结合系统的实际情况。
关于输入流需要注意的点是,读入的byte是unsigned,范围为【0,256】,返回的类型是int类型。当读不到数据的时候返回的是-1。
int[] data = new int[10];
for (int i = 0; i < data.length; i++) {
data[i] = System.in.read();
}
但是如果要保存到byte[],会转换成signed byte,范围【-128 ,127】。
byte[] b = new byte[10];
for (int i = 0; i < b.length; i++) {
b[i] = (byte) System.in.read();
}
当然byte还是可以转换成无符号int的,按照这个公式来转换
int i = (b >= 0) ? b : 256 + b;
读取输入流byte的时候,需要对这些心里有数。
7 java io的一个设计精髓是采用了装饰者模式,通过FilterInputStream和FilterOutputStream来装饰当下的流,添加新的行为。并且可以多层包装,形成一个调用链,外层的FilterInputStream引用内层的InputStream,在调用链的节点添加新行为,节点就好比是过滤器,因此得名为filter,一个典型的例子是BufferedInputStream,提供缓冲特性。
DataInputStream也是一种filter,用于提供基础数据类型和String的编码解码的封装。
使用DataInputStream可以给我们屏蔽数据格式解释的细节,比如读一个Int,在java里面int固定为4个字节、并且使用DataStream写入或者读出时都是采用大端编码。
关于unicode的读写也是类似,unicode占用两个字节,本身也有大小端的问题,java也是默认采用大端编码。
有些时候你又不得不读由其他语言或者在intel硬件上保存的小端编码的文件,但是datastream只支持大端编码,这个时候就只能自己去写一个输入流来解析小端编码的数据了。
8 对于对象的持久化或者传输,由于其可能存在复杂的引用关系,自己实现序列化可能会相当复杂,于是java提供了默认的序列化机制,Object Stream,简化对象的序列化。但是对象的序列化性能是比较一般的,所以如果只是想持久化一些固定格式的数据对象,最好还是自己按照某个协议去进行持久化,性能一般会比默认序列化强。
另外当一个对象写入一个对象流之后,对象流会持有这个对象的引用,因此不会被GC。所以一定记得,写入之后及时关闭、或者重置对象流,以免OOM。
使用序列化还具有一定的安全风险,hacker可以从字节流里面构建出对象本来私有的信息,private字段会被访问到。
序列化还需要注意版本问题,有可能反序列化的时候,对象已经被改变。关于static field和method随便改变无所谓,因为序列化的时候不会保存这些状态。但是普通的field就需要注意,不能随便删除一个普通field,或者改变field type,否则会导致反序列化失败。
可以兼容的改变包括以下几点:
• Most changes to constructors and methods, whether instance or static.
• All changes to static fields
• All changes to transient fields
• Adding an instance field. When an instance of the older version of the class is
deserialized, the new field will merely be set to its default value
• Adding or removing an interface (except the Serializable interface) from a class.
Interfaces say nothing about the instance fields of a class.
• Adding or removing inner classes, provided no nontransient instance field has the type
of the inner class.
• Changing the access specifiers of a field.
• Changing a field from static to nonstatic or transient to nontransient. This is the same
as adding a field.
不兼容的改变包括以下:
• Changing the name of a class.
• Changing the type of an instance field.
• Changing the name of an instance field. This is the same as removing the field with
the old name.
• Changing a field from nonstatic to static or nontransient to transient. This is the same
as removing the field.
• Changing the superclass of a class. This may affect the inherited state of an object.
• Changing the writeObject() or readObject() method (discussed later) in an
incompatible fashion.
• Changing a class from Serializable to Externalizable (discussed later) or
Externalizable to Serializable.
9 jvm里的一个File对象并不表示操作系统存在这个file,只有FileDescriptor实例才能表示操作系统中对应存在的一个文件。每个FileDescriptor实例都会关联一个打开的文件或者socket连接,当文件关闭或者socket关闭,那么这个FileDescriptor就是无效的。
我们能够用FileDescriptor作的事情就是调用sync方法。flush方法只会刷出vm内部的buffer,而syn方法会刷出操作系统buffer、设备驱动buffer、硬件buffer,把数据写入到硬件。
10 java.io.RandomAccessFile用于随机访问一个文件,提供seek方法和指针,可以通过seek移动指针,在文件的某个位置进行操作。他和普通的输入流不一样,普通的输入流同时针对文件io和网络io,只能顺序读写byte,提供的是io的共同抽象。seek方法是针对磁盘操作的,网络io不存在这个操作,所以普通的输入流InputStream不提供随机访问的接口。
11 计算机和计算机语言都是在英文世界里产生的,所以最早的字符集是ASCII,能够完全满足英文世界的要求。但是完全不能满足全球的字符要求,特别是中文、日文等,字数太多。虽然每个国家可以用他们自己的字符集,但是也有问题,比如印度发的邮件到日本,字符可能就乱码了,因为日本用他们自己的字符集。于是就有了unicode的,可以表示6万多个字符。虽然不能覆盖所有的语言,但是可以满足大部分国家字符的需求,都用unicode可以最大程度的统一全球的字符集。
于是jvm采用的默认字符集就是unicode。即便有些字符unicode支持不了,但是java也提供了相关的类可以用指定的字符集编码字符。
尽管如此,并不是所有的机器都会安装unicode,就算安装unicode的也不一定包括所有的字符。为了兼容所有的文本编辑器,java代码必须只能用ASCII字符,比如关键字、类名、字段名、方法名核心api都用纯ASCII字符写的。但是呢,注释比较特别,不得不用unicode里面的其他字符来写,还有字符串、字符的文本也有这个需求。为了使这些java源文件里的非ASCII字符能够使用,这些字符通过unicode转义的方式嵌入到源文件。比如
char tab = '\u0009';
char softHyphen = '\u00AD';
char sigma = '\u03C3';
char squareKeesu = '\u30B9';
在编译的时候,预处理阶段,java编译器会把源文件里面的ASCII和unicode转义字符全部转化为2byte的unicode。只有当编译完成之后,ASCII源文件才被真正的以unicode的形式载入到内存中。