• 【Notes】《Thinking in Java》【Chapter 11】Part II


    六、Typical uses of I/O streams

    /*
     * @filename IOStreamDemo.java
     * @author Bruce Eckel
     *
     * ToDo Typical I/O stream configurations.
     */
    
    import java.io.EOFException;
    import java.io.IOException;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.ByteArrayInputStream;
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.io.RandomAccessFile;
    import java.io.StringReader;
    
    public class IOStreamDemo {
        // Throw exceptions to console:
        public static void main(String[] args) throws IOException {
            String s = null;
            String s2 = null;
            
            // 1. Reading input by lines:
            BufferedReader in = new BufferedReader(
                new FileReader("IOStreamDemo.java"));
            while((s = in.readLine()) != null)
                s2 += s + "\n";
            in.close();
            
            // 1b. Reading standard input:
            BufferedReader stdin = new BufferedReader(
                new InputStreamReader(System.in));
            System.out.print("Enter a line:");
            System.out.println(stdin.readLine());
            
            // 2. Input from memory
            StringReader in2 = new StringReader(s2);
            int c;
            while((c = in2.read()) != -1)
                System.out.print((char) c);
                
            // 3. Formatted memory input
            try {
                DataInputStream in3 = new DataInputStream(
                    new ByteArrayInputStream(s2.getBytes()));
                
                while(true)
                    System.out.print((char) in3.readByte());
            } catch(EOFException e) {
                System.err.println("End of Stream");
            }
            
            // 4. File output
            try {
                BufferedReader in4 = new BufferedReader(
                    new StringReader(s2));
                    
                PrintWriter out1 = new PrintWriter(
                    new BufferedWriter(new FileWriter("IODemo.out")));
                
                int lineCount = 1;
                
                while((s = in4.readLine()) != null)
                    out1.println(lineCount++ + ": " + s);
                   
                out1.close();
            } catch(EOFException e) {
                System.err.println("End of Stream");
            }
            
            // 5. Storing & recovering data
            try {
                DataOutputStream out2 = new DataOutputStream(
                    new BufferedOutputStream(new FileOutputStream("Data.txt")));
                    
                out2.writeDouble(3.14159);
                out2.writeChars("That was pi\n");
                out2.writeBytes("That was pi\n");
                out2.close();
                
                DataInputStream in5 = new DataInputStream(
                    new BufferedInputStream(new FileInputStream("Data.txt")));
                BufferedReader in5br = new BufferedReader(
                    new InputStreamReader(in5));
                // Must use DataInputStream for data
                
                System.out.println(in5.readDouble());
                // Can now use the "proper" readLine()
                System.out.println(in5br.readLine());
                // But the line comes out funny
                // The one created with writeBytes is OK:
                System.out.println(in5br.readLine());
            } catch(EOFException e) {
                System.err.println("End of Stream");
            }
            
            // 6. Reading / Writing random access files
            RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw");
            for(int i = 0; i < 10; i++)
                rf.writeDouble(i * 1.414);
            rf.close();
            
            rf = new RandomAccessFile("rtest.dat", "rw");
            rf.seek(5 * 8);
            rf.writeDouble(47.0001);
            rf.close();
            
            rf = new RandomAccessFile("rtest.dat", "r");
            for(int i = 0; i < 10; i++)
                System.out.println("Value " + i + ": " + rf.readDouble());
            rf.close();
        }
    }
    

    (1)Input streams

    1. Buffered input file
      • 需要開啟一個文件用於字符的讀取時,可以使用 FileReader 並以一個 String 或 File 對象指定文件名。基於速度考量,通常會把這個 FileReader 的 reference 傳給 BufferedReader 的 constructor 使這個文件具備緩衝功能。

      • BufferedReader 的 readLine() 函數會將換行符過濾掉,所以使用它的時候必須自行添加換行符。

      • java.lang.System 的三個 I/O 成員(final static):
        • err:PrintStream

        • out:InputStream

        • in:PrintStream

    2. Input from memory
      • 通過 String(已含內容)產生 StringReader。

      • java.io.Reader(abstract class)的 read() 函數返回的是 int,所以使用 read() 時必須根據實際進行轉型。

    3. Formatted memory input
      • 讀取格式化數據通常使用的是 DataInputStream(byte-oriented),因此也就只能使用 InputStream classes 而不能使用 Reader classes。

      • 任何一個 Byte 對 DataInputStream 的 readByte() 函數來說都是合法的結果,所以不能憑它的返回值來判斷輸入是否結束。

      • DataInputStream 的 available()(繼承自 java.io.FilterInputStream)函數返回可供讀取的字符數。但它的運作方式取決於所輸入的對象,所以應當結合實際使用。

    4. File output
      • 產生一個 FileWriter 對象連接至文件,將其 reference 傳給 BufferedWriter 的 constructor(緩衝可以大幅提高 I/O 效能)。如果需要格式化輸出,可以再將 BufferedWriter 的 object reference 傳給 PrintWriter 的 constructor。(這樣產生的文件是文本文件)

      • 使用 Buffered 類的 classes 緩衝輸出文件後,必須調用 close() 函數關閉文件,否則緩衝區的內容可能不會被清空從而得不到完整的結果。

    (2)Output streams

    1. Storing and recovering data
      • output streams 主要分為兩種:1)為了讓人直接查看結果(如 java.io.PrintWriter);2)為了讓 DataInputStream 可以再次讀取(Random Access File 不屬于上述的兩種,但其數據格式與 DataInputStream 和 DataOutputStream 相容)。

      • DataOutputStream 使用 writeChars() 和 writeBytes() 這兩個函數輸出字符串。其中 writeChars() 是以 16bit 的 Unicode 字符進行輸出的,所以如果用 readLine() 讀取這些文本,那麽每個字符間都會多了一個空格,這是因為 Unicode 會額外插入一個 byte。因此,對 ASCII 而言,使用 writeBytes() 加上換行符輸出,然後以 readLine() 讀取的方法會更為簡單。

    2. Random access files
      • java.io.RandomAccessFile 完全獨立於 I/O 繼承體系之外。由於它實現了 DataInput 和 DataOutput 接口,所以它無法和 InputStream / OutputStream 的子類搭配使用(例如,無法為其加上緩衝功能)。

    (3)A bug?

    • 數據的寫入必須出現在文本之前,否則在讀取的時候就會擲出 EOFException。
    /*
     * @filename IOProblem.java
     * @author Bruce Eckel
     *
     * ToDO Java 1.1 and higher I/O Problem Deom.
     */
    
    import java.io.IOException;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.BufferedReader;
    import java.io.DataInputStream; 
    import java.io.DataOutputStream;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStreamReader;
    
    
    public class IOProblem {
        // Throw exceptions to console:
        public static void main(String[] args) throws IOException {
            DataOutputStream out = new DataOutputStream(
                new BufferedOutputStream(new FileOutputStream("Data.txt")));
            out.writeDouble(3.14159);
            out.writeBytes("That was the value of pi\n");
            out.writeBytes("This is pi/2:\n");
            out.writeDouble(3.14159/2);
            out.close();
    
            DataInputStream in = new DataInputStream(
                new BufferedInputStream(new FileInputStream("Data.txt")));
            BufferedReader inbr = new BufferedReader(
                new InputStreamReader(in));
            // The doubles written BEFORE the line of text
            // read back correctly:
            System.out.println(in.readDouble());
            // Read the lines of text:
            System.out.println(inbr.readLine());
            System.out.println(inbr.readLine());
            // Trying to read the doubles after the line
            // produces an end-of-file exception:
            System.out.println(in.readDouble());
        }
    }
    

    3.14159
    That was the value of pi
    This is pi/2:
    Exception in thread "main" java.io.EOFException
            at java.io.DataInputStream.readFully(Unknown Source)
            at java.io.DataInputStream.readLong(Unknown Source)
            at java.io.DataInputStream.readDouble(Unknown Source)
            at IOProblem.main(IOProblem.java:42)
    

    (4)Piped streams

    • PipedInputStream / PipedOutputStream / PipedReader / PipedWriter 主要用於 multithreads。

    七、Standard I/O

    • Standard I/O:可為程序所用的單一信息流。所有的程序輸入皆可取自 standard input,所有的輸出皆可送至 standard output;所有的錯誤信息皆可送至 standard error。

    (1)Reading from standard input

    • System.out 和 System.err 都被包裝為 PrintStream object(in 和 err 是 java.lang.System 的 final static PrintStream 成員),可以直接使用;而 System.in 則是原始的 InputStream,所以在讀取前必須加以包裝(通常是 InputStreamReader + BufferedReader)。
    /*
     * @filename Echo.java
     * @author Bruce Eckel
     *
     * ToDo How to read from standard input.
     */
    
    import java.io.IOException;
    
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    public class Echo {
        public static void main(String[] args) throws IOException {
            String s;
        
            BufferedReader in = new BufferedReader(
                new InputStreamReader(System.in));
            while((s = in readLine()).length() != 0)
                System.out.println(s);
            // An empty line will terminate the program.
        }
    }
    

    (2)Changing System.out to PrintWriter

    • System.out 是 PrintStream,而 PrintStream 則是一個 OutputStream。

    • PrintWriter 有一個構造函數可以以 OutputStream 為參數,將 System.out 轉換為 PrintWriter。
    /*
     * @filename ChangeSystemOut.java
     * @author Bruce Eckel
     *
     * ToDo Turn System.out into a PrintWriter.
     */
     
    import java.io.PrintWriter;
    
    public class ChangeSystemOut {
        public static void main(String[] args) {
            /* 此處不能使用 PrintWriter(OutputStream out) 而必須使用
               PrintWriter(OutputStream out, boolean autoFlush) 
               並將 autoFlush 設置為 true 將自動清空緩衝的功能開啟
               否則很可能會看不到輸出。 */
            PrintWriter out = new PrintWriter(System.out, true);
            out.println("Hello, world");
        }
    }
    

    (3)Redirecting standard I/O

    • java.io.System 提供了幾個 static 函數,可對 standard input、standard output、standard error 等 I/O streams 進行重定向:
      • setIn(InputStream)

      • setOut(PrintStream)

      • setErr(PrintStream)

    • I/O 重定向處理的是以 byte 而非 character 為單位的 streams,因此使用的是 InputStream 和 OutputStream 而不是 Reader 和 Writer。
    /*
     * @filename Redirecting.java
     * @author Bruce Eckel
     *
     * ToDo Demostrates standard I/O redirection.
     */
     
    import java.io.IOException;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.BufferedReader;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    
    public class Redirecting {
        public static void main(String[] args) throws IOException {
            BufferedInputStream in = new BufferedInputStream(
                new FileInputStream("Redirecting.java"));
            PrintStream out = new PrintStream(
                new BufferedOutputStream(new FileOutputStream("test.out")));
            System.setIn(in);
            System.setOut(out);
            System.setErr(out);
            
            BufferedReader br = new BufferedReader(
                new InputStreamReader(System.in));
            String s;
            while((s = br.readLine()).length() != 0)
                System.out.println(s);
            out.close(); // Remember this!!!
        }
    }
    

    八、Compression

    • Java I/O library 提供了一系列的 classes 讓我們可以以壓縮格式對 streams 進行讀寫,這些 classes 將既有的 I/O classes 包裝起來並提供壓縮功能。

    • Java I/O 的 compression library 全部繼承自 InputStream / OutputStream,這是因為它們處理的是 bytes 而不是 characters。
    compression class function
    java.util.zip.CheckedInputStream getCheckSum() 能針對任意類型的 InputStream 產生 checksum(校驗碼)而不僅是解壓
    java.util.zip.CheckedOutputStream getCheckSum() 能針對任意類型的 OutputStream 產生 checksum(校驗碼)而不僅是壓縮
    java.util.zip.DeflaterOutputStream compression classes 的 base class
    java.util.zip.ZipOutputStream 可將數據壓縮為 Zip 格式
    java.util.zip.GZIPOutputStream 可將數據壓縮為 GZIP 格式
    java.util.zip.InflaterInputStream decompression classes 的 base class
    java.util.zip.ZipInputStream 可將以 Zip 格式存儲的數據解壓
    java.util.zip.GZIPInputStream 可將以 GZIP 格式存儲的數據解壓

    (1)Simple compression with GZIP

    • compression 相關 classes 的運用方式很直觀:只需將 output stream 包裝成 GZIPOutputStream / ZipOutputStream,並將 input stream 包裝成 GZIPInputStream / ZipInputStream,然後直接進行一般的 I/O 處理即可。

    • GZIP 接口較為簡單因此比較適合對單一的 stream 數據而非多份相異數據進行壓縮。
    /*
     * @filename GZIPcompress.java
     * @author Bruce Eckel
     *
     * ToDo Uses GZIP compression to compress a file
     *      whose name is passed on the command line
     *
     *      【注】本例混用了 char-oriented streams 和
     *      byte-oriented streams:in 用的是 Reader,
     *      而 GZIPOutputStream 的 constructor 只接受
     *      OutputStream 而非 Writer。文件開啟後,
     *      GZIPInputStream 會被轉換為 Reader。
     */
    
    import java.io.IOException;
    
    import java.io.BufferedReader;
    import java.io.BufferedOutputStream;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.FileReader;
    import java.io.InputStreamReader;
    
    import java.util.zip.GZIPInputStream;
    import java.util.zip.GZIPOutputStream;
    
    public class GZIPcompress {
        public static void main(String[] args) throws IOException {
            // 1)壓縮文件
            BufferedReader in = new BufferedReader(
                new FileReader(args[0]));
            BufferedOutputStream out = new BufferedOutputStream(
                new GZIPOutputStream(new FileOutputStream("test.gz")));
            System.out.println("Writing file");
            
            int c;
            while((c = in.read()) != -1)
                out.write(c);
            in.close();
            out.close();
            
            // 2)顯示壓縮文件內容
            System.out.println("Reading file");
            BufferedReader in2 = new BufferedReader(
                new InputStreamReader(new GZIPInputStream(
                    new FileInputStream("test.gz"))));
                    
            String s;
            while((s = in2.readLine()) != null)
                System.out.println(s);
        }
    }
    

    (2)Multifile storage with Zip

    • java.util.zip 提供了一系列針對 Zip 格式的 classes;其中 CheckSum(interface)有兩類:Adler32(較快) 和 CRC32(較慢但更為精確)。

    (3)Java ARchives (JARs)

    • JAR 是由多個經過 Zip 壓縮的文件所組成的單一文件,它同時附有一份內部文件的清單(manifest)。其中 mainfest 可以自己建立,否則 JAR 程序會自動產生。

    • Sun JDK 內附了一個 JAR 工具,其壓縮格式如下:
          jar [options] destination [mainfest] inputfile(s)
    option function
    c 創建新的文件
    t 列出文件的內容
    x 解壓所有文件
    u 更新既有的文件
    v 產生 jar 執行過程的完整信息
    f 指定歸檔的文件名。如果不進行指定,JAR 就會假設其輸入來自 standard input,並在建立文件時假定其輸出對象為 standard output
    m 指定 manifest 文件名
    O 只存儲文件,並不進行 Zip 壓縮
    M 通知 JAR 不要自動產生 manifest 文件
    i 為指定的 JAR 文件生成索引信息
    C 指定歸檔的目錄(包含其子目錄)

    九、Object serialization

    • Java 的 object serialization 機制可以將任何實現了 Serializable interface 的對象轉換為 bytes 序列,該序列可被還原為原來的對象,還可以通過網絡進行傳輸(object serialization 機制會自動處理 bytes 序列在不同 OS 上的差異)。

    • Object serialization 可將 serializable 對象寫入磁盤,並於程序重新啟動時恢復其狀態;從而實現了 lightweight persistence(之所以稱其為 lightweight,是因為 serialize 和 deserialize 的動作需要手動完成)。

    • Object serialization 使 Java 得以支持 RMI(Remote Method Invocation)和 JavaBeans。

    • 對一個對象進行 serialization,只需它實現了 Serializable interface 即可;而 Serializable interface 本身並不具備任何的函數(這樣的 interface 也稱為 marker interface)。

    • Object Serialization:
      • serialize:產生某種 OutputStream 對象並以 ObjectOutputStream 對象加以包裝,然後調用 writeObject() 即可,輸出的數據將被送回該 OutputStream 中;

      • deserialize:產生某種 InputStream 對象並以 ObjectInputStream 對象加以包裝,然後調用 readObject() 即可(readObject() 返回的是經過 upcasting 的對象,所以使用該對象前必須進行 downcasting)。

    • Object serialization 不僅存儲對象在內存中的原始數據,還會追蹤該對象內含的 reference 所指的對象並將它們存儲,以此類推,整個對象網絡都會被存儲下來。

    • Deserializing 並不會調用任何的 constructor(包括 defalut constructor ),整個對象的狀態都是通過讀取 InputStream 恢復的。

    (1)Finding the class

    • Deserializing 需要通過對象原始的 .class 文件,所以進行 deserializing 時必須首先確保 JVM 可以找到( local 的 classpath 或是 Internet )相應的 .class 文件。

    (2)Controlling serialization

    • 與 Serializable interface 相比,Externalizable interface(繼承自 Serializable interface )增加了 writeExternal() 和 readExternal() 兩個函數,用以控制 serialization 的過程。

    • 與 Serializable interface 不同,使用 Externalizable interface 進行 deserializing 時,所有的 default constructor 都會被調用( Serializable interface 僅讀取 InputStream,並不會有任何的調用動作),然後才是 readExternal()。由於 Externalizable interface 的這個特點,因此在使用它的時候必須在 writeExternal() 和 readExternal() 中根據實際調用正確的 constructor。

    • The transient keyword
      • Serializtion 會略過由關鍵字 transient 聲明的數據(不進行存儲)。

      • 因為缺省情況下 Externalizable 對象的值並不會被存儲,所以 transient 只用於 Serializable。
    /*
     * @filename Logon.java
     * @author Bruce Eckel
     *
     * ToDo Demonstrates the "transient" keyword.
     */
    
    import java.io.IOException;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    import java.util.Date;
    
    class Logon implements Serializable {
        private Date date = new Date();
        private String username;
        // transient 聲明的 password (值)在 serializing 時將不會被存儲
        private transient String password;
    
        Logon(String name, String pwd) {
            username = name;
            password = pwd;
        }
    
        public String toString() {
            String pwd = (password == null) ? "(n/a)" : password;
            return "logon info: \n " + "username: " + username
                + "\n date: " + date + "\n password: " + pwd;
        }
    
        public static void main(String[] args) 
            throws IOException, ClassNotFoundException {
    
            Logon a = new Logon("Hulk", "myLittlePony");
            System.out.println("logon a = " + a);
            ObjectOutputStream o = new ObjectOutputStream(
                new FileOutputStream("Logon.out"));
            o.writeObject(a);
            o.close();
            // Delay:
            int seconds = 5;
            long t = System.currentTimeMillis() + seconds * 1000;
            while(System.currentTimeMillis() < t)
                ;
            // Now get them back:
            ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("Logon.out"));
            System.out.println("Recovering object at " + new Date());
            a = (Logon) in.readObject();
            System.out.println("logon a = " + a);
        }
    }
    
    • Externalizable 的替代方案
      • 實現 Serializable interface 並加入 private 的 writeObject() 和 readObject(),它們會取代 serialization 缺省的行為。
        // 必須遵循以下的 signatures:
        private void writeObject(ObjectOutputStream stream) throws IOException;
        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
        
      • 調用 ObjectOutputStream.writeObject() 時,傳入的 Serializable 對象會被檢查以確認有無自己的 writeObject(),有則執行;同理於 readObject()。

      • ObjectOutputStream.defaultWriteObject() 和 ObjectInputStream.defaultReadObject() 用於處理 non-static 和 non-transient 成員。

    • Versioning:Java 的 versioning 機制過於簡單,並不適用於所用的場合(詳見 JDK 的 HTML 說明文檔)。

    (3)Using persistence

    • Serialization 機制可以完全恢復單一 stream 中的對象網絡,並且不會額外的複製任何對象,也就是說單一 stream 中如果有多個對象擁有指向同一對象的 reference,該 reference 指向的對象僅會保存一份。對於非單一的 stream,由於系統無法知道各個 stream 中的對象其實是相同的,所以系統會製造出完全不同的對象網絡。

    • 由於 static 成員並不屬於對象,所以 serializing 並不存儲 static 成員,因此需要自行提供 static 函數對其進行處理。

    十、Tokenizing input

    • Tokenizing:将 character 序列劃分為 token 序列(由選定的分隔符劃分而成的文本片段)。

    (1)StreamTokenizer

    • java.io.StreamTokenizer 並非繼承自 InputStream / OutputStream,但因為它只能處理 InputStream(Deprecated)和 Reader 對象,所以被歸類於 I/O。

    (2)StringTokenizer

    • java.util.StringTokenizer 一般只用於處理簡單的應用( String ),可被視為簡化的 java.io.StreamTokenizer。

    (3)Checking capitalization style

    【Summary】

    • Java I/O 體系主要可分為 bytes-oriented 和 chars-oriented,應根據實際進行選擇,一般情況下支持 Unicode 的 chars-oriented classes 應當被優先考慮。

    • Java I/O library 中大量使用了 decorator 模式。
  • 相关阅读:
    Appium运行时,error: Logcat capture failed: spawn ENOENT的解决办法
    pwntools使用简介3
    pwntools使用简介2
    pwnable.kr memcpy之write up
    pwnable.kr uaf之wp
    【笔记】objdump命令的使用
    pwnable.kr cmd2之write up
    pwnable.kr cmd1之write up
    pwnable.kr lotto之write up
    pwnable.kr blackjack之write up
  • 原文地址:https://www.cnblogs.com/jim/p/278463.html
Copyright © 2020-2023  润新知