Java I/O輸入輸出流

編碼問題

String s = "Java 教程";
byte[] byte1 = s.getBytes();   //使用默認編碼。 
byte[] byte2 = s.getBytes("utf-8"); //顯示指定編碼格式爲 utf-8
//把字節(轉換成int)以16進制方式顯示
// GBK編碼 中文佔2個字節,英文佔1個字節
// UTF-8 編碼,中文佔用3個字節,英文佔用1個字節
// Java 是雙字節編碼 UTF-16be
for( byte b : byte1) {
    System.out.print(Integer.toHexString(b & 0xff) + "  ");
}

當你的字節序列是某種編碼時,在把字節序列變成字符串時,也需要使用該種編碼,否則會亂碼。

String str = new String(b,"utf-16be");

文本文件就是字節序列,可以是任意編碼的字節序列。
如果在中文機器上直接創建的文本文件,那麼文本文件只認識ansi編碼。
複製的還是按照原來的編碼格式

File 類常用API

Java中, java.io.FIle 類用於表示文件(目錄),只用於表示文件(目錄)的信息(名稱、大小),不能用於文件訪問。

File file = new File("E:\\java\\javaTest"); // 一個目錄
File file2 = new File("E://java//test1.java"); // 一個文件
File file3 = new File("E://java","test2.java") //一個文件

if(!file.exists()) {
    file.mkdir(); //創建一個目錄
    file.createNewFile(); //創建一個文件
} else {
    file.delete();
}
if(file.isDirectory()) { }

if(file2.isFile()) { }
//常用的File對象API
file.getAbsolutePath();
file.getName();
file.getParent();

File常用操作:遍歷,過濾

//列出指定目錄下的目錄和文件
String[] filenames = dir.list(); //列出當前目錄下的子目錄和文件,返回字符串數組。不包含子目錄下的內容

// 需要遍歷目錄下的內容時,就要構造File對象做遞歸操作。
//File提供了直接返回對象的方法。
File[] files = dir.listFiles(); //返回直接子目錄(文件)的對象
if(files != null && files.length > 0) {
    for (file : files) {
        if(file.isDirectory()) {
            //遞歸
        }
        else {
            //直接返回
        }
    }
}

RandomAccessFile的使用

RandomAcceseeFile 是Java 提供的對文件內容的訪問類。
既可以讀取文件,也可以寫文件。並且可以隨機訪問文件,可以訪問文件的任意位置

java 文件模型

  • 在硬盤上的文件是按 字節 存儲的,是數據的集合;

打開文件

  • 兩種模式: “rw”“r”。
  • RandomAccessFile raf = new RandomAccessFile(file, “rw”);
  • 文件指針: 打開文件時指針在開頭 pointer = 0;

寫方法

  • raf.write(int) ; //只寫一個字節(後8位),同時指針指向下一個字節的位置,準備再次寫入

讀方法

  • int b = raf.read(); //只讀一個字節

讀寫文件後一定要關閉文件,否則可能出現不可預測的錯誤。

File file  = new File("001.dat") {
    if(!file.exist()) {
        file.createNewFile();
    }
    RandomAccessFile raf = new RandomAccessFile(file,"rw");
    int i = 0x7fffffff;
    //用write方法沒戲只寫一個字節。int4個字節,需要寫4次
    raf.write(i >>> 24); 高8位
    raf.write(i >>> 16);
    raf.write(i >>> 8);

    //或是用 writeInt 直接寫一個int
    raf.writeInt(i);

    //write 可以直接寫一個字節數組
    String s = "中";
    byte[] gbk = s.getBytes("gbk");
    raf.write(gbk); //寫入一個字節數組

    //讀文件,必須把指針移到頭部
    raf.seek(0);
    //一次性讀取文件中的所有內容到字節數組中
    byte[] buf = new Byte[(int)raf.length()];
    raf.read(buf);

IO流(輸入流,輸出流)

字節流的使用

  1. InputStream、OutputStream
    InputStream 抽象了應用程序讀取數據的方式
    OutputStream 抽象了應用程序寫出數據的方式
  2. EOF == end of file 讀到-1表示讀到文件結尾
  3. 輸入流基本方法:(注意,鍵盤是輸入流。 從鍵盤讀入數據寫到文件中)

    • int b = in.read(); // 讀取一個字節無符號填充int低8位。 -1 是 EOF
    • `int read(byte[] buf); //讀取數據填充到字節數組buf
    • int read(byte[] buf, int start, int size); //讀取數據到字節數組buf,從buf 的start位置開始存放size長度的數據
  4. 輸出流基本方法

    • out.write(int b); // 寫出一個byte到流中,b的低8位
    • out.write(byte[] buf); //將buf字節數組寫入到流
    • out.write(byte[] buf, int start, int size); //字節數組buf從start位置開始寫size長度的字節到流
  5. FileInputStream 繼承InputStream,具體實現了文件上讀取數據。

    • FileInputStream in = new FileInputStream("file");
    • int b = in.read();
    • Byte[] buf = new byte[20*1024];
    • int bytes = in.read(buf,0,buf.length);//從in中批量讀取字節,放入到buf這個字節數組中,從第0個位置開始放,最多放buf.length個字節。返回的是讀到的字節的個數
  6. FileOutputStream 繼承了OutputStream,具體實現了向文件中寫出byte數據的方法

    • //如果文件不存在,直接創建。如果存在,刪除後創建
    • FileOutputStream out = new FileOutputStream(file);

    • //true 表示 append,在文件後面追加.

    • //如果文件不存在,直接創建。如果文件存在,直接在文件末尾寫入
    • FileOutputStream out = new FileOutputStream(file,true);
  7. DataInputStream 和 DateOutputStream 數據輸入輸出流
    是對“流”功能的擴展,可以更加方便的讀取int,long,字符等類型數據

    • writeInt()/ writeDouble()/ writeChar()
    • //有OutputStream嵌套生成
    • FileOutputStream fout = new FileOutputStream("E:\\001.txt");
    • DataOutputStream dos = new DataOutputStream(fout);
    • dos.writeInt(10);
    • dos.writeUTF("中國");// 採用utf-8編碼寫出,每個漢字3個字節
    • dos.writeChars("中國"); //採用utf-16be 編碼,每個漢字2個字節
  8. BufferedInputStream & BufferedOutputStream 帶緩衝區的字節流操作。
    一般打開文件進行寫入或讀取操作時,都會加上緩衝,這種流模式提高了IO的性能。

FileOutoutStream ---> write()方法:類似一滴一滴的把水“轉移”過去
DataOutputStream ---> writeXxx方法:較方便,類似一瓢一瓢的把水‘轉移’過去
BufferedOutputStream ---> write() 方法: 更方便,類似把水先一瓢一瓢放到桶中,在從桶中導入水缸中。
FileOutoutStream ---> write(buf,start,size)方法: 速度最快
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
int c;
while((c = bis.read()) != -1) {
    bos.write(c);
    bos.flush(); //一定要刷新緩衝區,將數據寫入文件中
}
bis.close();
bos.close();

字符流的使用

認識文本和文本文件

  • 文本: Java 文本(char)是16位無符號整數,是字符的unicode編碼(雙字節編碼)
  • 文件: 是按照byte存儲的數據序列
  • 文本文件: 是文本(char)序列按照某種編碼方案(utf-8,utf-16be, gbk) 序列化爲byte的存儲結構

字符流輸入輸出流(Reader, Writer) 操作文本文件

字符的處理: 一次處理一個字符
字符的底層仍然是基本的字節序列

字符流的基本實現

  • InputStreamReader : 完成byte流解析爲char流,按照編碼解析
  • OuputStreamWriter : 提供char流發到byte流,按照編碼解析
FileInputStream in = new FileInputStram("001.txt");
InputStreamReader isr = new InputStreamReader(in,"gbk"); //默認按照gbk編碼
int c = isr.read();
char[] buffer = new char[8 * 1024];
c = isr.read(buffer, 0, buffer.length); //批量讀取字符,放入buffer字符數組,從0位置開始放置,最多放置length個,返回讀取到的字符個數
String s = new String(buffer,0,c); //把字符存儲到字符串中

另外一種實現: FileReader&FileWriter

注意: 沒有編碼參數
需要設置編碼時,就用InputStreamReader&OutputStreamWriter

FileReader fr = new FileReader("001.txt");
FileWriter fw = new FileWriter("002.txt");
//FileWriter fw = new FileWriter("002.txt","true");//文件後面追加
while((int c = fr.read(buffer,0,buffer,length) != -1) {
    fw.write(buffer,0,c);
    fw.flush();
}
fr.close();
fw.close();

字符流的過濾器 BufferedReader

BufferedReader 除了基本的讀取外,可以一次讀取一行
BufferedWriter/ PrintWriter 可以一次寫一行

BufferedReader br = new BufferedReader(
    new InputStreamReader(
        new FileInputStream("001.txt"[,"utf-8"])));
//或是用FileReader()
//BufferedReader br = new BufferedWriter(FileReader("001.txt"));

BufferedWriter bw = new BufferedWriter(
    new OutputStreamWriter(
        new FileInputStream("002.txt"[,"utf-8"]));
String line;
//一次讀取一行,不能識別換行,返回是字符串
while(line = br.readLine() != null) {
    bw.write(line);
    //單獨寫出換行操作
    bw.newLine(); //換行操作
    bw.flush();
}
br.close();
bw.close();

//  可以直接用PrintWriter 代替 BufferedWriter
// PrintWriter pw = new PrintWriter("003.txt");
// PrintWriter pw1 = new PrintWriter(outputStream, boolean autoFlush); //自動刷新
// pw.print(line); //輸出一行,沒有換行
// pw.println(line); // 輸出一行,有換行

對象的序列化和反序列化

對象的序列化&反序列化

  • 對象的序列化,就是將Object 轉換成byte序列
  • 對象的反序列化,就是將byte序列轉換成Object

  • 序列化流:ObjectOutputStream, 是過濾流 -> writeObject 方法

  • 反序列化流:ObjectInputStream, -> readObject方法
序列化接口 (Serializable)

對象必須實現序列化接口,才能進行序列化,否則會出現異常;
序列化接口沒有任何實現方法,只是一個標準。

ObjectOutputStream oos = new ObjectOutputStream(
    new FileOutputStream("001.txt"));
ObjectInputStream ois = new ObjectInputStream( 
    new FileInputStream("002.txt"));
Student stu = new Student("1001","張三"); // Student 類要實現Serializable 接口,否則出現異常
oos.writeObject(stu);
Student stu2 = (Student)ois.readObject();
oos.flush();
oos.close();
ois.close();

關鍵字: transient

transient 修飾的元素不會進行JVM默認的序列化
但是可以自己定義給元素的序列化。

Student 類,其中age有transient修飾

自己定義序列化和反序列化方法

例如在ArrayLis中,底層的數組是不一定會被填滿的。
所以ArrayList 自己定義數組元素的序列化和反序列化,這樣可以捨去空元素,提高性能。

序列化中子類和父類構造函數的調用

父類實現了序列化接口,其子類都能進行序列化。
定義的3個foo類
定義的3個類

序列化時遞歸調用父類構造函數
序列化時遞歸調用父類構造函數

反序列化操作1
反序列化操作1

反序列化操作1 結果: 只打印出了對象,沒有打印出構造函數調用結果

反序列化操作1 結果: 只打印出了對象,沒有打印出構造函數調用結果

另外3個類
bar 3個類

序列化時會打印3個構造函數。

反序列化時會打印父類的構造函數:
反序列化操作 bar 類
反序列化操作 bar 類

說明 對子類對象進行反序列化操作時,如果其父類沒有實現序列化接口,那麼其父類的構造函數會被調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章