1. 程式人生 > >Java 高階程式設計-IO操作深入

Java 高階程式設計-IO操作深入

學習阿里雲大學零基礎學Java系列 Java高階程式設計

1. 字元編碼

在計算機的世界裡面只認0、1的資料,如果想描述一些文字的編碼就需要對這些二進位制的資料進行組合,所以才有了現在可以看見的中文,但是在進行編碼的時候如果要想正確顯示出內容則一定需要有解碼,所以編碼和解碼肯定要採用統一的標準,那麼如果不統一的時候就會出現亂碼。
那麼在實際的開發之中對於常用的編碼有如下幾種:

  • GBK/GB2312
    :國標編碼,可以描述中文資訊,其中GB2312只描述簡體中文,而GBK包含有簡體中文與繁體中文;
  • ISO8859-1:國際通用編碼,可以用其描述所有的文字資訊,如果是象形文字則需要進行編碼處理;
  • UNICODE:採用十六進位制的方式儲存,可以描述所有的文字資訊;
  • UTF:象形文字部分使用十六進位制編碼,而普通的字母採用的是ISO8859-1編碼,它的優勢在於適合快速的傳輸,節約頻寬,也就成為了我們在開發之中首選的編碼,主要使用UTF-8編碼

如果要想知道當前系統中支援的編碼規則,則可以採用如下程式碼列出全部的本機屬性
範例:列出本機屬性

     public
static void main(String[] args) { System.getProperties().list(System.out); }

部分屬性資訊:

【檔案路徑分割符】file.separator=\
【檔案預設編碼】file.encoding=UTF-8

也就是說本系統預設設定的編碼就是UTF-8

範例:預設編碼編寫程式

    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello"
+ File.separator + "mldn.txt");// 指定要操作檔案的路徑 OutputStream out = new FileOutputStream(file); out.write("中華人民共和國萬歲".getBytes()); out.close(); }

此時為預設的處理操作,不設定編碼的時候就將採用預設的編碼方式進行。

範例:強制性設定編碼編寫程式

    public static void main(String[] args) throws Exception {
        File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作檔案的路徑
        OutputStream out = new FileOutputStream(file);
        out.write("中華人民共和國萬歲".getBytes("ISO-8859-1"));
        out.close();
  }

專案中出現的亂碼問題就是編碼和解碼標準不統一,而最好的解決亂碼的方式,所有的編碼都使用UTF-8。

2. 記憶體操作流

在之前使用的都是檔案操作流,檔案操作流的特點,程式利用InputStream讀取檔案內容,而後程式利用OutputStream向檔案輸出內容,所有的操作都是以檔案為終端的:
這裡寫圖片描述

假設現在需要實現IO操作,可是又不希望產生檔案(臨時檔案),則就可以以記憶體為終端進行處理,這個時候的流程:
這裡寫圖片描述
在Java裡面提供有兩類的記憶體操作流

  • 位元組記憶體操作流:ByteArrayOutputStream、ByteArrayInputStream
  • 字元記憶體操作流:CharArrayWriter、CharArrayReader

類結構如下:
ByteArrayOutputStream
這裡寫圖片描述
ByteArrayInputStream
這裡寫圖片描述
CharArrayWriter
這裡寫圖片描述
CharArrayReader
這裡寫圖片描述

下面以ByteArrayOutputStream和ByteArrayInputStream類為主進行記憶體的使用分析,首先來分析各自的構造方法:

  • ByteArrayInputStream構造:public ByteArrayInputStream(byte[] buf)
  • ByteArrayOutputStream構造:public ByteArrayOutputStream()

在ByteArrayOutputStream類裡面有一個重要的方法,這個方法可以獲取全部儲存在記憶體流中的資料資訊,該方法為:

  • 獲取方法:public byte[] toByteArray()
  • 使用字串的形式獲取方法:public String toString()

範例:利用記憶體流實現一個小寫字母轉大寫字母的操作

    public static void main(String[] args) throws Exception {
     String str = "www.sunlh.vip";// 小寫字母
     InputStream input = new ByteArrayInputStream(str.getBytes());// 將資料儲存到記憶體流
     OutputStream output = new ByteArrayOutputStream();// 讀取記憶體中的資料
     int data = 0;
     while ((data = input.read()) != -1) {// 每一次讀取一個位元組
                output.write(Character.toUpperCase(data));// 儲存資料
           }
     System.out.println(output);
     input.close();
     output.close();
    }

如果現在不希望只是以字串的形式返回,因為可能存放的是其他二進位制的資料,那麼此時就可以利用ByteArrayOutputStream子類的擴充套件功能獲取全部資料

    public static void main(String[] args) throws Exception {
     String str = "www.sunlh.vip";// 小寫字母
     InputStream input = new ByteArrayInputStream(str.getBytes());// 將資料儲存到記憶體流
     // 必須使用子類來呼叫子類自己的擴充套件方法
     ByteArrayOutputStream output = new ByteArrayOutputStream();// 讀取記憶體中的資料
     int data = 0;
     while ((data = input.read()) != -1) {// 每一次讀取一個位元組
                output.write(Character.toUpperCase(data));// 儲存資料
           }
     byte [] result = output.toByteArray();// 獲取全部資料
     System.out.println(new String(result));// 自己處理位元組資料
     input.close();
     output.close();
    }

在最初的時候可以利用ByteArrayOutputStream實現大規模文字檔案的讀取。

3. 管道流

管道流主要的功能是實現兩個執行緒之間的管道操作
這裡寫圖片描述
對於管道流也是分為兩類:

  • 位元組管道流:PipedInputStream、PipedOutputStream
    • 連線處理:public void connect(PipedInputStream snk) throws IOException
  • 字元管道流:PipedReader、PipedWriter
    • 連線處理:public void connect(PipedReader src) throws IOException

類結構圖:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

範例:實現管道操作

package IOKnowledge;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Test2 {
    public static void main(String[] args) throws Exception {
     SendThread send = new SendThread();
     ReceiveThread receive = new ReceiveThread();
     send.getOutput().connect(receive.getInput());// 進行管道連線
     new Thread(send, "訊息傳送執行緒").start();
     new Thread(receive, "訊息接收執行緒").start();
    }
}
class SendThread implements Runnable {
     private PipedOutputStream output;// 管道輸出流
     public SendThread() {
           this.output = new PipedOutputStream();// 例項化管道輸出流
     }
     @Override
     public void run() {
           for (int i = 0; i < 10; i++) {
                try {// 利用管道實現資料的傳送處理
                      this.output.write(("【第" + (i + 1) + "次傳送\"" + Thread.currentThread().getName() + "\"資訊】www.sunlh.vip\n").getBytes());
                } catch (IOException e) {
                      e.printStackTrace();
                }
           }
           try {
                this.output.close();
           } catch (IOException e) {
                e.printStackTrace();
           }
     }
     public PipedOutputStream getOutput() {
           return output;
     }
}
class ReceiveThread implements Runnable {
     private PipedInputStream input;
     public ReceiveThread() {
           this.input = new PipedInputStream();
     }

     @Override
     public void run() {
           byte [] data = new byte[1024];
           ByteArrayOutputStream pos = new ByteArrayOutputStream();// 所有資料儲存到記憶體輸出流
           int len = 0;
           try {
                while ((len = this.input.read(data)) != -1) {
                      pos.write(data,0,len);
                }
                System.out.println("接收訊息:\n" + new String(pos.toByteArray()));
                pos.close();
           } catch (IOException e1) {
                e1.printStackTrace();
           }
           try {
                this.input.close();
           } catch (IOException e) {
                e.printStackTrace();
           }
     }

     public PipedInputStream getInput() {
           return input;
     }

}

管道就類似於醫院打點滴效果,一個只是負責傳送,一個負責接收,中間靠一個管道連線。

4. RandomAccessFile隨機儲存類

對於檔案內容的處理操作主要是通過InputStream(Reader)、OutputStream(Writer)來實現,但是利用這些類實現內容讀取只能夠將資料一部分一部分讀取進來,如果說現在有這樣一種要求。

現在給了你一個非常龐大的檔案,這個檔案的大小有20G,如果此時按照傳統IO操作進行讀取和分析根本不可能完成,所以在這種情況下在java.io包下面有一個RandomAccessFile類,這個類可以實現檔案的跳躍式的讀取,可以只讀取中間的部分內容(前提:需要有一個完善的儲存形式),資料的儲存位數要確定好(類似資料庫欄位給定長度)。
這裡寫圖片描述
現在如果要讀取李四的資訊,只需要從24位開始讀取就可以

RandomAccessFile類裡面定義有如下的操作方法:

  • 構造方法:public RandomAccessFile(File file, String mode) throws FileNotFoundException
    • 常用的檔案處理模式::r、rw

範例:實現檔案的儲存

     public static void main(String[] args) throws Exception {
           File file = new File("d:" + File.separator + "hello" + File.separator + "mldn.txt");// 定義操作檔案
           RandomAccessFile raf = new RandomAccessFile(file, "rw");// 讀寫模式
           String [] names = new String[] {"zhangsan", "wangwu   ", "lisi    "};
           int [] ages = new int[] {30, 20, 16};
           for (int x = 0; x < names.length; x++) {
                raf.write(names[x].getBytes());// 寫入字串
                raf.writeInt(ages[x]);
           }
           raf.close();

     }

RandomAccessFile最大的特點是在於資料的讀取處理上,因為所有的資料是按照固定的長度進行儲存,所以讀取的時候就可以進行跳位元組讀取:

  • 向下跳:public int skipBytes(int n) throws IOException;
  • 向回跳:public void seek(long pos) throws IOException

範例:讀取資料

     public static void main(String[] args) throws Exception {
           File file = new File("d:" + File.separator + "hello" + File.separator + "mldn.txt");// 定義操作檔案
           RandomAccessFile raf = new RandomAccessFile(file, "rw");// 讀寫模式
           {// 讀取“李四”的資料,跳過24位
                raf.skipBytes(24);
                byte [] data = new byte[8];
                int len = raf.read(data);
                System.out.println("姓名:" + new String(data, 0, len) + "、年齡:" + raf.readInt());
           }
           {// 讀取“王五”的資料,回跳12位
                raf.seek(12);
                byte [] data = new byte[8];
                int len = raf.read(data);
                System.out.println("姓名:" + new String(data, 0, len) + "、年齡:" + raf.readInt());
           }
           {// 讀取“張三”的資料,回跳頭
                raf.seek(0);
                byte [] data = new byte[8];
                int len = raf.read(data);
                System.out.println("姓名:" + new String(data, 0, len) + "、年齡:" + raf.readInt());
           }
           raf.close();
     }

整體的使用之中由使用者自行定義要讀取的位置,而後按照指定的結構進行資料的讀取。