Java基礎(27)——I/O流相關知識詳解及示例展示(字符流)


版權聲明

  • 本文原創作者:清風不渡
  • 博客地址:https://blog.csdn.net/WXKKang

一、字符流

1、什麼是編碼?

  在我們使用I/O流處理字符的時候,有時會出現亂碼的現象,所以我們在學習字符流之前,應該瞭解一下編碼和亂碼的原因,找到亂碼的根本,這樣就可以很好的解決亂碼的問題,下面就來學習一下吧 ~ ~
  在計算機中,所有的信息都是使用二進制數值進行存儲的,不能直接存儲其他類型的數據。我們經常看到‘中’,‘a’,‘1’等字符數據,爲了實現字符存儲,規定了各個字符對應的二進制數值,這個數值就是字符編碼。例如:當用戶輸入‘a’時就使用97,輸入‘b’時就使用98,然後將這個數值存到計算機中

(1)常見編碼表

  爲了讓計算機可以識別各個國家的文字,我們將這些文字用數值表示,並且文字和數值一一對應,形成了一張表,這就是編碼表。不同的編碼方式就形成了不同的編碼表,使用最多的編碼是ISO8859-1(URL)、GBK(簡體中文)、UTF-8(等價Unicode)
  下面列舉出一些推薦的編碼集,也就是Java採用的編碼方式:
在這裏插入圖片描述

(2)亂碼原因

  編碼:編碼指的是把字符串轉換成計算機識別的字節序列
  解碼:解碼指的是把字節序列轉換爲普通人能看的懂的字符串
  不同國家和地區使用的編碼表是不同的,即使是同一語言的不同編碼表之間也可能是不同的,因此,我們使用什麼樣的編碼寫入數據,就需要使用什麼樣的編碼來讀取數據
  例如:漢字’中’在UTF-8編碼集中對應的編碼值是100,在GBK編碼集中對應的編碼值是150。那麼使用UTF-8編碼寫入’中’時的數值是100,當使用GBK編碼來顯示時結果是錯誤的,因爲在GBK編碼中100並不是漢字’中’的編碼
  在JVM內部,所有字符都使用Unicode編碼表示,和本地操作系統的編碼是沒有關係的。如果只是在JVM內部使用是不會有亂碼發生的,但是字符從外部輸入到JVM,或者是從JVM輸出到外部時,如果選擇了不正確的編碼集,那麼將會出現亂碼。所以解決亂碼問題,就是需要保證解析時,使用的字符集一定要的被解析字符的編碼集要一致
  例如:將漢字字符串”大家好”利用gbk編碼保存在txt文本中,那麼它在計算機底層最終保存時會按照gbk譯碼成1010110。當需要將該字符串讀取出來後必須要按照gbk編碼纔可以將其還原成原本的”大家好”。這就是之前反覆強調的:用什麼編碼寫,就要用什麼編碼讀;如果讀寫時編碼不一致,那麼會產生亂碼

(3)編碼和解碼的主要函數

在這裏插入圖片描述
  注意:在Java中使用System.getProperty(“file.encoding”)可以得到當前系統的默認編碼
  中國大陸Windows系統上默認的編碼爲GBK
  示例如下:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.util.Arrays;

public class Demo {

	public static void main(String[] args) throws Exception {
		//獲取系統默認編碼方式
		System.out.println("系統默認編碼爲:"+System.getProperty("file.encoding"));
		//定義一個字符串常量
		String str = "中";
		//使用系統默認編碼方式進行編碼
		byte[] bytes1 = str.getBytes();
		System.out.println("使用系統默認編碼方式進行編碼:"+Arrays.toString(bytes1));
		//使用“GBK”編碼方式進行編碼
		byte[] bytes2 = str.getBytes("GBK");
		System.out.println("使用“GBK”編碼方式進行編碼:"+Arrays.toString(bytes2));
		//使用“GBK”編碼方式進行編碼
		byte[] bytes3 = str.getBytes("UTF-8");
		System.out.println("使用“GBK”編碼方式進行編碼:"+Arrays.toString(bytes3));
		
		//使用GBK編碼,GBK解碼是正確的
		String str1 = new String(bytes2,"GBK");
		System.out.println("使用GBK編碼,GBK解碼:"+str1);
		//使用GBK編碼,UTF-8解碼會出現亂碼
		String str2 = new String(bytes2,"UTF-8");
		System.out.println("使用GBK編碼,UTF-8解碼:"+str2);
		
		//使用UTF-8編碼,UTF-8解碼是正確的
		String str3 = new String(bytes3,"UTF-8");
		System.out.println("使用UTF-8編碼,UTF-8解碼:"+str3);
		
		//使用UTF-8編碼,GBK解碼會出現亂碼
		String str4 = new String(bytes3,"GBK");
		System.out.println("使用UTF-8編碼,GBK解碼:"+str4);
		
		//GBK兼容GB2312,使用GB2312編碼,GBK解碼是正確的
		String str5 = new String("中國".getBytes("GB2312"),"GBK");
		System.out.println("使用GB2312編碼,GBK解碼:"+str5);
	}
}

  執行結果如下:
在這裏插入圖片描述

2、什麼是字符流?

(1)問題引入

  我們之前所說的流稱之爲字節流,它已字節爲單位處理流中數據,如果處理中文字符呢?我們先來看下面一段代碼的執行情況,代碼如下:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Demo {

	public static void main(String[] args) throws Exception {
		FileOutputStream fileOutputStream1 = new FileOutputStream("D:"+File.separatorChar+"demo.txt");
		fileOutputStream1.write("原創作者:清風不渡".getBytes());
		fileOutputStream1.flush();
		fileOutputStream1.close();
		
		FileInputStream fileInputStream1 = new FileInputStream("D:"+File.separatorChar+"demo.txt");
		FileOutputStream fileOutputStream2 = new FileOutputStream("D:"+File.separatorChar+"demoCopy.txt");
		int len = 0;
		while((len=fileInputStream1.read())!=-1){
			fileOutputStream2.write(len);
		}
		fileInputStream1.close();
		fileOutputStream2.flush();
		fileOutputStream2.close();
	}
}

  執行結果如下:
在這裏插入圖片描述
  在這裏我們可以看到,利用FileInputStream和FileOutputStream來處理漢字的存儲和複製是沒有問題的,但是我們爲什麼要使用字符流呢?讓我們再來看下面這段代碼看看複製過程中的每個字符,代碼如下:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Demo {

	public static void main(String[] args) throws Exception {
		FileInputStream fileInputStream1 = new FileInputStream("D:"+File.separatorChar+"demo.txt");
		FileOutputStream fileOutputStream2 = new FileOutputStream("D:"+File.separatorChar+"demoCopy.txt");
		int len = 0;
		while((len=fileInputStream1.read())!=-1){
			fileOutputStream2.write(len);
			System.out.print((char)len);
		}
		fileInputStream1.close();
		fileOutputStream2.flush();
		fileOutputStream2.close();
	}
}

  執行結果如下:
在這裏插入圖片描述
  我們可以看到,打印結果爲亂碼,那麼是爲什麼呢?來看下一段代碼:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.util.Arrays;

public class Demo {

	public static void main(String[] args) throws Exception {
		String s = "清風不渡";
		byte[] bytes = s.getBytes();
		System.out.println(Arrays.toString(bytes));
		System.out.println("默認編碼方式爲:"+System.getProperty("file.encoding"));
	}
}

  執行結果如下:
在這裏插入圖片描述
  在GBK下一個漢字由兩個字節組成

(2)解決問題

  我們來驗證一下“清風不渡”對應的字節是什麼?在GBK中一個漢字由兩個字節組成,所以"清風不渡"對應的是八個字節;在UTF-8下一個漢字由三個字節組成,所以“清風不渡”對應的是十二個字節
  仔細分析亂碼產生的原因,FileInputStream 的read()一次僅僅讀取一個字節,並且每次都是輸出一個字節的內容,這是中文字符的一部分內容,因此顯示爲亂碼。很顯然,中文就不能夠再按一個字節一個字節的進行處理,而是需要按照一個字符一個字符的進行處理
  由於字節流處理字符時並不方便,那麼就出現了字符流,字符流是以字符爲單位執行讀取和寫入

3、字符流API

  字符流有兩個抽象基類,輸入流是java.io.Reader,輸出流是java.io.Writer,這兩個類都是抽象類,不能直接被實例化。實際開發時,從這兩個抽象基類派生各種流的子類實現,這些子類的名字都是以其父類名作爲子類名的後綴,例如: FileReader、CharArrayReader 等,結構圖如下:
在這裏插入圖片描述

4、Reader

  Reader是字符輸入流的抽象基類,最核心的方法是read,其主要方法如下:
在這裏插入圖片描述

  下面我們就通過字符流來將包含中文字符的文件的內容顯示在控制檯上,代碼如下:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileReader;
import java.io.Reader;

public class Demo {

	public static void main(String[] args) throws Exception {
		String path = "D:"+File.separatorChar+"demo.txt";
		Reader reader = new FileReader(path);
		int len = 0;
		while((len=reader.read())!=-1){
			System.out.print((char)len);
		}
		reader.close();
	}
}

  執行結果如下:
在這裏插入圖片描述

5、Writer

  Writer是字符輸出流的抽象類,最核心的方法是write、flush,其主要方法如下圖:
在這裏插入圖片描述
  下面我們就通過使用字符流來將中文字符或字符串寫入到txt文件中,代碼如下:

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

public class Demo {

	public static void main(String[] args) throws Exception {
		String path = "D:"+File.separatorChar+"demo.txt";
		Writer writer = new FileWriter(path);
		writer.write("清風清風");
		writer.write("不渡不渡".toCharArray());
		writer.flush();
		writer.close();
	}
}

  執行結果如下:
在這裏插入圖片描述
  同字節流存儲數據一樣,當輸出流發現文件不存在時,就會自動創建一個D:/demo.txt。
  注意:這種方式只是創建文件本身,如果文件所在的目錄也不存在,它是不會爲文件創建多級目錄的
  同字節流存儲數據一樣,每次執行都會覆蓋掉之前的數據,如果想要實現追加,實現方法和上一篇中字節流實現追加的方式相同

6、字符緩衝流

(1)Java類

  Java中已經提供了專門的字節緩衝流: BufferedReader 和BufferedWriter,它們內部有一個字符數組實現的緩衝區,通過預減少設備的讀寫次數來提高輸入和輸出的速度,查看API文檔,發現可以指定緩衝區的大小,大多數情況下,默認值就足夠大了
  注意:當然使用緩衝流來進行提高效率時,對於小文件可能看不到性能的提升,但是文件稍微大一些的話,就可以看到實質的性能提升了

(2)readLine();和newLine();

  BufferedReader的readLine()方法實現了一次讀一行文本的操作(不包含換行符號)
  BufferedWriter的newLine()可以輸出一個跨平臺的換行符號"\r\n"
  現在我們通過示例來看看其效果如何——複製txt文件(包含中文字符及換行):

package qfbd.com;
/*
原創作者:清風不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;

public class Demo {

	public static void main(String[] args) throws Exception {
		BufferedReader bufferedReader = new BufferedReader(new FileReader("D:"+File.separatorChar+"demo.txt")); 
		BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:"+File.separatorChar+"demoCopy.txt"));
		String line;
		while((line = bufferedReader.readLine())!=null){
			bufferedWriter.write(line);
			bufferedWriter.newLine();
			//bufferedWriter.write("\r\n");      //只適用於Windows平臺
		}
		bufferedReader.close();
		bufferedWriter.flush();
		bufferedWriter.close();
	}
}

  執行結果如下:
在這裏插入圖片描述

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