1、File類
File類的名字有一定的誤導性;我們可能認為它指代的是文件,實際上卻并非如此。它既能代表一個
特定文件的名稱,又能代表一個目錄下的
一組文件的名稱。
1.1、目錄列表器
如果需要查看目錄列表,可以通過file.list(FilenameFilter)方法來獲取過濾后的文件目錄列表(返回類型是String[])。
例子:https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/DirList2.java
1.2 目錄使用工具
產生文件集的工具類 :
https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/Directory.java
處理文件類:
https://github.com/xu509/Java-practise/blob/master/src/test/file/directory/ProcessFiles.java
1.3 目錄的檢查以及創建 常用File類方法:f.isFile(), f.isDirectory(),f.mkdirs();
2、輸入和輸出
2.1、InputStream類型
InputStream的作用是用來表示那些從不同數據源產生輸入的類。
數據源包括: 1、字節數組。2、String對象。3、文件。4、“管道”,即一端輸入,另一端輸出。5、一個由其他種類的流組成的序列。6、其他數據源,如Internet連接。
其中FilterInputStream為裝飾器類提供基類。
2.2、OutputStream類型
該類別的類決定了輸出所要去往的目標:字節數組、文件或管道。
FilterOutputStream為裝飾器類提供基類。
3、添加屬性和有用的接口
3.1、通過FilterInputStream從InputStream讀取數據
FilterInputStream類能夠完成兩件完全不同的事情。其中,
DataInputStream:允許我們讀取不同的基本類型數據以及String對象。
其他FilterInputStream類則在內部修改InputStream的行為方式:
BufferedInputStream :是否緩沖,可防治每次讀取時都進行實際寫操作。
LineNumberInputStream :跟蹤輸入流的行號,可調用getLineNumber()和setLineNumber(int)。
PushbackInputStream:具有“能彈出一個字節的緩沖區。”因此可以將督導的最后一個字符回退。
3.1、通過FilterInputStream從InputStream讀取數據 以及是否把單一字符推回輸入流等等。
3.2、通過FilterOutPutStream從OutputStream寫入
DataOutputStream? : 可以按照可移植的方式向流中寫入基本類型數據。
PrintStream: 用于格式化輸出打印,具備方法print()和pringln().
BufferedOutputStream:它對數據流使用緩沖技術,可調用flush()清空緩沖區。
4、Reader和Writer
java1.1后加入的,設計Reader和Writer繼承結構主要是為了國際化。老的I/O流繼承層次結構僅支持8位字節流,并且不能很好地處理16位Unicode字符。有時我們必須把來自“字節”層次結構中的類和“字符”層次結構的類結合起來使用。為了實現這個目的,要用到“適配器”類:InputStreamReader可以把InputStream轉換為Reader,而OutputStreamWriter可以把OutputStream轉換為Writer。
4.1、數據的來源和去處
盡量嘗試使用Reader和Writer,一旦程序代碼無法成功編譯,我們就會發現自己不得不使用面向字節的類庫。
來源于去處:Java 1.0 類 相應的 Java 1.1 類InputStream? OutputStream? FileInputStream FileOutputStream StringBufferInputStream(已棄用) 。。Reader 適配器:InputStreamReader Writer 適配器:OutputStreamWriter? FileReader FileWriter StringReader StringWriter
4.2、更改流的行為
過濾器:Java 1.0類相應的Java 1.1類? ? ?FilterInputStream FilterReaderFilterOutputStreamFilterWriter(抽象類,沒有子類)BufferedInputStream? ? ? BufferedReader(有readLine())BufferedOutputStreamBufferedWriterDataInputStream? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? DataInputStream,需要readline()時使用BufferedReaderPrintStreamPrintWriterLineNumberInputStream(已棄用)LineNumberReaderStreamTokenizerStreamTokenizer(使用接受Reader的構造器)PushbackInputStreamPushbackReader
4.3、未發生變化的類
DataOutputStream,File,RandomAccessFilemSequenceInputStream
5、自我獨立的類:RandomAccessFile
RandomAccessFile適用于由大小已知的記錄組成的文件,我們可以使用seek()將記錄從一處轉移到另一處,然后讀取或者修改記錄。
RandomAccessFile支持搜尋方法,并且只適用于文件。JDK1.4中,RandomAccessFile的大多數功能由nio存儲映射文件所取代。
6、I/O流的典型使用方式
6.1、緩沖輸入文件
使用BufferedReader的readLine來讀取文件。
public static String
read(String filename) throws IOException {
// Reading input by lines:
BufferedReader in = new BufferedReader(
new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while ((s = in.readLine()) != null)
sb.append(s + "\n");
in.close();
return sb.toString();
}
用
FileReader來創建輸入流,再通過BufferedReader來裝飾。
例子:https://github.com/xu509/Java-practise/blob/master/src/test/file/excerise/BufferReaderExercise.java
6.2、從內存輸入
使用BufferInputFile.read()讀入的結果來創建StringReader。
public class MemoryInput {
public static void main(String[] args)
throws IOException {
StringReader in = new StringReader(
BufferedInputFile.read("MemoryInput.java"));
int c;
while ((c = in.read()) != -1)
System.out.print((char) c);
}
}
注意in.read()是以int形式返回下一字節,因此必須類型轉換為char才能正確打印。
6.3、格式化的內存輸入 要讀取格式化的數據,可以使用DataInputStream,這是一個面向字節的I/O類(不是面向字符)。
例子:
try {
DataInputStream in = new DataInputStream(
new ByteArrayInputStream(
BufferedInputFile.read(
"file/example3.txt").getBytes()));
while (true)
System.out.print((char) in.readByte());
} catch (EOFException e) {
System.err.println("End of stream");
}
通過捕獲異常來檢測輸入末尾,但這是對異常特性的錯誤使用
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("file/example3.txt")));
while (in.available() != 0)
System.out.print((char) in.readByte());
6.4、基本的文件輸出 FileWriter對象可以向文件寫入數據。通常會用BufferedWriter將其包裝起來用以緩沖輸出。
例:
static String file = "file/BasicFileOutput.out";
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader(
new StringReader(
BufferedInputFile.read("src/io/BasicFileOutput.java")));
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
while ((s = in.readLine()) != null)
out.println(lineCount++ + ": " + s);
out.close();
// Show the stored file:
System.out.println(BufferedInputFile.read(file));
}為了格式化輸出,用PrintWriter裝飾BufferedWriter。
文本輸出的快捷方式
JAVA SE5在PrintWriter中添加了一個輔助構造器,使得不需要每次創建PrintWriter的時候都進行包裝工作。
舊 :
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(file)));
新:
PrintWriter out = new PrintWriter(file);新舊效果相同,其他常見的寫入任務都沒有快捷方式。
使用緩沖(BufferedReader,BufferedWriter)和不使用的區別:
前者將一部分內存作為緩沖區,使每次操作在內存中操作,而不是直接操作文件,等文件關閉或者緩沖區滿后才往文件中寫。
如果需要大量的讀寫,使用緩沖會提高性能。如果需要立即生效,則不需要用緩沖。
6.5、存儲和恢復數據 我們可以用
DataOutputStream寫入數據,并用
DataInputStream恢復數據。另這2個流都是面向字節的,需用使用InputStream以及OutputStream。
public class StoringAndRecoveringData {
public static void main(String[] args)
throws IOException {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("file/Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(
new FileInputStream("file/Data.txt")));
System.out.println(in.readDouble());
// Only readUTF() will recover the
// Java-UTF String properly:
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
根據API可以發現,DataOutputStream可以寫入所有基于基本類型的數據。
6.6、讀寫隨機訪問文件 使用RandomAccessFile類似組合使用了DataInpuStream和DataOutputStream。
static String file = "rtest.dat";
static void display() throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "r");
for (int i = 1; i < 3; i++)
System.out.println(
"Value " + i + ": " + rf.readInt());
System.out.println(rf.readUTF());
rf.close();
}
public static void main(String[] args)
throws IOException {
RandomAccessFile rf = new RandomAccessFile(file, "rw");
for (int i = 1; i < 3; i++)
rf.writeInt(i);
rf.writeUTF("The end of the file");
rf.close();
display();
rf = new RandomAccessFile(file, "rw");
rf.seek(2 * 2);//使用seek在文件中移動 查找精度類型,int2字節,第二個即是2*2
rf.writeInt(9);//修改第二個
rf.close();
display();
構造參數第二個自行選擇:r? - 只讀,rw - 讀寫。
6.7、管道流
PipedInputStream、PipedOutputStream、PipedReader和PipedWriter的價值在于多線程,管道流用于任務之間的通信。
7、文件讀寫的實用工具 因為javaI/O中使用裝飾者模式,使得類使用起來無比復雜,故需要設計添加幫助類完成基本任務。
https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/TextFile.java
該工具類實現讀取文件為String類型,寫入str等簡單功能。
7.1、讀取二進制文件
public class BinaryFile {
public static byte[] read(File bFile) throws IOException {
BufferedInputStream bf = new BufferedInputStream(
new FileInputStream(bFile));
try {
byte[] data = new byte[bf.available()];
bf.read(data);
return data;
} finally {
bf.close();
}
}
public static byte[]
read(String bFile) throws IOException {
return read(new File(bFile).getAbsoluteFile());
}
}
練習:使用上述工具類,統計文件中字符出現的次數/
統計字節出現的次數:
https://github.com/xu509/Java-practise/blob/master/src/test/file/excerise/RecordKeyMap.java
8、標準I/O 標準I/O既是Unix中的 “程序所使用單一信息流”的概念。程序中所有輸入都可以來自
標準輸入,所有輸出也都可以發送到
標準輸出,所有錯誤信息都可以發送到
標準錯誤。
標準I/O的意義在于:我們可以很容易地把程序串聯起來,一個程序的標準輸出可以成為另一個程序的標準輸入。
8.1、從標準輸入中讀取
JAVA提供了 System.in、System.out和System.err 標準I/O模型。
System.out和System.err已經事先包裝成了printStream對象。
System.in卻是一個沒有包裝過的未經加工的InputStream。所以使用需要對其進行包裝。
包裝步驟:
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));其中InputStreamReader是適配器。
8.2、將System.out轉換成PrintWriter
public class ChangeSystemOut {
public static void main(String[] args) {
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
} System.out 是一個 PrintStream,也是OutputStream,PrintWriter有一個可以接受OutputStream的構造器。
8.3、標準I/O重定向 java 的 System 類提供了 setIn(InputStream),setOut(PrintStream),setErr(PrintStream) 來對標準輸入、輸出和錯誤I/O流進行重定向。
例子:https://github.com/xu509/Java-practise/blob/master/src/io/Redirecting.java
例子中將標準輸出和標準錯誤重定向到另一個文件,并且在最后恢復了標準輸出。需注意的是,I/O重定向操縱的是字節流。
9、進程控制
提供一個工具類讓java內部執行其他操作系統的程序。
工具類:https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/OSExecute.java
自定義異常:https://github.com/xu509/Java-practise/blob/master/src/net/mindview/util/OSExecuteException.java
執行類:https://github.com/xu509/Java-practise/blob/master/src/io/OSExecuteDemo.java
10、新I/O
JDK1.4后提供了新的I/O類庫,其目的在于提高速度。
新I/O提出了通道和緩沖器的概念,我們可以把通道理解為一個包含煤層(數據)的礦藏,而緩沖器則是派送到礦藏的卡車。我們只和緩沖器交互,而通道也只和緩沖器交互。
ByteBuffer對象就是唯一與通道交互的緩沖器,可以存儲未加工字節的緩沖器。ByteBuffer用于基礎類型和字節形式的讀取或輸出,但無法輸出或讀取對象,包括字符串類型。
舊I/O類庫中的FileInputStream,FileOutputStream以及RandomAccessFile都被修改,用以產生FileChannel(通道),這些都是字節操縱流,而Reader和Writer就不能用于產生通道;但是Channels類提供了實用方法,用于在通道中產生Reader和Writer。
上述三種流產生通道的例子:
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
// Write a file:
FileChannel fc =
new FileOutputStream("file/nio/data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
// Add to the end of the file:
fc =
new RandomAccessFile("file/nio/data.txt", "rw").getChannel();
fc.position(fc.size()); // Move to the end
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
// Read the file:
fc = new FileInputStream("file/nio/data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();//告知buff做好被讀取字節的準備
while (buff.hasRemaining())
System.out.print((char) buff.get());
}
注意如果打算使用FileChannel在通知ByteBuffer讀取后執行進一步的read()操作,每當進行read()后都必須使用flip()方法告知緩沖器做好被讀取的準備,我們就必須調用clear()來為下一次的read()做好準備,如下:
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
args = new String[2];
args[0] = "file/example3.txt";
args[1] = "file/nio/copy.out";
if (args.length != 2) {
System.out.println("arguments: sourcefile destfile");
System.exit(1);
}
FileChannel
in = new FileInputStream(args[0]).getChannel(),
out = new FileOutputStream(args[1]).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while (in.read(buffer) != -1) {
buffer.flip(); // Prepare for writing
out.write(buffer);
buffer.clear();? // Prepare for reading
}
}
}
創建2個通道,一個用于讀,而另一個用于寫,使用緩沖器進行讀寫,注意緩沖器flip(),clear()的使用。
上面程序并不是處理此類問題最理想的方式,特殊方法transferTo()和transferFrom()允許我們將一個通道和另一個通道直接相連。
關鍵代碼:
in.transferTo(0, in.size(), out);
ByteBuffer b = ByteBuffer.allocate(BSIZE);
while (in.read(b,in.size()) != -1) {
b.flip();
in.write(b,in.size()+1);
b.clear();
}
注意通道read和write的改變,以及2個連接方法的用法。
10.1、轉變數據
使用CharBuffer()進行轉換字符,然后通過asCharBuffer()可輸出緩沖器。
例子:https://github.com/xu509/Java-practise/blob/master/src/io/BufferToText.java
對于獲取的字節,要么在寫入文件的時候就轉換成字符,要么就在緩沖器輸出的時候進行編碼轉換為字符。可以使用Charset類來實現功能。
如:(1)
buff.rewind();//返回數據開始部分
String encoding = System.getProperty("file.encoding");
System.out.println("Decoded using " + encoding + ": "
+ Charset.forName(encoding).decode(buff));通過Charset.forName(encoding)獲取Charset對象,然后對緩沖器進行解碼。
(2)
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap(
"Some text".getBytes("UTF-16BE")));//輸入時進行編碼
fc.close();在寫入時即使用編碼。
(3)通過CharBuffer向ByteBuffer寫入
buff = ByteBuffer.allocate(24); // More than needed
buff.asCharBuffer().put("Some text");//緩沖器輸出時進行編碼
fc.write(buff);
10.2、獲取基本類型 ByteBuffer只保存字節類型,但可以從其所容納的字節中產生不同的基本類型。
完整例子:
https://github.com/xu509/Java-practise/blob/master/src/io/GetData.java
獲取char:
bb.asCharBuffer().put("Howdy!");
char c;
while ((c = bb.getChar()) != 0)
printnb(c + " ");
獲取short:
bb.asShortBuffer().put((short) 471142);
print(bb.getShort());
bb.rewind();
其中asCharBuffer()和asShortBuffer()都獲取了該緩沖器上的視圖。
10.3、視圖緩沖器(View buffer) 視圖緩沖器可以讓我們通過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。ByteBuffer依然是實際存儲數據的地方,“支持”著前面的視圖,因此,對視圖的任何修改都會映射稱為對ByteBuffer中數據的修改(即視圖的put方法)。
訪問即修改底層ByteBuffer的例子:
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
// Store an array of int:
ib.put(new int[]{11, 42, 47, 99, 143, 811, 1016});
// Absolute location read and write:
System.out.println(ib.get(3));
ib.put(3, 1811);
// Setting a new limit before rewinding the buffer.
ib.flip();
while (ib.hasRemaining()) {
int i = ib.get();
System.out.println(i);
}
}
}
ByteBuffer通過不同的視圖顯示方式也不同
例子 : https://github.com/xu509/Java-practise/blob/master/src/io/ViewBuffers.java
當數據為8字節數組 :
new byte[]{0, 0, 0, 0, 0, 0, 0, 'a'}不同的視圖顯示不同:
Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97,
Float Buffer 0 -> 0.0, 1 -> 1.36E-43,
Int Buffer 0 -> 0, 1 -> 97,
Long Buffer 0 -> 97,
Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97,
Double Buffer 0 -> 4.8E-322,Char Buffer 0 -> ,1 -> , 2-> ,3 -> a
字節存放次序:不同的機器可能會使用不同的字節排序方法來存儲數據。“big endian”(高位優先)將最重要的字節放在地址最低的存儲單元。“little endian”(低位優先) 則是將最重要的字節放在地址最高的存儲器單元。當存儲量大于一個字節時,就要考慮字節的順序問題。
ByteBuffer是以高位優先的形式存儲數據的,并且數據在網上傳送時也常常使用高位優先的形式。
我們可以使用帶參數的ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN的order()方法改變排序方式。
bb.order(ByteOrder.BIG_ENDIAN);完整: https://github.com/xu509/Java-practise/blob/master/src/io/Endians.java
10.4、用緩沖器操縱數據當需要將字節數組寫入文件中時,使用ByteBuffer.wrap()方法將字節數組包裝,使用getChannel方法在FileOutputStream上打開一個通道,接著將緩沖器的數據寫入通道中。
10.5、緩沖器的細節Buffer由數據與4個索引組成,這4個索引hi:mark(標記)、position(位置)、limit(界限)和 capacity(容量)。
flip() : 將limit設置為position,position設置為0。此方法用于準備從緩沖區讀取已經寫入的數據。
remaining(): 返回(limit - position)。
hasRemaining(): 若介于position和limit之間的元素,則返回true()
研究方法對索引影響的例子 : https://github.com/xu509/Java-practise/blob/master/src/io/UsingBuffers.java
10.6、內存映射文件內存映射文件允許我們創建和修改那些因為太大而不能放入內存的文件。有了內存映射文件,我們就可以假定整個文件都放在內存中,而且可以完全把它當做非常大的數組來訪問。
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128 MB
public static void main(String[] args) throws Exception {
MappedByteBuffer out =
new RandomAccessFile("file/example1.txt", "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 + 20; i++)
printnb((char) out.get(i));
}
}如上圖,為了能同時讀寫大文件,使用RandomAccessFile來獲取通道,然后使用map()方法來產生MappedByteBuffer。
MappedByteBuffer是由ByteBuffer繼承而來,因此它具有ByteBuffer的所有方法。
性能上使用映射文件訪問要比舊的緩沖輸入輸出要高出不少。
測試類: https://github.com/xu509/Java-practise/blob/master/src/io/MappedIO.java
10.7、文件加鎖
JDK1.4引入了文件加鎖機制,允許我們同步訪問某個作為共享資源的文件,通過通道的tryLock,或者lock方法,可以獲得整個文件的鎖,或部分的
FileLock fl = fos.getChannel().tryLock();
fl.release();
可通過添加參數來對文件的一部分上鎖
tryLock(long position,long size,boolean shared) 或者 lock(...,...,...)
第三個參數決定了鎖是獨占鎖還是共享鎖,第一第二個參數決定了文件的加鎖區域,而沒有參數的加鎖方法會對整個文件進行加鎖。
獨占鎖或者共享鎖的支持必須通過底層的操作系統提供,如果操作系統不支持共享鎖,則會提供獨占鎖。可以通過FileLock.isShared()查詢。
對映射文件的部分上鎖
對大文件的不同部分加鎖:
https://github.com/xu509/Java-practise/blob/master/src/io/LockingMappedFiles.java
11、壓縮
Java I/O庫提供讀寫壓縮格式的數據流,使用它們進行封裝以提供壓縮功能,這些類不是從 Reader 和 Writer 派生出來,而是屬于InputStream 和 OutputStream。
壓縮類功能CheckedInputStream GetCheckSum() 為任何InputStream產生校驗和(不僅是解壓縮)CheckedOutputStreamGetCheckSum()為任何OutputStream產生校驗和(不僅是壓縮)DeflaterOutputStream壓縮類的基類ZipOutputStream一個DeflaterOutputStream,用于將數據壓縮成Zip文件格式GZIPOutputStream一個DeflaterOutputStreamm,用于將數據壓縮成GZIP文件格式InflaterInputStream解壓縮類的基類ZipInputStream一個InflaterInputStream,用于解壓縮Zip文件格式的數據GZIPInputStream 一個InflaterInputStream,用于解壓縮GZIP文件格式的數據
11.1 用GZIP進行簡單壓縮
如果對單個數據流(而不是互異數據)進行壓縮,那么它可能是比較適合的選擇
BufferedReader in2 = new BufferedReader(
new InputStreamReader(new GZIPInputStream(
new FileInputStream("test.gz"))));
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream("test.gz")));
11.2 用Zip進行多文件壓縮
支持Zip格式的Java庫更加全面。利用該庫可以方便的保存多個文件,甚至一個獨立的類,使得讀取Zip文件更加方便。
如下例,Checksum有2個類型,Adler32(更快)和CRC32(更準確)。
寫入ZIP文件:
FileOutputStream f = new FileOutputStream("test.zip");
CheckedOutputStream csum =
new CheckedOutputStream(f, new Adler32());
ZipOutputStream zos = new ZipOutputStream(csum);
BufferedOutputStream out =
new BufferedOutputStream(zos);
zos.setComment("A test of Java Zipping");
// No corresponding getComment(), though.
for (String arg : args) {
print("Writing file " + arg);
BufferedReader in =
new BufferedReader(new FileReader(arg));
zos.putNextEntry(new ZipEntry(arg));
int c;
while ((c = in.read()) != -1)
out.write(c);
in.close();
out.flush();
}
out.close();對每一個要加入壓縮檔案的文件,都需要調用putNextEntry(),將其傳遞個一個ZipEntry對象。
讀取ZIP文件:
FileInputStream fi = new FileInputStream("test.zip");
CheckedInputStream csumi =
new CheckedInputStream(fi, new Adler32());
ZipInputStream in2 = new ZipInputStream(csumi);
BufferedInputStream bis = new BufferedInputStream(in2);
ZipEntry ze;
while ((ze = in2.getNextEntry()) != null) {
print("Reading file " + ze);
int x;
while ((x = bis.read()) != -1)
System.out.write(x);
}
if (args.length == 1)
print("Checksum: " + csumi.getChecksum().getValue());
bis.close();
更簡單的方法:
ZipFile zf = new ZipFile("test.zip");
Enumeration e = zf.entries();
while (e.hasMoreElements()) {
ZipEntry ze2 = (ZipEntry) e.nextElement();
print("File: " + ze2);
// ... and extract the data as before
}
完整例子:https://github.com/xu509/Java-practise/blob/master/src/io/ZipCompress.java
11.3 Java檔案文件
jar [options] destination [manifest] inputfile(s)
options 是一個字母集合(不需要輸入任何“-”或其他任何標示符),一下字符在Unix和Linux系統的tar文件中也具有相同的意義.
c? ? ? ? ? ? ? ? 創建一個新的或空的壓縮文檔t列出目錄表x解壓所有文件x file解壓該文件f? ? ? ? ? ? ? ? ? ? ? ? ? ? ?意指:“我打算指定一個文件名。” 如果沒有用這個選項, jar假設所有的輸入都來自于標準輸入。m表示第一個參數將是用戶自建的清單文件的名字v 產生詳細輸出,描述jar所做的工作O只存儲文件,不壓縮文件(用來創建一個可放在類路徑中的JAR文件)M不自動創建文件清單
典型用法:
jar cf myJarFile.jar *.class? -
創建一個名為myJarFile.jar的JAR文件,該文件包含了當前目錄中的所有類文件,以及自動產生清單文件
jar cmf myJarFile.jar myManifestFile.mf *.class - 其他與上例相同,添加一個名為myManifestFile.mf的用戶自建清單文件
jar tf myJarFile.jar? - 產生myJarFile.jar內所有文件的一個目錄表
jar tvf myJarFile.jar? - 提供有關myJarFile.jar中的文件的更詳細的信息
假定audio、classes和image是子目錄,下面的命令將所有子目錄合并到文件myApp.jar中,其中也包括了 "v"標志,當jar程序運行時,該標志可以提供更詳細的信息 -? ?jar cvf myApp.jar audio classes image
12、對象序列化
Java的對象序列化將那些實現了Serializable接口的對象轉換成一個字節序列,并能夠在以后將這個字節序列完全恢復為原來的對象。
利用對象的序列化可以實現輕量級持久性。
要序列化一個對象,首先需要創建一個OutputStream對象,然后使用ObjectOuptStream進行裝飾。然后,只需要調用writeObject()即可將對象序列化,并將其發送給OutputStream。要反響進行該過程,需要將一個InputStream封裝在ObjectInputStream內,然后調動readObject(),獲得一個Object引用,然后對其向下轉型則能直接設置他們。
輸出序列化對象:
OutputStream outstream = new FileOutputStream(filepath);
ObjectOutputStream out = new ObjectOutputStream(outstream);
//? ? ? ? out.writeObject("test write");
out.writeObject(a1);
out.close();
輸入序列化對象:
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filepath));
String s = (String) in.readObject();
Object obj = in.readObject();
in.close();
詳細例子 : https://github.com/xu509/Java-practise/blob/master/src/io/Worm.java
12.1 尋找類
獲取序列化后的文件,重新進行包裝時需要原類的.class文件,否則無法包裝。
12.2 序列化的控制 如果需要考慮特殊的安全問題,希望對象的一部分被序列化,或者一個對象被還原后,其子對象需要重新創建。
在這些特殊情況下,可通過實現Externalizable接口代替Serializable接口,來對序列化過程進行控制。這個接口增添兩個方法:writeExternal()、readExternal()。這兩個方法會在序列化與反序列化還原的過程中被自動調用。
https://github.com/xu509/Java-practise/blob/master/src/io/Blips.java
如上例,Blip2因為構造方法非public所以無法反序列化還原。這是Externalizable和Serializable的不同,Serizalizable接口不依賴構造方法還原,而Externalizable還原時會調用所有的默認構造方法,然后再調用readExternal()。
https://github.com/xu509/Java-practise/blob/master/src/io/Blip3.java
Externalizable在反序列化時,需要向對象內所有的成員對象寫入數據,否則還原后的對象中的成員對象數據為空。
public void writeExternal(ObjectOutput out)
throws IOException {
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
s = (String) in.readObject();
i = in.readInt();
}
transient(瞬時)關鍵詞
除了Externalizeable外,可以用transient在Serializable對象中逐個字段關閉序列化。
例子: https://github.com/xu509/Java-practise/blob/master/src/io/Logon.java
private transient String password;
private Date date = new Date();
當對象被恢復時,password域會變成"null",date對象不會發生變化,序列化的時候是多少還原時就是多少。
Externalizable的替代方法
如果不希望實現Externalizable方法,我們可以實現Serializable接口,并且
添加名為writeObject()和readObject()方法。這樣一旦對象被序列化或者反序列化還原,就會自動地分別調用這兩個方法,這些方法必須有準確的方法特征簽名:
private void writeObject(ObjectOutputStream stream)
throws IOException ;
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
https://github.com/xu509/Java-practise/blob/master/src/io/SerialCtl.java
private void writeObject(ObjectOutputStream stream)
throws IOException {
stream.defaultWriteObject();
stream.writeObject(b);
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
b = (String) stream.readObject();
}版本控制
由于Java版本控制機制國語簡單,因而對于序列化的支持,只適用于短期存儲或應用之間的RMI。
12.3 使用”持久性“
我們將2個對象(他們都具有指向第三個對象的引用 )進行序列化,當我們恢復他們時,第三個對象會只出現一次嗎?如果兩個對象序列化成獨立的文件,然后在代碼的不同部分對它們進行反序列化還原,又會怎么樣呢?
https://github.com/xu509/Java-practise/blob/master/src/io/MyWorld.java
經過上述程序的試驗,在同一個流內,2個對象引用了相同的地址。
https://github.com/xu509/Java-practise/blob/master/src/io/StoreCADState.java
在上面這個例子里,xPos,yPos,dimension都成功的序列化與反序列化了,但是static的color并沒有成功的恢復。而Line類中的
/*序列化時記錄static變量的值*/
public static void
serializeStaticState(ObjectOutputStream os)
throws IOException {
os.writeInt(color);
}
/*序列化時記錄static變量的值*/
public static void
deserializeStaticState(ObjectInputStream os)
throws IOException {
color = os.readInt();
}方法則的作用就是將靜態變量的序列化和反序列化。
13、XML
書中使用XOM類庫來完成對XML的操作。
14、Preferences
Preferences API 與對象序列化相比,與對象持久性更密切,因為它可以自動存儲和讀取信息。不過它只能用于小的、受限的數據集合——我們只能存儲基本類型和字符串,并且每個字符串的存儲長度不能超過8K。
顧名思義,Preferences API用于存儲和讀取用戶的偏好,以及程序配置項的設置。
Preferences是一個鍵值集合(類似映射),存儲在一個節點層次結構中。簡單例子:
public class PreferencesDemo {
public static void main(String[] args) throws Exception {
Preferences prefs = Preferences
.userNodeForPackage(PreferencesDemo.class);
prefs.put("Location", "Oz");
prefs.put("Footwear", "Ruby Slippers");
prefs.putInt("Companions", 4);
prefs.putBoolean("Are there witches?", true);
int usageCount = prefs.getInt("UsageCount", 0);
usageCount++;
prefs.putInt("UsageCount", usageCount);
for (String key : prefs.keys())
print(key + ": " + prefs.get(key, null));
// You must always provide a default value:
print("How many companions does Dorothy have? " +
prefs.getInt("Companions", 0));
}
}
/* Output: (Sample)
Location: Oz
Footwear: Ruby Slippers
Companions: 4
Are there witches?: true
UsageCount: 53
How many companions does Dorothy have? 4
*///:~
每次PreferencesDemo運行后,usageCount都會+1。