主頁 > 後端開發 > "萬字" Java I/O 詳解

"萬字" Java I/O 詳解

2023-02-12 07:09:58 後端開發

Java I/O流講解

每博一文案

誰讓你讀了這么多書,又知道了雙水村以外還有一個大世界,如果從小你就在這個天地里,日出而作,日落而息,
那你現在就會和眾鄉親抱同一理想:經過幾年的辛勞,像大哥一樣娶個滿意的媳婦,生個胖兒子,加上你的體魄,
會成為一名出色的莊稼人,不幸的是,你知道的太多了,思考的太多了,因此才有了,這種不能為周圍人所理解的苦惱,
                                          —————— 《平凡的世界》
人生是這樣的不可預測,沒有永恒的痛苦,也沒有永恒的幸福,生活就像流水一般,
有時是那么平展,有時又是那么曲折,
世界上有些人因為忙而感到生活的沉重,也有些人因為閑而活得壓抑,人啊,都有自己一本難念的經;
可是不同處境的人又很難理解別人的苦處,
細想過來,每個人的生活也同樣是一個世界,即使是最平方的人,也要為他那個世界的存在而戰斗,
                                          ——————  《平凡的世界》

@

目錄
  • Java I/O流講解
    • 每博一文案
    • 1. File 類
      • 1.1 File 類中:構造器
      • 1.2 File 類中:路徑分隔符
      • 1.3 File 類中:常用方法
      • 1.4 實用案例:
    • 2. I/O 流的概述
      • 2.1 I/O的分類和體系結構
        • 2.1.1 輸入流 和 輸出流
        • 2.1.2 位元組流 和 字符流
        • 2.1.3 節點流 和 處理流(包裝流)
        • 2.1.4 流的概念模型
        • 2.1.5 I/O 的體系結構
      • 2.2 字符流
        • 2.2.1 java.io.FileReader 字符輸入流
        • 2.2.2 java.io.FileWriter 字符輸出流
        • 2.2.3 實體文本檔案的拷貝
      • 2.3 位元組流
        • 2.3.1 FileInputStream 位元組輸入流
        • 2.3.2 FileOutputStream 位元組輸出流
        • 2.3.3 實體圖片檔案的拷貝
        • 2.3.4 實體對圖片的簡單加密
      • 3.1 緩沖流
        • 3.1.1 BufferedReader (字符輸入緩沖流) / BufferedWriter (字符輸出緩沖流)
        • 3.1.2 BufferedInputStream(位元組輸入緩沖流) / BufferedOutputStream (位元組輸出緩沖流)
      • 4.1 轉換流
      • 4.2 標準輸入\輸出流
      • 4.3 資料流
      • 4.4 物件流 (序列化 ObjectOutputStream,反序列化 ObjectInputStream)
          • 4.4.1 物件的序列化
          • 4.4.2 物件的反序列化
        • 4.4.3 不顯式定義 serialVersionUID 值的問題
          • 4.4.3.1  設定不被序列化的型別
    • 5. 實用案例:
    • 6. 檔案 I/O(nio.2)
    • 7. 總結:
    • 10. 最后:

1. File 類

java.io.File 類:檔案和檔案目錄路徑的抽象表示形式,與平臺無關

File 能新建,洗掉,重命名檔案和目錄,但File 不能訪問檔案內容本身,如果需要訪問檔案內容本身,則需要使用 輸入/輸出 流,

想要在Java 程式中表示一個真實存在的檔案或目錄,那么必須有一個 File 物件,但是 Java 程式中的一個 File 物件,可能沒有一個真實存在的檔案或目錄,

File 物件可以作為引數傳遞給流的構造器,

1.1 File 類中:構造器

public File(String pathname);  // 過將給定路徑名字串轉換為抽象路徑名來創建一個新 File 實體,如果給定字串是空字串,那么結果是空抽象路徑名
public File(String parent,String child);  // 根據 parent 路徑名字串和 child 路徑名字串創建一個新 File 實體,以parent為父路徑,child為子路徑創建File物件
public File(File parent, String child); // 根據 parent 抽象路徑名和 child 路徑名字串創建一個新 File 實體,根據一個父File物件和子檔案路徑創建File物件
// 路徑可以是絕對路徑,也可以是相對路徑
  • 絕對路徑: 是一個固定的路徑,從盤符開始,
  • 相對路徑: 是相對于某個位置開始,IDEA中默認相對路徑是從**Project**專案(路徑)下和同級的 **src** 的路徑開始的,注意不是模塊開始的**Module** ,如下圖所示:src 和 Module 模塊是同級的,

1.2 File 類中:路徑分隔符

路徑中的每級目錄之間用一個路徑分隔符隔開,

路徑分隔符和系統有關:

  • windows和DOS系統默認使用“\”來表示,需要注意的是在 java 中 **"\"** 具有轉義的意思,所以想要表示真正的 “\” 需要兩個 **"\\"** 來轉義回來表示一個斜桿,
  • UNIX和URL使用“/”來表示,

Java程式支持跨平臺運行,因此路徑分隔符要慎用,

為了解決這個隱患,File類提供了一個常量:

public static final String separator,// 根據作業系統,動態的提供分隔符,
File file1 = new File("E:\\Test\\info.txt");

File file2 = new File("E:" + File.separator + "Test" + File.separator + "info.txt");

File file3 = new File("E:/Test");

// 這三者表示的路徑是一樣的,只是表示方式不同而已,

舉例:

package blogs.blog9;


import java.io.File;

public class FileTest {

    /**
     * File 構造器的使用
     */
    public static void main(String[] args) {
        // 構造器一:
        // 絕對路徑: 帶盤符
        File file = new File("E:\\Java\\JavaRebuilt\\src\\blogs\\blog9"); // 雙右斜桿表示一個 \ (轉義)
        // 相對路徑: IDEA默認是Project的根目錄,不是模塊Module的根目錄,
        // 也可以使用:左斜桿表示路徑分隔符
        System.out.println(file);
        File file2 = new File("src/blogs/blog9"); // 這里是在src的包下(src 和 Module 模塊是同級的)
        System.out.println(file2);

        // 構造器二:
        // 第一個引數是第二個引數的父路徑,第二個引數是子路徑
        File file3 = new File("E:\\Java\\JavaRebuilt\\src\\blogs","blog9");
        System.out.println(file3);

        // 構造器三:
        // 第一個引數是 File 類物件(這里是第二個引數的父路徑的File物件),第二個引數是子路徑
        File file4 = new File(file3,"blog9");
        System.out.println(file4);

    }
}

1.3 File 類中:常用方法

獲取檔案屬性的資訊的方法:

  • getAbsoluteFile() : 回傳此File物件中路徑的絕對路徑,回傳的是 File 物件
public File getAbsoluteFile(); // 回傳此抽象路徑名的絕對路徑名形式,
  • getAbsolutePath() : 回傳此抽象路徑名的絕對路徑名字串
public String getAbsolutePath(); // 回傳此抽象路徑名的絕對路徑名字串
  • getPath() : 回傳此抽象路徑名以字串的形式,
public String getPath(); // 回傳此抽象路徑名
  • getName() : 獲取名稱
public String getName(); // 回傳由此抽象路徑名表示的檔案或目錄的名稱,該名稱是路徑名名稱序列中的最后一個名稱,如果路徑名名稱序列為空,則回傳空字串
  • getParent() : 獲取上層檔案目錄路徑(也就是父路徑),若無,回傳null
public String getParent();  // 回傳此抽象路徑名父目錄的路徑名字串;如果此路徑名沒有指定父目錄,則回傳 null,
  • length() : 回傳檔案長度即(位元組數)
public long length();  // 獲取檔案長度(即:位元組數),不能獲取目錄的長度
  • lastModified() : 獲取最后一次的修改時間,毫秒值,
public long lastModified();  // 獲取最后一次的修改時間,毫秒值
  • list() : 獲取指定目錄下的所有檔案或者檔案目錄的名稱陣列
public String[] list();  // 回傳一個字串陣列,這些字串指定此抽象路徑名表示的目錄中的檔案和目錄,
  • listFiles() : 獲取指定目錄下的所有檔案或者檔案目錄的 File 陣列
public File[] listFiles();  // 回傳一個抽象路徑名陣列,這些路徑名表示此抽象路徑名表示的目錄中的檔案,

舉例:

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src\\blog9\\hello.txt"); // 注意轉義以及檔案后綴
        File file2 = new File("src/blog9/hello3.txt"); // 左斜桿也是可以的

        String absolutePath = file.getAbsolutePath();  // 回傳絕對路徑,以String的形式回傳
        System.out.println(absolutePath);
        File absoluteFile = file.getAbsoluteFile();  // 回傳絕對路徑,以File 物件的形式回傳
        System.out.println();
        System.out.println(file.getPath());  // 回傳此路徑名/目錄名
        System.out.println(file.getName()); // 回傳該檔案名/目錄名
        System.out.println(file.getParent()); // 回傳該上層檔案/目錄名稱
        System.out.println(file.length());  // 回傳檔案長度即檔案的大小(位元組)
        long l = file.lastModified();  // 回傳該檔案的最后一次修改的時間值(毫秒值)時間戳
        // 將時間戳轉換為Date,再轉換為 指定格式的字串
        Date date = new Date(l);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:ss:mm SSS");
        String format = simpleDateFormat.format(date);
        System.out.println(format);

    }
}

舉例:

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FileTest {

    /**
     * File 檔案目錄
     */
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9"); // 也可以使用 左斜桿
        String[] list = file.list();  // 回傳獲取指定目錄下的所有檔案或者檔案目錄的名稱陣列
        for (String s : list) {
            System.out.println(s);
        }

        File[] files = file.listFiles();
        for(File f : files) {
            System.out.println(f);
        }
    }
}


File類的重命名功能

  • renameTo(File dest) : 把檔案重命名為指定的檔案路徑,換句話說:就是剪切附加對檔案的重命名 的意思,
public boolean renameTo(File dest);  // 重新命名此抽象路徑名表示的檔案,

注意: 這里的剪切效果,有一定的要求:就是比如:file.renameTo(dest) 想要將 file 剪切到 dest 位置路徑上,要保證 file 剪切的檔案實際在硬碟中存在,并且 dest 不能在硬碟檔案中存在(僅僅當一個路徑),如果不滿足會失敗,回傳 false

舉例:

import java.io.File;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src\\blogs\\blog9\\hello.txt");
        File dest = new File("E:\\臨時檔案\\temp\\test.txt");
        boolean b = file.renameTo(dest);  // 將file檔案剪切到 dest 中并重命名
        System.out.println(b);

    }
}

失敗:原因是:dest 中的 test.txt 是在硬碟中實際存在的,

將test.txt去了就沒事了 ,再重新剪切,就可以了,

File 類的判斷功能

  • isDirectory() : 判斷是否是檔案目錄
public boolean isDirectory(); // 測驗此抽象路徑名表示的檔案是否是一個目錄,
  • isFile()  :判斷是否是檔案
public boolean isFile();  // 當且僅當此抽象路徑名表示的檔案存在且 是一個標準檔案時,回傳 true;否則回傳 false
  • exists() : 判斷該檔案/目錄是否存在
public boolean exists();  // 測驗此抽象路徑名表示的檔案或目錄是否存在
  • canRead() : 判斷該檔案是否可讀的
public boolean canRead(); // 當且僅當此抽象路徑名指定的檔案存在且 可被應用程式讀取時,回傳 true;否則回傳 false
  • canWrite() : 判斷該檔案是否可寫的
public boolean canWrite(); // 當且僅當檔案系統實際包含此抽象路徑名表示的檔案且 允許應用程式對該檔案進行寫入時,回傳 true;否則回傳 false.
  • isHidden() :  判斷該檔案是否隱藏的
public boolean isHidden(); // 當且僅當此抽象路徑名表示的檔案根據底層平臺約定是隱藏檔案時,回傳 true

舉例:

import java.io.File;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src\\blogs\\blog9\\hello.txt"); // 注意轉義以及檔案后綴(該檔案實際存在的)
        File file2 = new File("src/blogs/blog9/hello3.txt"); // 左斜桿也是可以的 (該檔案不存在的)

        System.out.println(file.isDirectory()); // 判斷是否是檔案目錄
        System.out.println(file.isFile());  // 判斷是否為檔案
        System.out.println(file.exists()); // 判斷該檔案/目錄是否實際存在
        System.out.println(file.canRead());  // 判斷該我呢見是否可讀的
        System.out.println(file.canWrite()); // 判斷該檔案是否是可寫的
        System.out.println(file.isHidden()); // 判斷該檔案是否隱藏的

        System.out.println("*************************** file2 *************************");
        System.out.println(file2.isDirectory()); // 判斷是否是檔案目錄
        System.out.println(file2.isFile());  // 判斷是否為檔案
        System.out.println(file2.exists()); // 判斷該檔案/目錄是否實際存在
        System.out.println(file2.canRead());  // 判斷該我呢見是否可讀的
        System.out.println(file2.canWrite()); // 判斷該檔案是否是可寫的
        System.out.println(file2.isHidden()); // 判斷該檔案是否隱藏的
    }
}

File 類的創建功能

  • **createNewFile() ** : 創建檔案,若檔案存在,則不創建,回傳false
public boolean createNewFile() throws IOException // 如果指定的檔案不存在并成功地創建,則回傳 true;如果指定的檔案已經存在,則回傳 false
  • mkdirs() : 創建檔案目錄,創建檔案目錄,如果上層檔案目錄不存在,一并創建
public boolean mkdirs(); // 創建此抽象路徑名指定的目錄,包括所有必需但不存在的父目錄,注意,此操作失敗時也可能已經成功地創建了一部分必需的父目錄,
  • mkdir() : 創建檔案目錄,如果此檔案目錄存在,就不創建了,如果此檔案目錄的上層目錄不存在,也不創建,
public boolean mkdir(); // 創建此抽象路徑名指定的目錄,

注意事項:如果你創建檔案或者檔案目錄沒有寫盤符路徑,那么,默認在專案路徑下,

舉例:

import java.io.File;
import java.io.IOException;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9/hello.txt");

        boolean b = false;
        try {
            b = file.createNewFile();  // 檔案存在不創建,不存在檔案創建
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(b);
    }
}

創建目錄: 使用 mkdir

import java.io.File;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9/test/test2/");
        boolean b = file.mkdir();  // 如果對應的 test2目錄的上級目錄test不存在,則目錄都不創建
        System.out.println(b);
    }
}

使用 mkdirs()

import java.io.File;
import java.io.IOException;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9/test/test2/");
        boolean b = file.mkdirs();  // 如果對應的 test2目錄的上級目錄test不存在,則目錄一并都創建
        System.out.println(b);
    }
}

File類的洗掉功能:

  • delete()  : 洗掉檔案或者檔案夾
public boolean delete(); // 洗掉此抽象路徑名表示的檔案或目錄,如果此路徑名表示一個目錄,則該目錄必須為空才能洗掉,

洗掉注意事項:Java中的洗掉不走回收站,要洗掉一個檔案目錄,請注意該檔案目錄內不能包含檔案或者檔案目錄,如果含有無法洗掉的,

舉例:

import java.io.File;

public class FileTest {
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9/test");
        boolean b = file.delete();  // test 目錄下不能有檔案/目錄,有的話無法洗掉
        System.out.println(b);
    }
}

小結

1.4 實用案例:

判斷指定目錄下是否有后綴名為.jpg的檔案,如果有,就輸出該檔案名稱

package com.atguigu.exer2;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;

import org.junit.Test;
/**
判斷指定目錄下是否有后綴名為.jpg的檔案,如果有,就輸出該檔案名稱
 */
public class FindJPGFileTest {

	@Test
	public void test1(){
		File srcFile = new File("d:\\code");
		
		String[] fileNames = srcFile.list();
		for(String fileName : fileNames){
			if(fileName.endsWith(".jpg")){
				System.out.println(fileName);
			}
		}
	}
    
	@Test
	public void test2(){
		File srcFile = new File("d:\\code");
		
		File[] listFiles = srcFile.listFiles();
		for(File file : listFiles){
			if(file.getName().endsWith(".jpg")){
				System.out.println(file.getAbsolutePath());
			}
		}
	}
    
	/*
	 * File類提供了兩個檔案過濾器方法
	 * public String[] list(FilenameFilter filter)
	 * public File[] listFiles(FileFilter filter)
	 */
	@Test
	public void test3(){
		File srcFile = new File("d:\\code");
		
		File[] subFiles = srcFile.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".jpg");
			}
		});
		
		for(File file : subFiles){
			System.out.println(file.getAbsolutePath());
		}
	}
	
}

遍歷指定目錄所有檔案名稱,包括子檔案目錄中的檔案,
拓展1:并計算指定目錄占用空間的大小
拓展2:洗掉指定檔案目錄及其下的所有檔案

package com.atguigu.exer2;

import java.io.File;
/**
 * 3. 遍歷指定目錄所有檔案名稱,包括子檔案目錄中的檔案,
	拓展1:并計算指定目錄占用空間的大小
	拓展2:洗掉指定檔案目錄及其下的所有檔案

 * @author shkstart 郵箱:[email protected]
 * @version  創建時間:2019年2月23日  上午1:55:31
 *
 */
public class ListFilesTest {

	public static void main(String[] args) {
		// 遞回:檔案目錄
		/** 列印出指定目錄所有檔案名稱,包括子檔案目錄中的檔案 */

		// 1.創建目錄物件
		File dir = new File("E:\\teach\\01_javaSE\\_尚硅谷Java編程語言\\3_軟體");

		// 2.列印目錄的子檔案
		printSubFile(dir);
	}

	public static void printSubFile(File dir) {
		// 列印目錄的子檔案
		File[] subfiles = dir.listFiles();

		for (File f : subfiles) {
			if (f.isDirectory()) {// 檔案目錄
				printSubFile(f);
			} else {// 檔案
				System.out.println(f.getAbsolutePath());
			}

		}
	}

	// 方式二:回圈實作
	// 列出file目錄的下級內容,僅列出一級的話
	// 使用File類的String[] list()比較簡單
	public void listSubFiles(File file) {
		if (file.isDirectory()) {
			String[] all = file.list();
			for (String s : all) {
				System.out.println(s);
			}
		} else {
			System.out.println(file + "是檔案!");
		}
	}

	// 列出file目錄的下級,如果它的下級還是目錄,接著列出下級的下級,依次類推
	// 建議使用File類的File[] listFiles()
	public void listAllSubFiles(File file) {
		if (file.isFile()) {
			System.out.println(file);
		} else {
			File[] all = file.listFiles();
			// 如果all[i]是檔案,直接列印
			// 如果all[i]是目錄,接著再獲取它的下一級
			for (File f : all) {
				listAllSubFiles(f);// 遞回呼叫:自己呼叫自己就叫遞回
			}
		}
	}

	// 拓展1:求指定目錄所在空間的大小
	// 求任意一個目錄的總大小
	public long getDirectorySize(File file) {
		// file是檔案,那么直接回傳file.length()
		// file是目錄,把它的下一級的所有大小加起來就是它的總大小
		long size = 0;
		if (file.isFile()) {
			size += file.length();
		} else {
			File[] all = file.listFiles();// 獲取file的下一級
			// 累加all[i]的大小
			for (File f : all) {
				size += getDirectorySize(f);// f的大小;
			}
		}
		return size;
	}

	// 拓展2:洗掉指定的目錄
	public void deleteDirectory(File file) {
		// 如果file是檔案,直接delete
		// 如果file是目錄,先把它的下一級干掉,然后洗掉自己
		if (file.isDirectory()) {
			File[] all = file.listFiles();
			// 回圈洗掉的是file的下一級
			for (File f : all) {// f代表file的每一個下級
				deleteDirectory(f);
			}
		}
		// 洗掉自己
		file.delete();
	}

}

2. I/O 流的概述

一個 I / O流 代表輸入源或輸出目的地,流可以表示許多不同種類的源和目的地,包括磁盤檔案,設備,其他程式和存盤器陣列,

流支持許多不同型別的資料,包括簡單位元組,原始資料型別,本地化字符和物件,一些流簡單地傳遞資料; 其他人以有用的方式操縱和轉換資料,

I/O  其中的 IInput 的縮寫,OOutput 的縮寫,I/O 技術是非常實用的技術,用于處理設備之間的資料傳輸,如讀/寫檔案,網路通訊等,

Java 程式中,對于資料的輸入/輸出操作以 流(stream) 的方式進行,

java.io 包下提供了各種 “流”類和介面,用以獲取不同種類的資料,并通過方法輸入或輸出資料,

無論內部作業如何,所有流都會使用與使用它們的程式相同的簡單模型:

流是一系列資料,程式使用 輸入流 從源中讀取資料:**input** 輸入流以記憶體為參考物件(將檔案中的資料內容寫入到記憶體當中) 以及 **Read** (以檔案為參考物件,將讀取檔案中的資料到記憶體當中),這兩個都是將意思都是一樣的將檔案中的資料讀取出來寫入到記憶體當中,

程式使用 輸出流 將資料寫入目的地,**Output** 輸出流以記憶體為參考物件(將記憶體中的資料內容輸出到硬碟檔案當中) 以及 **Write** (以檔案為參考物件,將記憶體中的資料到寫入到硬碟檔案當中),這兩個都是將意思都是一樣的:將記憶體中的資料輸出到硬碟檔案當中,

2.1 I/O的分類和體系結構

按照不同的分類方式, 可以將流分為不同的型別,

2.1.1 輸入流 和 輸出流

按照流的流向來分, 可以分為輸入流和輸出流:

輸入流: 只能從中讀取資料, 而不能向其寫入資料,
輸出流: 只能向其寫入資料, 而不能從中讀取資料,
此處的輸入、 輸出涉及一個方向問題, 對于如圖 1 所示的資料流向, 資料從記憶體到硬碟, 通常稱為輸出流——也就是說, 這里的輸入、 輸出都是從程式運行所在記憶體的角度來劃分的,

對于如圖 2 所示的資料流向, 資料從服務器通過網路流向客戶端, 在這種情況下, Server 端的記憶體負責將資料輸出到網路里, 因此 Server 端的程式使用輸出流; Client 端的記憶體負責從網路里讀取資料, 因此 Client 端的程式應該使用輸入流,

2.1.2 位元組流 和 字符流

按操作資料單位不同分為:位元組流(8 bit),字符流(16 bit),

位元組流和字符流的用法幾乎完全一樣, 區別在于位元組流和字符流操作的資料單元的不同:位元組流是 8 位的位元組, 而字符流操作的資料單元是 16 位的字符,其中還有一點不同的就是:

  • 字符流:只能讀取操作文本檔案,因為字符流讀取的是檔案中的 char 字符資訊, .c,.java,.c++,.txt 等等這些都是文本檔案不僅僅只是 txt檔案,而特別注意的是 :.wrod 不是文本檔案,wrod中的文字是經過特殊處理的存在一定的規范格式,不是純文本檔案,
  • 位元組流:可以操作任何的檔案,因為位元組流讀取的是二進制資訊,讀取1個位元組byte,等同于一次讀取8個二進制,這種流是萬能的,什么型別的檔案都可以讀取到,因為檔案都是有二進制組成的,包括: 文本檔案,圖片,聲音檔案,

2.1.3 節點流 和 處理流(包裝流)

按流的角色的不同分為:節點流,處理流,

  • 節點流: 所謂的節點流:就是最基本的一個流到底目的的流向,其中的流沒有被其它的流所包含住,直接從資料源或目的地讀寫資料,如下圖所示

  • 處理流: 處理流又稱為包裝流 ,處理流對一個己存在的流進行連接或封裝/包裝, 通過封裝后的流來實作資料讀/寫功能,不直接連接到資料源或目的地,而是“連接”在已存在的流(節點流或處理流)之上,通過對資料的處理為程式提 供更為強大的讀寫功能,
  • 如下圖所示:

注意: 節點流和包裝流是相對的,有時候,相對于不同的流的,一個節點流變成了是另一個流的包裝流,一個包裝流變成了另一個流的節點流 ,如下圖所示:

區分一個流是節點流還是包裝流:大家可以:以誰包裝誰作為參考,被包裝的流就是節點流,包裝了其它的流的就是包裝流,當然注意這是相對的,

2.1.4 流的概念模型

Java 把所有設備里的有序資料抽象成流模型, 簡化了輸入/輸出處理, 理解了流的概念模型也就了解了Java IO,

通過使用處理流, Java 程式無須理會輸入/輸出節點是磁盤、 網路還是其他的輸入/輸出設備, 程式只要將這些節點流包裝成處理流, 就可以使用相同的輸入/輸出代碼來讀寫不同的輸入/輸出設備的資料,

2.1.5 I/O 的體系結構

  • Java的IO流共涉及40多個類,實際上非常規則,都是從如下 Java Io 流四大家族  個 抽象基類派生的,
  • 由以下這四個類派生出來的子類,其名稱都是以其父類名作為子類名后綴,這樣用于我們辨認,
  • Java IO流四大家族:
    • java.io.InputStream  位元組輸入流,類名是以  "stream" 結尾的,
public abstract class InputStream implements Closeable {}
  • java.io.OutputStream 位元組輸出流,類名是以  "stream" 結尾的,
public abstract class OutputStream implements Closeable, Flushable {}
  • java.io.Reader 字符輸入流,類名是以 "Reader/Writer"結尾的,
public abstract class Reader implements Readable, Closeable {}
  • java.io.Writer 字符輸出流,類名是以以 "Reader/Writer"結尾的,
public abstract class Writer implements Appendable, Closeable, Flushable {}
  • 上述四大家族的首領都是抽象類 abstract class,這四個類都實作了 java.io.Closeable 介面,都是可以關閉的,都有 close() 方法,流畢竟是一個管道,這個記憶體和硬碟之間的通道,用完之后一定要關閉,不然會耗費很多資源(因為Java打開的資源是有限的,當你打開過多的資源超過限制時就無法打開其它的資源了),養成好習慣,用完之后一定要關閉(必須關閉),

  • 這四個類都實作了java.io.Flushable 介面,都是可重繪 的,都是有 flush() 方法的,養成一個好習慣,輸出流(將記憶體當中的資料輸出到硬碟檔案當中)在最終(輸出完)之后,一定要記得 flush() ,重繪一下,這個重繪的作用就是清空管道(強制將記憶體當中的資料輸出到檔案中),為什么要清空管道呢:因為:如果沒有 flush() 可以會導致記憶體中一部分的資料并沒有全部輸出到硬碟當中,從而導致一部分的資料丟失,
  • 注意:在Java中只要 類名是以 **"stream"** 結尾的都是位元組流,以 **"Reader/Writer"**結尾的都是字符流,
java.io包下需要掌握的流有 16個

檔案專屬:
java.io.FileInputStream
java.io.FileOutputStream    位元組流無法讀取到: 檔案中的空格的
java.io.FileReader
java.io.FileWriter          字符流可以讀取到:檔案中的空格的

轉換流: (將位元組流轉換字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter

緩沖流專屬:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
java.io.BufferedOutputStream

資料流專屬:
java.io.DataInputStream
java.io.DataOutputStream

標準輸出流:
java.io.PrintWtiter
java.io.PrinStream

物件專屬流:
java.io.ObjectInputStream
java.io.ObjectOutputStream

2.2 字符流

2.2.1 java.io.FileReader 字符輸入流

關于字符輸入流的類都是繼承了:java.io.Writer(字符輸出流)/ java.io.Reader (字符輸入流) 來使用的,但是這個兩個類是抽象類,是無法 new 物件來使用的,所以我們就需要使用其實作的子類:對于檔案字符輸入流比較常用的就是: java.io.FileReader 這個子類了,

字符流: 只能讀取文本檔案,不能讀取其它格式的檔案,文本檔案不僅僅是 .txt 后綴的檔案,.c,.java,.c++ 都是文本檔案,注意 : word 不是文本檔案,因為 word 中的文本字符是有一個規范格式設定的,不是純的文本檔案,字符流操作字符,只能操作普通文本檔案,最常見的文本檔案:.txt,.java,.c,.cpp 等語言的源代碼,尤其注意.doc,excel,ppt這些不是文本檔案,

在讀取檔案時,必須保證該檔案已存在,否則報例外,

其中繼承的 InputStreamReader 是個轉換流,繼承了  Reader 抽象類的,

FileReader的構造器

public FileReader(File file) throws FileNotFoundException; // 在給定從中讀取資料的 File 的情況下創建一個新 FileReader物件
public FileReader(String fileName) throws FileNotFoundException; // 根據檔案的相對路徑名/絕對路徑創建一個新 FileReader物件

舉例:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class FileReaderTest {
    public static void main(String[] args) {
        File file = new File("E:\\Java\\JavaRebuilt\\src\\blogs\\blog9\\hello.txt");  // 絕對路徑
        try {
            FileReader fileReader = new FileReader(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try {
            FileReader fileReader2 = new FileReader("src/blogs/blog9/hello.txt"); // 相對路徑
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

如下是 FileReader  繼承 java.io.InputStreamReader** 繼承的方法**

  • read() : 讀取單個字符,作為整數讀取的字符,范圍在 0 到 65535 之間 (0x00-0xffff)(2個位元組的Unicode碼),如果已到達流的末尾,則回傳 -1,
public int read() throws IOException;  // 讀取單個字符,
  • int read(char[] cbuf) : 將字符讀入陣列,如果已到達流的末尾,則回傳 -1,否則回傳本次讀取的字符數,
public int read(char[] cbuf) throws IOException; // 將檔案中的資料讀取到char[] cbuf的字符陣列當中,回傳讀取到的個數,
  • int read(char[] cbuf,int off,int len) : 將字符讀入陣列的某一部分,存到陣列cbuf中,從off處開始存盤,最多讀len個字 符,如果已到達流的末尾,則回傳 -1,否則回傳本次讀取的字符數,
public int read(char[] cbuf,int offset, int length) throws IOException; // 將字符讀入陣列中的某一部分.
  • public void close() throws IOException :關閉此輸入流并釋放與該流關聯的所有系統資源,
public void close() throws IOException;  // 關閉此輸入流并釋放與該流關聯的所有系統資源,

需要明白:對于 read() 讀取檔案內容的方式是,一個一個字符的讀取的,每呼叫一次 read()對于的檔案中的游標就會往后移動一下,對于特殊的 read(char[ ] cbuf) 讀取的個數是 char[] 陣列的長度,往后移動游標的位置也是  char[] 陣列的長度,以及回傳的是對于字符的編碼值,

 設檔案 file1.txt ,采用字符流的話是這樣讀的:
  a中國bo張三
  第一次讀: ‘a’字符
  第二次讀: ‘中’字符

舉例:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null; // 相對路徑
        try {
            fileReader = new FileReader("src/blogs/blog9/hello.txt");
            int read = fileReader.read();  // 回傳的是編碼值
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);

            read = fileReader.read();
            System.out.println(read);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // fileReader 防止 null參考
            if(fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

舉例: 使用 while() 回圈處理

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null; // 相對路徑
        try {
            fileReader = new FileReader("src/blogs/blog9/hello.txt");
            int len = 0;

            // 當read()讀取到 檔案末尾回傳 -1,跳出回圈
            while((len = fileReader.read()) != -1) {
                System.out.println(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // fileReader 防止 null參考
            if(fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

舉例: 使用 int read(char[] cbuf) : 將字符讀入陣列,如果已到達流的末尾,則回傳 -1,否則回傳本次讀取的字符數

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null; // 相對路徑
        try {
            fileReader = new FileReader("src/blogs/blog9/hello.txt");
            int len = 0;
            char [] chars = new char[4];
            // read(chars) 一次性讀取陣列長度個字符,回傳讀取到的字符個數,到達檔案末尾回傳-1
            while((len = fileReader.read(chars)) != -1) {
                // 將char[] 陣列轉換為字串
                System.out.println(new String(chars));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // fileReader 防止 null參考
            if(fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

read(char[]) 讀取資料時的覆寫效果的講解

舉例: 去除 read(char[] cduf) 的覆寫效果,我們讀取多到了多少個字符,就 new String 轉換多少個字符

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null; // 相對路徑
        try {
            fileReader = new FileReader("src/blogs/blog9/hello.txt");
            int len = 0;
            char [] chars = new char[4];
            // read(chars) 一次性讀取陣列長度個字符,回傳讀取到的字符個數,到達檔案末尾回傳-1
            while((len = fileReader.read(chars)) != -1) {
                // 將char[] 陣列轉換為字串
                // 這里我們 讀取到了多少個字符,就將 chars陣列中的前多少個轉換為字串
                System.out.println(new String(chars,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // fileReader 防止 null參考
            if(fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.2.2 java.io.FileWriter 字符輸出流

于字符流輸出的類都是繼承了:java.io.Writer(字符輸出流)/ java.io.Reader (字符輸入流) 來使用的,但是這個兩個類是抽象類,是無法 new 物件來使用的,所以我們就需要使用其實作的子類:對于檔案字符輸出流比較常用的就是: java.io.FileWriter 這個子類了,

其中繼承的 OutputStreamWriter 是個轉換流,繼承了  Writer 抽象類的,

OutputStreamWriter 的構造器:

public FileWriter(File file) throws IOException;  // 根據給定的 File 物件構造一個 FileWriter 物件
public FileWriter(String fileName) throws IOException; // 根據給定的檔案名構造一個 FileWriter 物件,
public FileWriter(File file,boolean append) throws IOException; // 根據給定的 File 物件構造一個 FileWriter 物件,如果第二個引數為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處,
//  file - 要寫入資料的 File 物件
// append - 如果為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處 ,默認是 false,不寫的話也是 false

舉例:

package blogs.blog9;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {
    public static void main(String[] args) {
        File file = new File("E:\\Java\\JavaRebuilt\\src\\blogs\\blog9\\hello.txt"); // 絕對路徑
        try {
            FileWriter fileWriter = new FileWriter(file);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileWriter fileWriter2 = new FileWriter("src/blogs/blog9/hello.txt"); // 相對路徑
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileWriter fileWriter3 = new FileWriter("src/blogs/blog9/hello.txt",true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如下是 FileWriter  繼承 java.io.OutputStreamWriter繼承的方法

  • void write(int c) : 寫入單個字符,要寫入的字符包含在給定整數值的 16 個低位中,16 高位被忽略, 即寫入0 到 65535 之間的Unicode碼,
public void write(int c) throws IOException;  // 寫入單個字符,
  • void write(char[] cbuf) : 將字符陣列的內容,寫入到對應的硬碟檔案中去
public void write(char[] cbuf) throws IOException; // 將字符陣列的內容,寫到檔案中
  • void write(char[] cbuf,int off,int len) : 寫入字符陣列的某一部分,從off開始,寫入len個字符,
public void write(char[] cbuf,int off,int len) throws IOException // 寫入字符陣列的某一部分,
  • void write(String str) :將字串的內容,寫入到對應的硬碟檔案中去
public void write(Stirng str) throws IOException
  • void write(String str,int off,int len) :寫入字串的某一部分,從off開始,寫入len個結束
public void write(String str,int off,int len) throws IOException
  • void flush() :重繪該流的緩沖,立即記憶體中的資料寫入預期目標硬碟檔案中去,
public void flush() throws IOException;  // 重繪該流的緩沖,
  • public void close() :關閉此輸出流并釋放與該流關聯的所有系統資源
public void close() throws IOException; // 關閉此流,但要先重繪它,在關閉該流之后,再呼叫 write() 或 flush() 將導致拋出 IOException,關閉以前關閉的流無效,

注意: 如果寫入到的檔案不存在,是會自動創建的,如果檔案已經存在了,在創建FileWriter 物件時沒有設定為 true 的話,是會將原本檔案中已經存在的內容覆寫的,寫入新的內容,

舉例: 檔案不存在,自動創建,

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderTest {
    public static void main(String[] args) {
        FileReader fileReader = null; // 相對路徑
        try {
            fileReader = new FileReader("src/blogs/blog9/hello.txt");
            int len = 0;
            char [] chars = new char[4];
            // read(chars) 一次性讀取陣列長度個字符,回傳讀取到的字符個數,到達檔案末尾回傳-1
            while((len = fileReader.read(chars)) != -1) {
                // 將char[] 陣列轉換為字串
                // 這里我們 讀取到了多少個字符,就將 chars陣列中的前多少個轉換為字串
                System.out.println(new String(chars,0,len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // fileReader 防止 null參考
            if(fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

舉例: 檔案已經存在,寫入的資訊覆寫原本檔案的全部內容

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {
    public static void main(String[] args) {
        FileWriter fileWriter = null; // 相對路徑
        try {
            // 1. 創建輸出流物件: FileWriter
            fileWriter = new FileWriter("src/blogs/blog9/hello2.txt");

            // 2. 將記憶體當中的內容寫入到檔案中
            fileWriter.write("你好世界");
            // 3. 重繪:將記憶體中沒有輸出到檔案中的內容,強制全部寫入到檔案中
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 防止 null 參考
            if(fileWriter != null) {
                // 4. 關閉IO資源
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

舉例: 創建 **FileWriter ** 物件時,設定 true ,將記憶體當中的資訊寫入到檔案的末尾去,不會覆寫原本檔案中的內容

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {
    public static void main(String[] args) {
        FileWriter fileWriter = null; // 相對路徑
        try {
            // 1. 創建輸出流物件: FileWriter,并設定 true 將寫入的內容追加到檔案的末尾中去
            fileWriter = new FileWriter("src/blogs/blog9/hello2.txt",true);

            // 2. 將記憶體當中的內容寫入到檔案中
            fileWriter.write("\n");  // 換行
            fileWriter.write("Hello World");
            // 3. 重繪:將記憶體中沒有輸出到檔案中的內容,強制全部寫入到檔案中
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 防止 null 參考
            if(fileWriter != null) {
                // 4. 關閉IO資源
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.2.3 實體文本檔案的拷貝

將同目錄中的 hello.txt 檔案的內容拷貝到 同目錄中的 hello2.txt 中去

思路:

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterTest {
    public static void main(String[] args) {
        FileWriter descFile = null;
        FileReader srcFile = null; // 注意檔案后綴
        try {
            // 1. 創建hello.txt 檔案的字符輸入流物件,以及 hello2.txt檔案的字符輸出流物件
            descFile = new FileWriter("src/blogs/blog9/hello2.txt");
            srcFile = new FileReader("src/blogs/blog9/hello.txt");

            // 2. 一邊讀,一邊寫
            int len = 0;
            char[] chars = new char[10];
            // 讀取hello.txt的資料資訊
            while((len = srcFile.read(chars)) != -1) {
                // 將讀取到的內容寫入到hello2.txt檔案中
                descFile.write(chars,0,len);
            }

            // 3. 重繪:將記憶體中遺留沒有寫入到檔案中的資訊,全部強制寫入到檔案中去
            descFile.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5. 關閉IO資源
            // 分開 try,如果兩個一起try的話其中一個出現例外了,后面的一個IO就無法關閉了
            if(srcFile != null) {  // 防止 null參考
                try {
                    srcFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(descFile != null) {  // 防止 null參考
                try {
                    descFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }
}

2.3 位元組流

2.3.1 FileInputStream 位元組輸入流

關于位元組輸入流的類都是繼承了:InputStream(位元組輸入流) 來使用的,但是個類是抽象類,是無法 new 物件來使用的,所以我們就需要使用其實作的子類:對于檔案字符輸入流比較常用的就是: **java.io.FileInputStream ** 這個子類了,

位元組流: 可以操作任何的檔案,因為位元組流讀取的是二進制資訊,讀取1個位元組byte,等同于一次讀取8個二進制,這種流是萬能的,什么型別的檔案都可以讀取到,因為檔案都是有二進制組成的,包括: 文本檔案,圖片,聲音檔案,再比如:比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt等檔案,

FileInputStream的構造器

public FileInputStream(File file) throws FileNotFoundException; // 通過打開一個到實際檔案的連接來創建一個 FileInputStream,該檔案通過檔案系統中的 File 物件 file 指定,
public FileInputStream(String name) throws FileNotFoundException;  // 通過打開一個到實際檔案的連接來創建一個 FileInputStream,該檔案通過檔案系統中的路徑名 name 指定,

和 字符流中的 FileReader 基本上是一樣的,

舉例:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class FileInputStreamTest {
    public static void main(String[] args) {
        File file = new File("E:\\Java\\JavaRebuilt\\src\\blogs\\blog9\\test.png"); // 絕對路徑
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fileInputStream2 = new FileInputStream("src/blogs/blog9/test.png"); // 相對路徑
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}

如下是  FileInputStream  繼承 java.io.InputStream 繼承的方法

位元組流和字符流的用法幾乎完全一樣, 區別在于位元組流和字符流操作的資料單元的不同:位元組流是 8 位的位元組, 而字符流操作的資料單元是 16 位的字符,方法是上的使用也是一樣的,

  • read() :讀取檔案的一個位元組,回傳值是: 讀取到"位元組"本身,到達檔案末尾回傳 -1,
public int read() throws IOException ; // 從此輸入流中讀取一個資料位元組,如果沒有輸入可用,則此方法將阻塞,
  • read(byte[] b) :  讀取檔案中 byte[] 陣列長度的位元組個數,回傳讀取的的位元組個數,到位檔案末尾回傳 -1,
public int read(byte[] b) throws IOException; // 從此輸入流中將最多 b.length 個位元組的資料讀入一個 byte 陣列中,在某些輸入可用之前,此方法將阻塞,
  • read(byte[] b int off, int len) : 將位元組讀入陣列的某一部分,存到陣列b中,從off處開始存盤,最多讀len個字 符,如果已到達流的末尾,則回傳 -1,否則回傳本次讀取的字符數,
public int read(byte[] b, int off, int len) throws IOException; // 從此輸入流中將最多 len 個位元組的資料讀入一個 byte 陣列中,如果 len 不為 0,則在輸入可用之前,該方法將阻塞;否則,不讀取任何位元組并回傳 0,
  • public void close() throws IOException :關閉此輸入流并釋放與該流關聯的所有系統資源,
public void close() throws IOException;  // 關閉此輸入流并釋放與該流關聯的所有系統資源,

舉例:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest {
    public static void main(String[] args) {
         FileInputStream fileInputStream = null;
        try {
            // 1. 創建位元組流物件
            fileInputStream = new FileInputStream("src/blogs/blog9/hello.txt");

            int len = 0;
            // 2.讀取檔案資訊
            while((len = fileInputStream.read()) != -1) {
                System.out.println(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉資源
            // 防止null參考
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

舉例: 使用 byte[] 位元組陣列


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStreamTest {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            // 1. 創建位元組流物件
            fileInputStream = new FileInputStream("src/blogs/blog9/hello.txt");

            // 2.讀取檔案資訊
            int len = 0;
            byte[] bytes = new byte[1024];  // 1KB
            // read(bytes) 一次性讀取byte[]陣列大小的位元組個數,并存盤到 bytes 陣列中,回傳讀取的位元組個數
            while((len = fileInputStream.read(bytes)) != -1) {
                // 將 bytes 陣列轉換為字串,讀取多少,轉換多少
                String s = new String(bytes,0,len);
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.3.2 FileOutputStream 位元組輸出流

關于位元組輸出流的類都是繼承了:OutputStream(位元組輸出流) 來使用的,但是個類是抽象類,是無法 new 物件來使用的,所以我們就需要使用其實作的子類:對于檔案字符輸出流比較常用的就是: java.io.FileOutputStream 這個子類了,

FileOutputStream的構造器

public FileOutputStream(File file) throws FileNotFoundException // 創建一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流,創建一個新 FileDescriptor 物件來表示此檔案連接, 
public FileOutputStream(String name) throws FileNotFoundException // 創建一個向具有指定名稱的檔案中寫入資料的輸出檔案流,創建一個新 FileDescriptor 物件來表示此檔案連接
public FileOutputStream(File file,boolean append)throws FileNotFoundException // 創建一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流,如果第二個引數為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處,創建一個新 FileDescriptor 物件來表示此檔案連接, 
// file - 為了進行寫入而打開的檔案,
// append - 如果為 true,則將位元組寫入檔案末尾處,而不是寫入檔案開始處

舉例:


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class FileOutputStreamTest {
    public static void main(String[] args) {
        File file = new File("E:\\Java\\JavaRebuilt\\src\\blogs\\blog9\\test.png"); // 絕對路徑
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            FileOutputStream fileOutputStream2 = new FileOutputStream("src/blogs/blog9/test.png"); // 相對路徑
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

}

如下是 FileOutputStream 繼承 java.io.OutputStream繼承的方法

  • void write(int c) : 寫入單個字符,要寫入的字符包含在給定整數值的 16 個低位中,16 高位被忽略, 即寫入0 到 65535 之間的Unicode碼,
public void write(int b) throws IOException;  // 寫入單個字符,
  • void write(byte[] b) : 將字符陣列的內容,寫入到對應的硬碟檔案中去
public void write(byte[] cbuf) throws IOException; // 將字符陣列的內容,寫到檔案中
  • void write(byte[] b,int off,int len) : 寫入字符陣列的某一部分,從off開始,寫入len個字符,
public void write(byte[] b,int off,int len) throws IOException // 寫入字符陣列的某一部分,
  • void flush() :重繪該流的緩沖,立即記憶體中的資料寫入預期目標硬碟檔案中去,
public void flush() throws IOException;  // 重繪該流的緩沖,
  • public void close() :關閉此輸出流并釋放與該流關聯的所有系統資源
public void close() throws IOException; // 關閉此流,但要先重繪它,在關閉該流之后,再呼叫 write() 或 flush() 將導致拋出 IOException,關閉以前關閉的流無效,

注意: 如果寫入到的檔案不存在,是會自動創建的,如果檔案已經存在了,在創建 FileOutputStream 物件時沒有設定為 true 的話,是會將原本檔案中已經存在的內容覆寫的,寫入新的內容,

舉例: 創建 FileOutputStream 物件時沒有設定為 true 的,在檔案的末尾添加資訊,不會覆寫原來檔案的資訊,


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest {
    public static void main(String[] args) {
        File file = new File("src/blogs/blog9/hello.txt");
        FileOutputStream fileOutputStream = null;
        try {
            // 1. 創建FileOutputStream物件
            fileOutputStream = new FileOutputStream(file,true);

            // 2. 寫入資訊到檔案中
            byte[] bytes = new byte[]{'A','B'} ;
            fileOutputStream.write(bytes);

            // 3. 重繪:將遺留在記憶體當中沒有寫入到檔案的資訊,強制全部寫入到檔案中
            fileOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 關閉IO資源
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.3.3 實體圖片檔案的拷貝

思路:


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamTest {
    public static void main(String[] args) {
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            // 1.創建 test2.png 檔案的輸入位元組流物件
            fileOutputStream = new FileOutputStream("src/blogs/blog9/test2.png");
            // 2.創建 test.png 檔案的輸出位元組流物件
            fileInputStream = new FileInputStream("src/blogs/blog9/test.png");

            // 2. 一邊讀,一邊寫
            int len = 0;
            byte[] bytes = new byte[1024 * 1024]; // 1MB
            // 讀
            while ((len = fileInputStream.read(bytes)) != -1) {
                // 讀取多少寫入多少
                fileOutputStream.write(bytes, 0, len);
            }

            // 3. 重繪:
            fileOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.關閉IO資源
            // 分開 try 防止,如果一起try的話,其中一個出現了例外,后面的IO資源就無法關閉了,
            if (fileInputStream != null) {  // 防止null參考
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.3.4 實體對圖片的簡單加密

思路:

加密:這里我們通過位元組流,獲取到圖片中的每個 byte 位元組資訊,再對獲取到的每個 byte 位元組資訊進行 ^ 5 運算加密,新生成一個加密后的圖片(這個圖片是加密了的,是無法打開的),

解密:同樣獲取到圖片中每個 byte 位元組資訊,再對獲取到的每個 byte 位元組資訊進行 ^ 5 運算解密,新生成一個解密后的圖片(這個圖片就可以正常打開了)

核心 : 就是利用 對于一個數^ 兩個異或同一個數值,回傳原來的數值,例如:6 ^ 2 = 4;4 ^ 2 = 6;

舉例:


import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 圖片的加密解密操作
 */
public class ImageEncryDecry {


    /**
     * 對圖片中的每個像素點中的 byte 進行 ^ 5 的加密
     */
    @Test
    public void test() {
        FileInputStream fileInputStream = null; // 注意檔案后綴
        FileOutputStream fileOutputStream = null;  //
        try {
            fileInputStream = new FileInputStream("src/day27/test2.jpg");
            fileOutputStream = new FileOutputStream("src/day27/test3.jpg");

            // 一邊讀,一邊加密,一邊寫
            byte[] bytes = new byte[20];
            int len = 0;

            // 讀取
            while((len = fileInputStream.read(bytes)) != -1) {
                // 讀取多少,加密多少,注意了不是 bytes陣列的長度,因為存在重復的覆寫效果
                for (int i = 0; i < len; i++) {
                    bytes[i] = (byte)(bytes[i] ^ 5);  // 加密
                }

                // 加密完后,寫入到檔案中:讀取多少,寫入多少
                fileOutputStream.write(bytes,0,len);

            }

            // 重繪
            fileOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        // 關閉:
            if(fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

package day27;


import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 圖片的加密解密操作
 */
public class ImageEncryDecry {
    /**
     * 對加密 ^ 5 的檔案解密
     */
    @Test
    public void test2() {
        FileInputStream fileInputStream = null; // 注意檔案后綴
        FileOutputStream fileOutputStream = null;  //
        try {
            fileInputStream = new FileInputStream("src/day27/test3.jpg");
            fileOutputStream = new FileOutputStream("src/day27/test4.jpg");

            // 一邊讀,一邊加密,一邊寫
            byte[] bytes = new byte[20];
            int len = 0;

            // 讀取
            while((len = fileInputStream.read(bytes)) != -1) {
                // 讀取多少,加密多少,注意了不是 bytes陣列的長度,因為存在重復的覆寫效果
                for (int i = 0; i < len; i++) {
                    bytes[i] = (byte)(bytes[i] ^ 5);  // 加密
                }

                // 加密完后,寫入到檔案中:讀取多少,寫入多少
                fileOutputStream.write(bytes,0,len);

            }

            // 重繪
            fileOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉:
            if(fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

3.1 緩沖流

為了提高資料讀寫的速度,Java API 提供了帶緩沖區功能的流類,在使用這些流類時,會創建一個內部緩沖區陣列,預設使用 8192個位元組(8kb)的緩沖區

  • 緩沖流要 ”套接“ 在相應的節點流之上,根據資料操作單位可以把緩沖流分為 :
  • 位元組緩沖流
    • java.io.BufferedInputStream : 位元組輸入緩沖流

  • java.io.BufferedOutputStream :位元組輸出緩沖流

  • 字符緩沖流
    • java.io.BufferedReader : 字符輸入緩沖流
    • java.io.BufferedWriter : 字符輸出緩沖流

  • 緩沖流的使用:當讀取資料時,資料按塊讀入緩沖區 ,其和的讀操作則直接訪問緩沖區,
  • BufferedInputStream 讀取位元組檔案時,BufferedInputStream 會一次性從檔案中讀取 8192 個 ( 1024 * 8 = 8KB) ,存在緩沖區中,直到緩沖區裝滿了,才重新從檔案中讀取下一個 8192 個位元組陣列,
  • 向流中寫入位元組時,不會直接寫到檔案中,先寫道緩沖區中直到緩沖區寫滿,BufferedOutputStream 才會把緩沖區中的資料一次性寫到檔案中里,使用方法 flush()  可以強制將緩沖區的內容全部寫入輸出流,
  • 關閉流的順序和打開流的順序相反,只要關閉最外層流即可,關閉最外層流也會相應關閉內層節點流,因為從原始碼中可以看出,
    BufferedOutputStream.close() 關閉的同時,會將其中對應的節點流關閉,如下原始碼:

3.1.1 BufferedReader (字符輸入緩沖流) / BufferedWriter (字符輸出緩沖流)

BufferedReader的構造器

public BufferedReader(Reader in);  // 創建一個使用默認大小輸入緩沖區的緩沖字符輸入流,
public BufferedReader(Reader in,int sz); // 創建一個使用指定大小輸入緩沖區的緩沖字符輸入流,

BufferedReader 中的方法和FileReader是一樣的因為都是繼承了 Reader的抽象類的方法的 所以這里就不多介紹說明了,

舉例: 使用字符緩沖區讀取檔案資訊


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderWriterTest {
    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        try {
            FileReader fileReader = new FileReader("src/blogs/blog9/hello.txt");
            // 1. 創建字符輸入緩沖區流物件
            // 引數是:Reader 抽象類,這里我們使用 FileReader 同樣也是 Reader 的子類作為引數
            bufferedReader = new BufferedReader(fileReader);

            // 2. 讀取檔案資訊
            int len = 0;
            char [] chars = new char[3];
            while((len = bufferedReader.read(chars))!= -1) {
                // 將 char 轉換為字串
                // 讀多少轉換多少
                String s = new String(chars,0,len);
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (bufferedReader != null) {
                try {
                    // 只需要關閉外層緩沖區的資源就可以,內層的會自動一起關閉
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

BufferedWriter的構造器

public BufferedWriter(Writer out);  // 創建一個使用默認大小輸出緩沖區的緩沖字符輸出流,
public BufferedWriter(Writer out,int sz); // 創建一個使用給定大小輸出緩沖區的新緩沖字符輸出流,

BufferedWriter 中的方法和FileReader是一樣的因為都是繼承了 Writer的抽象類的方法的 所以這里就不多介紹說明了,

舉例: 使用字符緩沖區寫入檔案資訊


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedReaderWriterTest {
    public static void main(String[] args) {
        BufferedWriter bufferedWriter = null;
        try {
            FileWriter fileWriter = new FileWriter("src/blogs/blog9/hello.txt",true);
            // 1. 創建字符輸出緩沖流物件
            // 引數是:Writer 抽象類,這里我們使用 BufferedWriter 同樣也是 Writer 的子類作為引數
            bufferedWriter = new BufferedWriter(fileWriter);
            // 2. 寫入檔案資訊
            char[] chars = new char[]{'H','H'};
            bufferedWriter.write("\n"); // 換行
            bufferedWriter.write(chars);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源這里不用flush()因為緩沖流會自動重繪
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

3.1.2 BufferedInputStream(位元組輸入緩沖流) / BufferedOutputStream (位元組輸出緩沖流)

這里位元組緩沖流和上面的字符緩沖流是一樣的這里就不多說明了,

BufferedOutputStream / BufferedInputStream 位元組的構造器

public BufferedInputStream(InputStream in); //創建一個 BufferedInputStream 并保存其引數,即輸入流 in,以便將來使用,創建一個內部緩沖區陣列并將其存盤在 buf 中,
public BufferedInputStream(InputStream in,int size); // 創建具有指定緩沖區大小的 BufferedInputStream 并保存其引數,即輸入流 in,以便將來使用,創建一個長度為 size 的內部緩沖區陣列并將其存盤在 buf 中,
public BufferedOutputStream(OutputStream out); // 創建一個新的緩沖輸出流,以將資料寫入指定的底層輸出流,
public BufferedOutputStream(OutputStream out, int size); // 創建一個新的緩沖輸出流,以將具有指定緩沖區大小的資料寫入指定的底層輸出流,

舉例: 使用BufferedInputStream 輸入緩沖流讀取檔案的資訊


import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedReaderWriterTest {
    public static void main(String[] args) {
        BufferedInputStream bufferedInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/hello.txt");

            // 1. 創建位元組輸入緩沖流物件
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            // 2. 讀取檔案資訊
            int len = 0;
            byte[] bytes = new byte[3];
            while ((len = bufferedInputStream.read(bytes)) != -1) {
                // 將 bytes 轉換為字串,讀取了多少轉換為多少
                String s = new String(bytes,0,len);
                System.out.println(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            // 關閉IO資源這里只需要關閉外層緩沖區的資源就可以,內層的會自動一起關閉
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

舉例: 使用BufferedOutputStream 輸出緩沖流,


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedReaderWriterTest {
    public static void main(String[] args) {
        BufferedOutputStream bufferedOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/hello.txt",true);
            // 1.創建位元組輸出緩沖流物件
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

            // 2. 寫入資訊
            byte[] bytes = new byte[]{'K','K'};
            bufferedOutputStream.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源這里不用flush()因為緩沖流會自動重繪
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
}

4.1 轉換流

  • 轉換流提供了在位元組流和字符流之間的轉換
  • Java API 提供了兩個轉換流:
    • java.io.InputStreamReader :將 InputStream 轉換為 Reader

  • Java.io.OutputStreamWriter : 將 OutputStream 轉換為 Writer

  • 位元組流中的資料都是字符時,轉成字符流操作更高效,
  • 很多時候我們使用轉換流來處理檔案亂碼問題,實作編碼和 解碼的功能,

InputStreamReader

實作將位元組的輸入流按指定字符集轉換為字符的輸入流,

需要和 InputStream ”套接“

InputStreamReader 構造器:

public InputStreamReader(InputStream in); // 創建一個使用默認字符集的 InputStreamReader, 
public InputStreamReader(InputStream in,String charsetName) throws UnsupportedEncodingException; //創建使用指定字符集的 InputStreamReader,

舉例:

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
public class StreamWriterTest {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream("src/blogs/blog9/hello.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 將 FileInputStream 位元組輸入流轉換為 InputStreamReader 字符輸入流
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
    }
}

OutputStreamWriter

  • 實作將字符的輸出流按指定字符集轉換為位元組的輸出流,
  • 需要和OutputStream“套接”,

OutputStreamWriter的構造器

public OutputStreamWriter(OutputStream out); // 創建使用默認字符編碼的 OutputStreamWriter
public OutputStreamWriter(OutputStream out,String charsetName) throws UnsupportedEncodingException; // 創建使用指定字符集的 OutputStreamWriter,

舉例:


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

public class StreamWriterTest {
    public static void main(String[] args) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("src/blogs/blog9/hello.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 將 FileOutputStream 位元組輸出流轉換為 OutputStreamWriter 字符輸出流
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);

    }

4.2 標準輸入\輸出流

  • System.in  和 System.out 分布代表了系統標準的輸入和輸出設備,
  • 默認輸入設備時:鍵盤,輸出設備時:顯示幕
  • System.in 實際上是一個 InputStream 位元組輸入流,將控制臺的資料讀取到,一個System 類中的靜態屬性

  • System.out 的型別實際上是 PrintStream 位元組輸出流,其是OutputSteam 的子類,FilterOutputStream 的子類,
  • 重定向:通過 System 類的 setIn ,setOut 方法對默認設備進行改變,
public static void setIn(InputStream in); //重新分配“標準”輸入流, 首先,如果有安全管理器,則通過 RuntimePermission("setIO") 權限呼叫其 checkPermission 方法,查看是否可以重新分配“標準”輸入流, 
public static void setOut(PrintStream out); //重新分配“標準”輸出流, 首先,如果有安全管理器,則通過 RuntimePermission("setIO") 權限呼叫其 checkPermission 方法,查看是否可以重新分配“標準”輸出流

舉例: 將 System.out 輸出的內容,不顯示在控制臺中,而是寫入到檔案中,制作一個日志檔案資訊

package blogs.blog9;


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PrintWriterStreamTest {
    public static void main(String[] args) {
        log("呼叫了System類的gc()方法,建議啟動垃圾回收");
        log("呼叫了UserService的doSome()方法");
        log("用戶嘗試進行登錄,驗證失敗");

    }

    public static void log(String msg) {
        // 1. 創建一個輸出的位元組的檔案物件,用于 System的重定向
        PrintStream printStream = null;
        try {
            printStream = new PrintStream(new FileOutputStream("src/blogs/blog9/log.txt",true));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        // 2.改變 System.out()的輸出方向使用:SetOut()方法改為重定向到 printSteam的檔案中
        System.setOut(printStream);

        // 3. 設定輸入的日期時間
        Date date = new Date();  // 獲取當前系統的時間(毫秒值)時間戳
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");
        // 將 Date 轉換為規定格式的字串
        String strTime = simpleDateFormat.format(date);
        System.out.println(strTime +": " + msg);

        // 4. 關閉IO資源
        System.out.close();
    }
}

舉例:

從鍵盤輸入字串,要求將讀取到的整行字串轉成大寫輸出,然后繼續進行輸入操作,

  • 直至當輸入 "e" 或者 "exit" 時,退出程式,
  • 方法一: 使用Scanner 實作,呼叫next()回傳一個字串
  • 方法二:使用System.in 實作,System.in ---> 轉換流 ---> BufferedReader 的readLine

這里我們使用方法二的方式:


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PrintWriterStreamTest {
    public static void main(String[] args) {
        // System.in 就是 public final static InputStream in = null;
        InputStreamReader isr = new InputStreamReader(System.in);  // System.in 控制臺
        // 轉換為該字符輸出流,可以讀取一行的資訊
        BufferedReader bufferedReader = new BufferedReader(isr);

        while(true) {
            String data = https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/02/11/null;
            try {
                data = bufferedReader.readLine();
                if("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                    System.out.println("程式結束");
                    break;
                }

                // 將字符轉換為大寫字符
                String upperCase = data.toUpperCase();
                System.out.println(upperCase);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        if(bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4.3 資料流

資料流支持原始資料型別值的二進制 I / O 操作,booleancharbyteshortintlongfloat and double 以及字串值,所有資料流都實作 DataInput 介面或 DataOutput 介面,

為了方便地操作Java語言的基本資料型別和String的資料,可以使用資料流,

  • 資料流有兩個類:(用于讀取和寫出基本資料型別、String類的資料)
  • DataInputStreamDataOutputStream
  • 分別“套接”在 InputStream OutputStream 子類的流上
  • DataInputStream中的方法
boolean readBoolean()
byte readByte()
char readChar() 
float readFloat() 
double readDouble() 
short readShort() 
long readLong() 
int readInt() 
String readUTF() 
void readFully(byte[] b)
  • DataOutputStream 中的方法 將上述的方法的read改為相應的即可,
boolean writeBoolean()
byte writeByte()
char writeChar()
float writeFloat()
double writeDouble()
short writeShort()
long writeLong()
int writeInt()
String writeUTF()
void writeFully(byte[] b)

需要特別注意的是:

  • 輸入流判斷是否結束,不是按普通流那樣判斷一個回傳值,而是通過:EOFException 例外,
    此例外主要被資料輸入流用來表明到達流的末尾,注意,其他許多輸入操作回傳一個特殊值表示到達流的末尾,而不是拋出例外,
  • write 和 read 的取值順序一定要匹配,
    輸入流由簡單的二進制資料組成,沒有指示單個值的型別,或者它們在流中開始的位置,
    該示例使用一個非常糟糕的編程技術:它使用浮點數來表示貨幣值,一般來說,浮點對精確值是不利的,它對于小數分數特別不利,因為常用值(如 0.1)不具有二進制表示形式,

舉例: DataOutputStream的使用


import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataInputStreamWriterTest {
    public static void main(String[] args) {
        DataOutputStream dataOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/hello.txt");
            // 1.創建對應的 DataOutputStream物件
            dataOutputStream = new DataOutputStream(fileOutputStream);

            byte b = 100;
            short s = 200;
            int i = 300;
            long l = 400;
            float f = 2.0f;
            double d = 3.14;
            boolean bool = true;
            char c = 'A';
            String str = "hello";

            // 2. 寫資料,將 記憶體當中的資料寫入到檔案中
            dataOutputStream.writeByte(b);
            dataOutputStream.writeShort(s);
            dataOutputStream.writeInt(i);
            dataOutputStream.writeLong(l);
            dataOutputStream.writeFloat(f);
            dataOutputStream.writeDouble(d);
            dataOutputStream.writeBoolean(bool);
            dataOutputStream.writeChar(c);
            dataOutputStream.writeChars(str);
            dataOutputStream.writeChars(str);

            // 3.重繪
            dataOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.關閉IO資源
            if (dataOutputStream != null) {
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

舉例:  DataInputStream 讀取被 DataOutputStream 寫入到檔案中的資訊

注意: write 和 read 的取值順序一定要匹配, DataOutputStream  依次寫入到檔案的 型別是什么順序,后面的 DataInputStream  讀取檔案的型別的順序就是什么要的,必須保持一致性,不然 讀取獲取到的資料是錯誤的,


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataInputStreamWriterTest {
    public static void main(String[] args) {
        DataInputStream dataInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/hello.txt");
            // 1.創建對應的DataInputStream 物件
            dataInputStream = new DataInputStream(fileInputStream);

            // 2. 讀取其中檔案的型別資訊:注意:write 和 read 的取值順序一定要匹配
            byte b = dataInputStream.readByte();
            System.out.println(b);

            short s = dataInputStream.readShort();
            System.out.println(s);

            int i = dataInputStream.readInt();
            System.out.println(i);

            long l = dataInputStream.readLong();
            System.out.println(l);

            float f = dataInputStream.readFloat();
            System.out.println(f);

            double d = dataInputStream.readDouble();
            System.out.println(d);

            boolean bool = dataInputStream.readBoolean();
            System.out.println(bool);

            char c = dataInputStream.readChar();
            System.out.println(c);

            String str = dataInputStream.readLine();
            System.out.println(str);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

4.4 物件流 (序列化 ObjectOutputStream,反序列化 ObjectInputStream)

就像資料流支持原始資料型別的 I / O,物件流支持 I / O 的物件,大部分,但不是全部,標準類支持物件的序列化, 都實作了標記介面 Serializable

物件流類是

  • java.io.ObjectInputStream

  • java.io.OjbectOutputSteam

  • 用于存盤和讀取基本資料型別資料物件的處理流,它的強大之處就是可以把 Java中的物件寫入到硬碟當中,也能把物件從硬碟當中還原回來,
  • 序列化:  用 ObjectOutputStream 類將基本資料型別或物件型別存盤保存到硬碟檔案的當中,
  • 反序列化:ObjectInputStream類 讀取存盤到硬碟檔案當中的基本資料型別或物件型別還原回來,存盤到記憶體當中,
  • 物件序列化機制 :允許把記憶體中的Java物件轉換成平臺無關的二進制流,從而允許把這種二進制流持久地保存在磁盤上,或通過網路將這種二進制流傳輸到另一個網路節點,當其它程式獲取了這種二進制流,就可以恢復成原來的 Java物件
  • 序列化是 RMI(Remote Method Invoke – 遠程方法呼叫)程序的引數和回傳值都必須實作的機制,而 RMI 是 JavaEE 的基礎,因此序列化機制是 JavaEE 平臺的基礎,
  • Java的序列化與反序列化的圖示如下:

什么時候需要用到序列化和反序列化呢?

在本地 JVM 里運行下 Java 實體,這個時候是不需要什么序列化和反序列化的

但當我們需要將記憶體中的物件持久化到磁盤,資料庫中時, 當我們需要與瀏覽器進行互動時,或者當我們需要實作 RPC 時, 這個時候就需要序列化和反序列化了,

只要我們對 JVM 堆記憶體中的物件進行持久化或網路傳輸, 這個時候都需要序列化和反序列化,

序列化的好處: 在于可將任何實作了 Serializable 介面的物件轉化為 位元組資料 ,使其在保存和傳輸時可被還原,

JSON 格式實際上就是將一個物件轉化為字串, 所以服務器與瀏覽器互動時的資料格式其實是字串,我們來看來 String 型別的原始碼:

String 型別實作了 Serializable 介面,并顯示指定 serialVersionUID 的值,也就是說我們使用Json進行傳輸字串資料的時候, JVM已經將字串資料序列化了

4.4.1 物件的序列化

如果需要讓某個物件支持序列化機制,則必須讓物件所屬的類及其屬性是可序列化的,為了讓某個類是可序列化的,該類必須實作如下兩個介面之一, 否則,會拋出NotSerializableException例外,

  • java.io.Serializable 介面一般使用這個介面,序列化

  • java.io.Externalizable

凡是實作了 Serializable 介面的類都有一個表示序列化版本識別符號的靜態變數:

private static final long serialVersionUID;
  • Java當中所有的包裝類以及 String 都實作了 java.io.Serializable 介面,所以一般要實作該介面的都是我們自定義的類,

  • serialVersionUID用來表明類的不同版本間的兼容性,簡言之,其目的是以序列化物件 進行版本控制,有關各版本反序列化時是否兼容,在 Java 中實作了 Serializable 介面后, JVM 在類加載的時候就會發現我們實作了這個介面, 然后在初始化實體物件的時候就會在底層幫我們實作序列化和反序列化
  • 如果一個類實作了了 java.io.Serializable 介面,但是卻沒有定義這個 serialVersionUID 靜態變數,以及也沒有從父類中繼承這個靜態變數,那么它的值會由 Java運行時環境根據類的內部細節自動生成,這個自動生成的值,我們是看不到的,導致我們無法手動修改,而且每次生成的都不太一樣,若類中的實體變數做了修改,那么 **serialVersionUID ** 可能會發生變化,就不是原來的了,故建議,手動顯式定義該靜態變數,不要讓Java自動生成,
  • 簡單來說,Java的序列化機制是通過在運行時,判斷類的 serialVersionUID 來驗證版本是否一致性的,在進行反序列化時,JVM會把傳來的位元組流中的 serialVersionUID 與本地相應物體類的 serialVersionUID 進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的例外,(InvalidCastException)
  • Java 序列化機制采用了一種特殊的序列化演算法, 其演算法內容如下:

所有保存到磁盤中的物件都有一個序列化編號,
當程式試圖序列化一個物件時, 程式將先檢查該物件是否己經被序列化過, 只有該物件從未(在本次虛擬機中) 被序列化過, 系統才會將該物件轉換成位元組序列并輸出,
如果某個物件已經序列化過, 程式將只是直接輸出一個序列化編號, 而不是再次重新序列化該物件,

舉例:使用:ObjectOutputStream 類 將自定義類序列化(將類物件型別存盤到硬碟檔案中),該類沒有實作  java.io.Serializable這個 介面的錯誤無法序列化的錯誤演示:

package blogs.blog9;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectOutputStreamTest {
    public static void main(String[] args) {

        ObjectOutputStream objectOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/temp");
            // 1. 創建 ObjectOutputStream 序列化輸出流物件
            // 這里的引數是 OutputStream ,而 FileOutputSteam 實作了該介面
            objectOutputStream = new ObjectOutputStream(fileOutputStream);

            Person person = new Person("Tom",99);

            // 2. 將自定義的Person 物件序列化:存盤到硬碟檔案當中去.
            objectOutputStream.writeObject(person);

            // 3. 重繪
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4 關閉IO資源
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


// 該自定義類沒有實作 java.io.Serializable 介面,無法序列化
class Person {
    String name;
    int age;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

舉例: 自定義類 Person 實作了 Serailaizable 介面,但是沒有顯式定義 serialVersionUID 靜態屬性,而是由Java自動生成的,

package blogs.blog9;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {
    public static void main(String[] args) {

        ObjectOutputStream objectOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/temp");
            // 1. 創建 ObjectOutputStream 序列化輸出流物件
            // 這里的引數是 OutputStream ,而 FileOutputSteam 實作了該介面
            objectOutputStream = new ObjectOutputStream(fileOutputStream);

            Person person = new Person("Tom",99);

            // 2. 將自定義的Person 物件序列化:存盤到硬碟檔案當中去.
            objectOutputStream.writeObject(person);

            // 3. 重繪
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4 關閉IO資源
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


// 該自定義類沒有實作 java.io.Serializable 介面,無法序列化
class Person implements Serializable {
    String name;
    int age;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.4.2 物件的反序列化

舉例: 使用 ObjectInputStream 類讀取存盤到物件的序列化檔案 temp ,


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/temp"); // 注意檔案后綴

            // 1. 創建反序列化輸入物件
            // 注意: 這里的引數是: InputSteam ,而FileInputStream 實作了該介面
            objectInputStream = new ObjectInputStream(fileInputStream);

            // 2.讀取其中序列化物件資訊
            // Object object = objectInputStream.readObject();
            // 這里因為我們知道該檔案中序列化的物件是 Person型別的,所以可以直接進行強制轉化
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    
}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    int age;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

4.4.3 不顯式定義 serialVersionUID 值的問題

不指定serialVersionUID出現的問題

  1. 如果我們自定義的類想要序列化,實作了  java.io.Serializable 介面,但是卻沒有顯式定義 serialVersionUID 靜態屬性,而是由Java自動 生成的,但是該自動生成的 serialVersionUID,我們無法修改,當我們對應的類中是屬性發生了改變時,由于我們沒有自行顯式定義 serialVersionUID靜態屬性,導致Java會重新生成一個新的 serialVersionUID 屬性值,該屬性值與原先就的屬性值的版本不一致,導致再次想序列化物件時,會出錯,
  2. 如果我們在不同的電腦上都有一個Person類, 我們想通過網路進行傳輸, 那就必須現在A電腦實作序列化, 在B電腦實作反序列化, 那么如果我們不指定serialVersionUID就就有可能反序列化失敗
  3. 在實體開發程序中, 我們的類會經常改變, 如果我們使用JVM幫我們自動生成的serialVersionUID, 那么如果這個類已經有一些序列化物件, 那我們一旦修改了這個類,這些物件反序列化的時候就都會報錯,

為什么要顯式定義serialVersionUID的值?

如果不顯示指定 serialVersionUID, JVM 在序列化時會根據屬性自動生成一個 serialVersionUID, 然后與屬性一起序列化,再進行持久化或網路傳輸,

在反序列化時,JVM 會再根據屬性自動生成一個新版 serialVersionUID,然后將這個新版 serialVersionUID 與序列化時生成的舊版 serialVersionUID 進行比較,如果相同則反序列化成功, 否則報錯.

如果顯示指定了 serialVersionUID, JVM 在序列化和反序列化時仍然都會生成一個 serialVersionUID, 但值為我們顯示指定的值, 就會進行serialVersionUID值的覆寫,這樣在反序列化時新舊版本的 serialVersionUID 就一致了,

舉例: 在沒有自定義 serialVersionUID 的值的前提:這里我們修改了Person類中是屬性,多加一個 int id 屬性后,反序列化,讀取存盤序列化檔案時的報錯提示:java.io.InvalidClassException:

package blogs.blog9;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/temp"); // 注意檔案后綴

            // 1. 創建反序列化輸入物件
            // 注意: 這里的引數是: InputSteam ,而FileInputStream 實作了該介面
            objectInputStream = new ObjectInputStream(fileInputStream);

            // 2.讀取其中序列化物件資訊
            // Object object = objectInputStream.readObject();
            // 這里因為我們知道該檔案中序列化的物件是 Person型別的,所以可以直接進行強制轉化
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    int age;

    // 多加一個屬性
    int id;

    public Person() {

    }

    public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}


顯式定義serialVersionUID 的值:

  1. 定義的 serialVersionUID 的值是 private 私有化的,static 靜態的所以物件共用,final 常量不可修改的,值是 Long 型別的
private static final long serialVersionUID = -6849794470754667710L;
  1. Java比較判斷版本號時,是在同一個專案中,先判斷類名是否相等,類名相等的前提下,再比較判斷對應的類名的版本號,所以版本號可以不用設定的太大 ,1L 也是可以的,
  2. 注意靜態屬性名是:serialVersionUID 這是固定的,不要修改了,
private static final long serialVersionUID = 1L;

舉例: 在顯式定義 serialVersionUID 的值的前提:這里我們修改了Person類中是屬性,多加一個 int id 屬性后,反序列化,讀取存盤序列化檔案時的,讀取正常,注意: 要先將我們已經顯式定義的 serialVersionUID 的值,先序列化一下,再添加 int id 屬性,后在反序列化,不然你就是還是用的是 serialVersionUID 由Java自行生成的版本號,還是會報錯的,

package blogs.blog9;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/temp"); // 注意檔案后綴

            // 1. 創建反序列化輸入物件
            // 注意: 這里的引數是: InputSteam ,而FileInputStream 實作了該介面
            objectInputStream = new ObjectInputStream(fileInputStream);

            // 2.讀取其中序列化物件資訊
            // Object object = objectInputStream.readObject();
            // 這里因為我們知道該檔案中序列化的物件是 Person型別的,所以可以直接進行強制轉化
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    int age;

    // 多加一個屬性
    int id;

    // 顯式定義了 serialVersionUID 的版本號值
    private static final long serialVersionUID = 1L;


    public Person() {

    }


     public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }


}

4.4.3.1  設定不被序列化的型別

在一些特殊的場景下, 如果一個類里包含的某些實體變數是敏感資訊, 例如銀行賬戶資訊等, 這時不希望系統將該實體變數值進行序列化; 或者某個實體變數的型別是不可序列化的, 因此不希望對該實體變數進行遞回序列化, 以避免引java.io.NotSerializableException 例外,

通過在實體變數前面使用 transient 關鍵字修飾, 可以指定 Java 序列化時無須理會該實體變數, 如下 Person 類與前面的 Person 類幾乎完全一樣, 只是它的 age 使用了 transient 關鍵字修飾,

簡單的說就是:被 transient 關鍵字修飾的屬性不會被序列化到檔案中,更不會被反序列化讀取到

舉例: 將 Person 類中的 age 被 transizent 修飾,不會被序列化,

transient int age;

先序列化一下:將物件存盤到檔案中

package blogs.blog9;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {

    public static void main(String[] args) {

        ObjectOutputStream objectOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/temp");
            // 1. 創建 ObjectOutputStream 序列化輸出流物件
            objectOutputStream = new ObjectOutputStream(fileOutputStream);

             Person person = new Person("Tom", 99,001);

             //2. 將自定義的Person 物件序列化:存盤到硬碟檔案當中去.
            objectOutputStream.writeObject(person);

            // 3. 重繪
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4 關閉IO資源
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    transient int age;  // 被transient 修飾不會序列化

    // 多加一個屬性
    int id;

    // 顯式定義了 serialVersionUID 的版本號值
    private static final long serialVersionUID = 1L;


    public Person() {

    }


     public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }


}

再反序化查看效果:

package blogs.blog9;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/temp"); // 注意檔案后綴

            // 1. 創建反序列化輸入物件
            // 注意: 這里的引數是: InputSteam ,而FileInputStream 實作了該介面
            objectInputStream = new ObjectInputStream(fileInputStream);

            // 2.讀取其中序列化物件資訊
            // Object object = objectInputStream.readObject();
            // 這里因為我們知道該檔案中序列化的物件是 Person型別的,所以可以直接進行強制轉化
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    transient int age;

    // 多加一個屬性
    int id;

    // 顯式定義了 serialVersionUID 的版本號值
    private static final long serialVersionUID = 1L;


    public Person() {

    }


     public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }


}


注意:同樣的被 **static** 修飾為靜態的屬性也不會被序列化

static 屬性為什么不會被序列化?

因為序列化是針對實體物件而言的,而 static 屬性優先于物件存在, 隨著類的加載而加載, 所以不會被序列化.

是不是有人會問, serialVersionUID 也被 static 修飾, 為什么 serialVersionUID 會被序列化?

其實 serialVersionUID 屬性并沒有被序列化, JVM 在序列化物件時會自動生成一個 serialVersionUID, 然后將我們顯示指定的 serialVersionUID 屬性值賦給自動生成的 serialVersionUID,

補充:

一次性序列化多個物件:可以,可以將物件放到集合當中,序列化集合,如下

創建多個 Person 物件,并存盤到 List 集合中并,反序列化到 temp 檔案中,



import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        ObjectOutputStream objectOutputStream = null;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("src/blogs/blog9/temp");

            // 1. 創建 ObjectOutputStream 序列化輸出流: 注意構造器的引數是 OutputStream
            objectOutputStream = new ObjectOutputStream(fileOutputStream);

            // 2. 創建多個 Person 物件型別,存盤到 List 集合中
            Person person1 = new Person("Tom",18,001);
            Person person2 = new Person("zhangsan",28,002);
            Person person3 = new Person("lisi",20,003);

            // <Person>泛型限定存盤物件型別
            List<Person> list = new ArrayList<Person>();
            // 添加元素
            list.add(person1);
            list.add(person2);
            list.add(person3);

            // 3. 序列化:將存盤到 List集合中的元素,寫入到 temp 硬碟檔案中
            objectOutputStream.writeObject(list);

            // 4. 重繪:將遺留在記憶體中沒有寫入到檔案的資訊,強制全部寫入到檔案中,防止資料丟失
            objectOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5. 關閉IO資源
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    transient int age;

    // 多加一個屬性
    int id;

    // 顯式定義了 serialVersionUID 的版本號值
    private static final long serialVersionUID = 1L;


    public Person() {

    }


     public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }


}

讀取檔案中的多個序列化物件 :將我存盤到 List 集合中的多個物件,從檔案中讀取到記憶體當中

package blogs.blog9;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class ObjectOutputStreamTest {
    public static void main(String[] args) {
        ObjectInputStream objectInputStream = null;
        try {
            FileInputStream fileInputStream = new FileInputStream("src/blogs/blog9/temp");

            // 1.創建 ObjectInputStream 反序列化輸入流物件,構造器引數是 InputStream
            objectInputStream = new ObjectInputStream(fileInputStream);

            // 2. 讀取存盤序列資訊的檔案到記憶體當中
            // 因為這里我們知道該檔案中存盤的是 List<Person> 集合型別的所以可以直接強制轉化
            List<Person> list = (List<Person>)objectInputStream.readObject();

            // 遍歷集合
            for (Person person : list) {
                System.out.println(person);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 3. 關閉IO資源
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}


// 該自定義類實作 java.io.Serializable 介面
class Person implements Serializable {
    String name;
    transient int age;

    // 多加一個屬性
    int id;

    // 顯式定義了 serialVersionUID 的版本號值
    private static final long serialVersionUID = 1L;


    public Person() {

    }


     public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }


}

5. 實用案例:

拷貝目錄以及目錄下的所有檔案

package day26;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 拷貝目錄以及目錄下的所有檔案
 */
public class CopyAll {


    /**
     * 拷貝目錄下的所有檔案
     * @param srcFile
     * @param destFile
     */
    private static void copyDir(File srcFile, File destFile) {
        // 遞回結束條件,(是檔案是最后一層了,不用再遞回下去了)
        // 判斷該拷貝物件是否是檔案,
        if(srcFile.isFile()) {
            // srcFile如果是檔案的話,將檔案拷貝完,就回傳了遞回結束,因為檔案都是最后的東西的
            // 是檔案拷貝,一邊讀一邊寫
            FileInputStream in = null;
            FileOutputStream out = null;

            try {
                in = new FileInputStream(srcFile);

                String path = destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() :
                        "\\" + srcFile.getAbsolutePath().substring(3);
                out = new FileOutputStream(destFile);
                // 一邊讀,一邊寫
                byte[] bytes = new byte[1024*1024];  // 一次復制1mb
                int readCout = 0;

                while((readCout = in.read(bytes)) != -1) {
                    out.write(bytes,0,readCout);  // 讀到多少回傳多少
                }

                // 重繪
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

            return;
        }

        // 獲取被拷貝的目錄下面的子目錄:該目錄下的目錄和檔案
        File[] files = srcFile.listFiles();

        for (File file : files) {

            // 判斷該檔案是否在同一個目錄下
            if(file.isDirectory()) {
                // D:/curse/02-javaSe/ 被拷貝的目錄
                // C:/curse/02-javaSe/ 到的目錄  兩者之間的目錄盤必須是一樣的

                // 獲取所有檔案的(包括目錄和檔案)絕對路徑
                String srcDir = file.getAbsolutePath();
                String destDir = file.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() :
                        "\\" + srcDir.substring(3);

                File newFile = new File(destDir);
                // 判斷該 目錄是否存在,不存在創建
                if(!newFile.exists()) {
                    newFile.mkdirs();  // 多重目錄的創建
                }

            }
            // 遞回呼叫
            copyDir(file,destFile);  // 將其中的檔案/目錄,拷貝到 destFile目錄中
        }
    }
}

6. 檔案 I/O(nio.2)

關于這部分內容,大家可以移步至:??????  https://zq99299.github.io/java-tutorial/essential/io/fileio.html 觀看學習,

7. 總結:

  1. File 類中的方法的使用,
  2. I/O 流的分類,輸入流:將硬碟檔案的資料資訊寫入到記憶體當中;輸出流:將記憶體當中的資訊寫入到硬碟檔案中,
  3. Java的IO流共涉及40多個類,實際上非常規則,都是從如下 Java Io 流四大家族  個 抽象基類派生的,
  4. I/O流的四大首領:java.io.InputStream ,java.io.OutputStream,java.io.Reade,java.io.Writer
  5. 在Java中只要 類名是以 "stream" 結尾的都是位元組流,以 "Reader/Writer"結尾的都是字符流,
  6. 位元組流和字符流的用法幾乎完全一樣, 區別在于位元組流和字符流操作的資料單元的不同:位元組流是 8 位的位元組, 而字符流操作的資料單元是 16 位的字符,其中還有一點不同的就是:
  • 字符流:只能讀取操作文本檔案,因為字符流讀取的是檔案中的 char 字符資訊, .c,.java,.c++,.txt 等等這些都是文本檔案不僅僅只是 txt檔案,而特別注意的是 :.wrod 不是文本檔案,wrod中的文字是經過特殊處理的存在一定的規范格式,不是純文本檔案,
  • 位元組流:可以操作任何的檔案,因為位元組流讀取的是二進制資訊,讀取1個位元組byte,等同于一次讀取8個二進制,這種流是萬能的,什么型別的檔案都可以讀取到,因為檔案都是有二進制組成的,包括: 文本檔案,圖片,聲音檔案,
  1. 靈活使用:位元組流,字符流,緩沖流,轉換流,標準輸入、輸出流,資料流
  2. 序列化:把物件轉換為位元組序列的程序稱為物件的序列化,將物件寫入到硬碟檔案中
    反序列化:把位元組序列恢復為物件的程序稱為物件的反序列化,將存盤到硬碟檔案中的物件,讀取到記憶體當中,
  3. 如果某個類的屬性不是基本資料型別或 String 型別,而是另一個 參考型別,那么這個參考型別必須是可序列化的,否則擁有該型別的Field 的類也不能序列化,
  4. 如果需要讓某個物件支持序列化機制,則必須讓物件所屬的類及其屬性是可序列化的,為了讓某個類是可序列化的,該類必須實作如下兩個介面(java.io.Serializable(常用),java.io.Externalizable)之一, 否則,會拋出NotSerializableException例外,
  5. 建議顯式定義 serialVersionUID 版本值,
private static final long serialVersionUID = -6849794470754667710L;
  1. transient 關鍵字修飾的屬性不會被序列化, static 屬性也不會被序列化.

10. 最后:

?????? ????????????????????   感謝以下大佬提供的參考資料    ???????????????????? ??????

【1】: https://fighter3.blog.csdn.net/article/details/103554407

【2】:https://zq99299.github.io/java-tutorial/essential/io/streams.html

【3】:https://blog.csdn.net/Shangxingya/article/details/113744323

限于自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,后會有期,江湖再見 !!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543610.html

標籤:其他

上一篇:類和類的定義

下一篇:《分布式技術原理與演算法決議》學習筆記Day08

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more