Thinking in Java 第18章 Java I/O 系統(18.9-18.14)
//宣告:部分內容引自《Java程式設計思想(第四版)》機械工業出版社
【程序控制】
– 你經常會需要在 Java 內部執行其他作業系統的程式,並且要控制這些程式的輸入和輸出。Java 類庫提供了執行這些操作的類。
【新 I/O】
– 轉換資料。緩衝器容納的是普通的位元組,為了把它們轉換成字元,我們要麼在輸入它們的時候對其進行編碼,要麼在將其從緩衝器輸出時對它們進行解碼。可以使用 java.nio.charset.Charset 類實現這些功能,該類提供了把資料編碼成多種不同型別的字符集的工具。
例:
package io;//: io/AvailableCharSets.java
// Displays Charsets and aliases
import java.nio.charset.*;
import java.util.*;
import static net.mindview.util.Print.*;
public class AvailableCharSets {
public static void main(String[] args) {
SortedMap<String,Charset> charSets =
Charset.availableCharsets();
Iterator<String> it = charSets.keySet().iterator();
while (it.hasNext()) {
String csName = it.next();
printnb(csName);
Iterator aliases =
charSets.get(csName).aliases().iterator();
if(aliases.hasNext())
printnb(": ");
while(aliases.hasNext()) {
printnb(aliases.next());
if(aliases.hasNext())
printnb(", " );
}
print();
}
}
} /* Output:
Big5: csBig5
Big5-HKSCS: big5-hkscs, big5hk, big5-hkscs:unicode3.0, big5hkscs, Big5_HKSCS
EUC-JP: eucjis, x-eucjp, csEUCPkdFmtjapanese, eucjp, Extended_UNIX_Code_Packed_Format_for_Japanese, x-euc-jp, euc_jp
EUC-KR: ksc5601, 5601, ksc5601_1987, ksc_5601, ksc5601-1987, euc_kr, ks_c_5601-1987, euckr, csEUCKR
GB18030: gb18030-2000
GB2312: gb2312-1980, gb2312, EUC_CN, gb2312-80, euc-cn, euccn, x-EUC-CN
GBK: windows-936, CP936
...
*///:~
– 獲取基本型別。
– 檢視緩衝器。
– 位元組存放次序。ByteBuffer 是以高位優先的形式儲存資料的,並且資料在網上傳送時也常常使用高位優先的形式。
例:(怎樣通過位元組存放模式設定來改變字元中的位元組次序)
package io;//: io/Endians.java
// Endian differences and data storage.
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import static net.mindview.util.Print.print;
/**
* Created by JT on 2016/7/27.
*/
public class Endians {
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put("abcdef");
print(Arrays.toString(bb.array()));
}
}
ByteBuffer 有足夠的空間,以儲存作為外部緩衝器的 charArray 中的所有位元組,因此可以呼叫 array() 方法顯示檢視底層的位元組。array() 方法是“可選的”並且我們只能對由陣列支援的緩衝器呼叫此方法,否則,將會丟擲 UnsupportedOperationException。
通過呼叫 CharBuffer 檢視可以將 charArray 插入到 ByteBuffer 中。在底層的位元組被顯示時,我們會發現預設次序和隨後的高位優先次序相同,然而低位優先次序則與之相反,後者交換了這些位元組次序。
– 用緩衝期操縱資料。
例如:如果想把一個位元組陣列寫到檔案中去,那麼就應該使用 ByteBuffe.wrap() 方法把位元組陣列包裝起來,然後用 getChannel() 方法在 FileOutputStream 上開啟一個通道,接著將來自於 ByteBuffer 的資料寫到 FileChannel 中。
注意,ByteBuffer 是將資料移進移出通道的唯一方式,並且我們只能建立一個獨立的基本型別緩衝器,或者使用“as”方法從 ByteBuffer 中獲得。也就是說,我們不能把基本型別的緩衝器轉換成 ByteBuffer。然而,由於我們可以經由檢視緩衝器將基本型別資料移進移出 ByteBuffer,所以這也就不是什麼真正的限制了。
– 緩衝器的細節。
Buffer 由資料和可以高效地訪問以及操縱這些資料的四個索引組成,這四個索引是:mark(標記),position(位置),limit(界限),capacity(容量)。在緩衝器中插入和提取資料的方法會更新這些索引,用於反應所發生的變化。
例(用到交換相鄰字元的演算法,以對 CharBuffer 中的字元進行編碼和譯碼):
package io;//: io/UsingBuffers.java
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import static net.mindview.util.Print.print;
/**
* Created by JT on 2016/7/27.
*/
public class UsingBuffers {
private static void symmetricScramble(CharBuffer buffer) {
while (buffer.hasRemaining()) {
buffer.mark();
char c1 = buffer.get();
char c2 = buffer.get();
buffer.reset();
buffer.put(c2).put(c1);
}
}
public static void main(String[] args) {
char[] data = "UsingBuffers".toCharArray();
ByteBuffer bb = ByteBuffer.allocate(data.length * 2);
CharBuffer cb = bb.asCharBuffer();
cb.put(data);
print(cb.rewind());
symmetricScramble(cb);
print(cb.rewind());
symmetricScramble(cb);
print(cb.rewind());
}
}
– 記憶體對映檔案。
記憶體對映檔案允許我們建立和修改那些因為太大而不能放入記憶體的檔案。有了記憶體對映檔案,我們就可以假定整個檔案都放在記憶體中,而且可以完全把它當作非常大的陣列來訪問。這種方法極大地簡化了用於修改檔案的程式碼。
package io;//: LargeMapFiles.java
// Creating a very large file using mapping.
// {RunByHand}
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import static net.mindview.util.Print.print;
import static net.mindview.util.Print.printnb;
/**
* Created by JT on 2016/7/27.
*/
public class LargeMappedFiles {
static int length = 0x8FFFFF; // 128MB
public static void main(String[] args) throws Exception {
MappedByteBuffer out = new RandomAccessFile("test.dat", "rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++)
out.put((byte)'x');
print("Finished writing");
for (int i = length/2; i < length/2 + 6; i++)
printnb((char)out.get(i));
}
}
為了既能寫又能讀,我們先由 RandomAccessFile 開始,獲得該檔案上的通道,然後呼叫 map() 產生 MappedByteBuffer,這是一種特殊型別的直接緩衝器。注意我們必須指定對映檔案的初始位置和對映區域的長度,這意味著我們可以對映某個大檔案的較小的部分。
“對映檔案訪問”可以更加顯著地加快速度。
– 檔案加鎖。允許我們同步訪問某個作為共享資源的檔案。檔案鎖對其他的作業系統程序是可見的,因為 Java 的檔案加鎖直接對映到了本地作業系統的加鎖工具。
例:
package io;//: io/FileLocking.java
import java.nio.channels.*;
import java.util.concurrent.*;
import java.io.*;
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl != null) {
System.out.println("Locked File");
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
} /* Output:
Locked File
Released Lock
*///:~
tryLock() 是非阻塞式的,它設法獲取鎖,但是如果不能獲得,它將直接從方法呼叫返回。lock() 則是阻塞式的,它要阻塞程序直至鎖可以獲得,或呼叫 lock() 的執行緒中斷,或呼叫 lock() 的通道關閉。使用 FileLock.release() 可以釋放鎖。
– 對對映檔案的部分加鎖。
【壓縮】
– 用 GZIP 進行簡單壓縮。適合對單個數據流,而不是一系列互異資料進行壓縮。
– 用 Zip 進行多檔案儲存。這個類庫使用的是標準 Zip 格式,所以能與當前那些可通過因特網下載的壓縮工具很好地協作。
– Java 檔案檔案。Zip 格式也被應用於 JAR(Java ARchive,Java 檔案檔案)檔案格式中。這種檔案格式就像 Zip 一樣,可以將一組檔案壓縮到單個壓縮檔案中。
一個 JAR 檔案由一組壓縮檔案構成,同時還有一張描述了所有這些檔案的“檔案清單”(可自行建立檔案清單,也可以由 jar 程式自動生成)。
【物件序列化】
– Java 的物件序列化將那些實現了 Serializable 介面的物件轉換成一個位元組序列,並能夠在以後將這個位元組序列完全恢復為原來的物件。這一過程甚至可以通過網路進行,這意味著序列化機制能自動彌補不同作業系統之間的差異。
【XML】
– 物件序列化的限制:只有 Java 程式才可使用。解決方案:將資料轉換成 XML 格式,可被各種平臺和語言使用。
【Preferences】
– Preferences API 用於儲存和讀取使用者的偏好以及程式配置項的設定。
– Preferences 是一個鍵-值集合(類似對映),儲存在一個節點層次結構中。通常建立以你的類名命名的單一節點,然後將資訊儲存於其中。
【注意】
– ‘0’和‘O(大寫o)’;
– ‘1’和‘l(小寫 L)’;