java.io 相关tips


tip 0, 读到文件(或流)结尾的标志

java.io.InputStream 中有个关键方法:java

     int read(),  数组

    它从流中读取一个字节,并将该字节返回; 若是到达流末尾,则返回 -1 .ide

那么问题是, 若是流中正好有一个字节的值是 -1 (即该字节的二进制是 11111111, 十六进制 FF),那么怎样和碰到流的结尾时的返回值区分呢?工具

答案在这个方法的返回值类型int上。 虽然每次读取并返回一个字节,但返回值类型并非byte , 而是int。 byte长度一个字节,而int 是 4个字节, 当读取一个字节 XY 时, 返回的值是 00 00 00 XY ,这个值永远不会为负数 ;当遇到流的结尾时, 返回的值是 FF FF FF FF, 即 -1this


tip 1,关于路径

    All the classes in java.io interpret relative path names as starting from the user’s working directory. You can get编码

this directory by a call to System.getProperty("user.dir"). spa


tip 2,关于InputStreamReader  和 InputStreamWriter 的编码问题:

When saving text strings, you need to consider the character encoding. In the UTF-16 encoding, the string "1234" is encoded as 00 31 00 32 00 33 00 34 (in hex). However, many programs expect that text files are encoded in a different encoding.设计

In ISO 8859-1, the encoding most commonly used in the United States and Western Europe, the string would be written as 31 32 33 34, without the zero bytes.code


OutputStreamWriter 把 Unicode 字符转换为字节流(按系统默认编码或指定的编码)写入,InputStreamReader 将字节流(按系统默认编码或指定的编码)转化为Unicode字符:对象

The OutputStreamWriter class turns a stream of Unicode code units into a stream of bytes, using a chosen character encoding. Conversely, the InputStreamReader class turns an input stream that contains bytes (specifying characters in some character encoding) into a reader that emits Unicode code units. 

For example, here is how you make an input reader that reads keystrokes from the console and converts them to Unicode:

InputStreamReader in = new InputStreamReader(System.in);

This input stream reader assumes the default character encoding used by the host system, such as the ISO 8859-1 encoding in Western Europe. You can choose a different encoding by specifying it in the constructor for the InputStreamReader, for example:

InputStreamReader in = new InputStreamReader(new FileInputStream("kremlin.dat"), "ISO8859_5");

 

tip 3, 关于对象序列化

序列化对象时,序列号的做用不光是校验,还能够处理对象之间的引用关系。若是序列化时没提供序列号,ObjetOutputStream会默认生成一个

对象序列化有严谨的顺序与格式。序列化时,ObjectOutputStream内部按序列化顺序为每一个对象赋予一个隐式的序列号,通常来说,若是类型为A的对象a内部有一个类型为 B 的成员变量b, 那么对象b的序号要比a的序列号要小,也就是说b序列化更早(一样的,反序列化也更早);假设b是被0个序列化,其隐式的序列号为0, 那么在序列化a时,a的对象数据内部存储b对象时会使用一个序列号为0的引用。

ObjectInputStream在反序列化时一样的顺序,也会按反序列化顺序为每一个对象维护一个序列号,这样就能够解决a引用b的问题。


序列化的数据文件除了头数据(魔数,版本等)外,核心数据有两种:类描述符 和 对象数据。

一个序列化对象的格式是 :首先是类描述符,后面紧跟对象数据;

类描述符存储了类的结构,包括类型、类结构的指纹(类名、成员变量、方法签名等数据的一个哈希值),成员个数以及每一个成员的描述;

对象数据按类描述中成员变量的顺序存储了各个成员变量的值;若是成员变量是基本类型或String类型,对象数据会直接存储它的值;若是是其余的对象类型,若是这个对象以前没序列化过,则按“类描述符 数据对象”的格式存储,若是已经序列化过,则存储一个指向该对象的序列号(判断一个对象是否序列化过,是由ObjectOutputStream经过维护对象的序列号来实现的);

对象存储格式73  类描述符  对象数据

数组对象存储格式:75  类描述符  4字节长的数组项的数量  数组项(对象或基本类型的数据)

类描述符:72 两字节长的类名长度 类名  8字节长的指纹  1字节长的序列化方式标志  两字节长的成员变量个数  成员变量描述符(数量与成员变量个数对应)  78(结束标记)   超类类型(没有的话,写70)

引用:71  4字节长的序列号

字符串对象存储格式:74  两字节字符串长度  全部字符

采用外部存储方式的对象:  77  对象数据

注:对于实现了Externalizable接口的类,其对象序列化时会采用“外部存储方式”,序列化的数据以及反序列时数据的处理都是类实现的接口方法控制的。


版本管理:类描述符中的指纹是反序列化时用来校验最新的类定义与当前序列化数据是否一致,好比要从一个序列化文件中反序列化出一个类型为C的对象, 那么会对C的类结构(类名、成员、方法签名)进行哈希,而后与文件中类描述符中的指纹对比,若是不一致会反序列化失败。为了兼容能够在类定义中显示的声明这个指纹:public static final long serialVersionUID = ..., 这个值能够jdk的工具 serialver生成。这样在反序列化时,若是新的类定义中少了某些成员,那序列化的对象数据中相应的域会被掉弃;若是新的类定义中多了某些成员,这些成员会被置为默认值(null, 0, false等)。


序列化的定制:默认的序列化机制忽略transient 和  static域, 咱们能够经过实现 private void readObject(ObjectInputStream in) 和 private void writeObject(ObjectOutputStream out) 来定制序列化、反序列化过程,写入本身要存储的数据,甚至没必要和类定义的成员变量对应;另一种方法就是实现外部存储方式:实现 Externalizable 接口,折中方式彻底有类对象本身控制,接口方法也须要先有对象才能调用(这种反序列方式相似于给对象初始化成员值),而前一种方式的readObject 和 writeObject 则是由序列化机制调用的,咱们不能显示调用。



read () 方法读一个字节,可是返回int:


write(int)写一个字节,该字节是int 参数的低8位, 高24位被忽略:


Reader Writer被设计来读写文本文件,设计到字符集与编码问题:


DataInput 和 DataOutput 用来规范读写二进制文件,文件内容能够方便的转化为基本类型或对象数据:

相关文章
相关标签/搜索