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 用來規範讀寫二進制文件,文件內容能夠方便的轉化爲基本類型或對象數據:

相關文章
相關標籤/搜索