字元流
我們位元組流讀字元資料,需要人工去做位元組和字元資料的轉換。我們自己轉換,我們不清楚哪些位元組應該一起轉,這些位元組應該怎麼轉。都不清楚。是以,在轉換的過程中,就有可能出錯。于是,就有了字元流,專門解決這個問題。
字元流 = 位元組流 + 編碼表;字元流的底層會用普通位元組流進行讀寫,然後字元流僅僅是做了把位元組與字元進行轉換的事情。這樣就不需要程式員自己來轉了。就不會出錯了!
編碼表
編碼表:可以看做是一個字典。這本字典翻譯是 人類的字元 和 機器語言(二進制) 之間對應關系。
編碼表:就是人類生活的字元和計算機二進制的對照關系表。
ASCII:最早的,美國人制定的一張碼表。包含字母、數字、符号、拉丁文。它用1個位元組的最低7位表示資料。
a 97 01100001
ISO8859-1:歐洲制定的碼表。相容ASCII。多了一些歐洲的語言。它用1個位元組的全部來表示資料。沒有未知字元的!
GB2312:識别數千中文
GBK:識别2萬多中文,2個位元組表示一個字元。
GB18030:GBK的更新
Unicode:世界計算機協會制定通用碼表,2個位元組表示一個字元。
UTF-8:Unicode更新版。能用1個位元組表示的就用1個位元組,要用兩個位元組的就用2個位元組,能用3個位元組表示的就用3個位元組。漢字基本都是3個位元組
編碼和解碼
* 編碼:把我們能看懂的變成看不懂的(字元 --> 位元組)
* byte[] getBytes() 使用平台的預設字元集将此 String 編碼為 byte 序列
* byte[] getBytes(String charsetName)使用指定的字元集将此 String 編碼為 byte 序列
* 解碼:把我們看不懂的變成能看懂的(位元組 --> 字元)
* String(byte[] bytes) 通過使用平台的預設字元集解碼指定的 byte 數組,構造一個新的 String
* String(byte[] bytes, String charsetName)通過使用指定的 charset 解碼指定的 byte 數組,構造一個新的 String
* 亂碼:
* 原因:編碼和解碼的碼表不一緻
public static void main(String[] args) throws UnsupportedEncodingException {
encode();
System.out.println("------------------");
decode();
System.out.println("------------------");
luanCode();
}
// 示範:亂碼
public static void luanCode() throws UnsupportedEncodingException {
String str = "你好";
// 用UTF-8編碼
byte[] bytes = str.getBytes("UTF-8");
// 把這個數組給别人,讓他解碼,用GBK
String s = new String(bytes,"GBK");
System.out.println(s);// 浣犲ソ
// 先用GBK編碼
byte[] bytes2 = s.getBytes("GBK");
// 在用UTF-8解碼
String s2 = new String(bytes2,"UTF-8");
System.out.println(s2);// 你好
}
// 示範:解碼
public static void decode() throws UnsupportedEncodingException {
// 準備位元組數組
byte[] gbk_bytes = {-60, -29, -70, -61};
// String(byte[] bytes) 通過使用平台的預設字元集解碼指定的 byte 數組,構造一個新的 String
String s1 = new String(gbk_bytes);
System.out.println(s1);// 你好
byte[] utf_bytes = {-28, -67, -96, -27, -91, -67};
String s2 = new String(utf_bytes,"UTF-8");
System.out.println(s2);
}
// 示範:編碼
public static void encode() throws UnsupportedEncodingException {
String s = "你好";
// 使用平台預設編碼集進行編碼
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));// [-60, -29, -70, -61]
// 用GBK編碼
byte[] bgk_bytes = s.getBytes("GBK");
System.out.println(Arrays.toString(bgk_bytes));// [-60, -29, -70, -61]
// 用UTF-8編碼
byte[] utf_bytes = s.getBytes("UTF-8");
System.out.println(Arrays.toString(utf_bytes));// [-28, -67, -96, -27, -91, -67]
}
運作結果:
字元流
Writer是寫入字元流的抽象類
OutputStreamWriter(輸出轉換流)是字元流通向位元組流的橋梁,将要寫入流的字元編碼成位元組。
構造方法摘要OutputStreamWriter(OutputStream out) 建立使用預設字元編碼的 OutputStreamWriter。OutputStreamWriter(OutputStream out, String charsetName) 建立使用指定字元集的 OutputStreamWriter。
字元流的底層還是用位元組流進行讀寫,字元流僅僅是做了位元組和字元的轉換(編碼和解碼)。接收一個位元組流,形成了字元流。把位元組流轉換為字元流!接收字元資料,首先要把字元資料變成位元組資料,然後再用普通位元組流,把資料寫到硬碟。實作了字元資料轉換為位元組資料。
Writer的5種寫出功能
* public void write(int c) 寫出一個字元
* public void write(char[] cbuf) 寫出字元數組
* public void write(char[] cbuf,int off,int len) 寫出字元數組cbuf中,從off開始,共len個字元
* public void write(String str) 寫出字元串
* public void write(String str,int off,int len)寫出字元串,從off開始,共len個字元
public static void main(String[] args) throws IOException {
// 建立輸出流,關聯目标檔案
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw2.txt"));
// 寫出資料
// public void write(int c) 寫出一個字元
osw.write('a');
osw.write('你');
osw.write('9');
// public void write(char[] cbuf) 寫出字元數組
char[] cbuf = {'h','e','l','l','o','柳','岩'};
osw.write(cbuf);
// public void write(char[] cbuf,int off,int len) 寫出字元數組cbuf中,從off開始,共len個字元
// 需求:寫出:lo柳岩
osw.write(cbuf, 3, 4);
String str = "helloworld";
// public void write(String str) 寫出字元串
osw.write(str);
// public void write(String str,int off,int len)寫出字元串,從off開始,共len個字元
// 需求:寫出owo
osw.write(str, 4, 3);
// 釋放資源
osw.close();
}
寫入結果:
Reader是用于讀取字元流的抽象類
InputStreamReader(輸入轉換流)是位元組流通向字元流的橋梁,将要讀取位元組并将其解碼成字元。
構造函數:InputStreamReader(InputStream in) 建立一個使用預設字元集的 InputStreamReaderInputStreamReader(InputStream in, String charsetName)建立使用指定字元集的 InputStreamReader。
字元流底層還是用的位元組流,字元流僅僅做字元和位元組的轉換!接收一個位元組流,生成的一個字元流。把位元組流轉換為字元流。底層會用位元組流去硬碟讀取位元組資料,把位元組資料轉換為字元資料,然後傳回。實作了把位元組資料轉換為字元資料。
字元流複制文本檔案
使用字元轉換流複制文本檔案,複制EncodeDecodeDemo.java 到 copy.java
public static void main(String[] args) throws IOException {
// 建立輸入流,關聯源檔案
InputStreamReader isr = new InputStreamReader(new FileInputStream("EncodeDecodeDemo.java"));
// 建立輸出流,關聯目标檔案
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("copy.java"));
// 讀寫資料
// 一次一個字元
// int c = 0;
// while((c = isr.read()) != -1){
// osw.write(c);
// }
// 一次多個字元
char[] cbuf = new char[1024];
int len = 0;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf, 0, len);
}
// 釋放資源
isr.close();
osw.close();
}
轉換流的便捷形式:FileWriter和FileReader
/*
* 示範:FileWriter的使用
* public FileWriter(String pathname)
* public FileWriter(File file)
* public FileWriter(String pathname,boolean append)
* public FileWriter(File file,boolean append)
*/
public class FileWriterDemo01 {
public static void main(String[] args) throws IOException {
// 建立輸出流,關聯目标檔案
FileWriter fw = new FileWriter("fw.txt");
// 寫出資料
fw.write("hello,虎哥");
// 釋放資源
fw.close();
}
}
/*
* 示範:FileReader便捷類的使用
* public FileReader (String pathname)
* public FileReader (File file)
*/
public class FileReaderDemo01 {
public static void main(String[] args) throws IOException {
// 建立輸入流,關聯源檔案
FileReader fr = new FileReader("copy.java");
// 讀取資料
char[] cbuf = new char[1024];
int len = 0;
while((len = fr.read(cbuf)) != -1){
System.out.print(new String(cbuf,0,len));
}
// 釋放資源
fr.close();
}
}
便捷類複制檔案
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
// 建立輸入流,關聯源檔案
FileReader fr = new FileReader("copy.java");
// 建立輸出流,關聯目标檔案
FileWriter fw = new FileWriter("copy2.java");
// 讀寫資料
char[] cbuf = new char[1024];
int len = 0 ;
while((len = fr.read(cbuf)) != -1){
fw.write(cbuf, 0, len);
}
// 釋放資源
fr.close();
fw.close();
}
}
便捷類和父類的關系
父類:
OutputStreamWriter = OutputStream + 任意的編碼表
InputStreamReader = InputStream + 任意編碼表
子類:
FileWriter = FileOutputStream + 本地預設編碼表
FileReader = FileInputStream + 本地預設編碼表
字元緩沖流
為了高效的進行讀寫,Java提供了字元的緩沖流,也就是在緩沖流的内部維護了緩沖數組,這樣以來就能實作字元資料的高效讀寫!普通字元流,内部有一個緩沖數組,這是一個位元組數組。作用是減少了流與硬碟之間的互動次數。提高了效率。緩沖字元流,内部有一個緩沖數組,這是一個字元數組。有什麼作用?
普通字元流,每次write都會調用一次編碼轉換器,調用編碼轉換器,本身也是非常消耗資源的!
緩沖字元流,當我們需要write時,不會去調用編碼轉換器,而是把要寫的字元存入緩沖區,等緩沖區中足夠多,然後一次性調用編碼轉換器,把資料轉為位元組。這樣就減少了調用編碼轉換器的次數,進而提高了效率。
BufferedReader的特有功能:String readLine()讀取一個文本行
// 普通方法複制
public static void method_01() throws IOException {
// 建立輸入流,關聯源檔案
BufferedReader br = new BufferedReader(new FileReader("copy.java"));
// 建立輸出流,關聯目标檔案
BufferedWriter bw = new BufferedWriter(new FileWriter("copy3.java"));
// 讀寫資料
char[] cbuf = new char[1024];
int len = 0;
while ((len = br.read(cbuf)) != -1) {
bw.write(cbuf, 0, len);
bw.flush();
}
// 釋放資源
br.close();
bw.close();
}
// 特有功能複制
public static void method_02() throws IOException {
// 建立輸入流,關聯源檔案
BufferedReader br = new BufferedReader(new FileReader("copy.java"));
// 建立輸出流,關聯目标檔案
BufferedWriter bw = new BufferedWriter(new FileWriter("copy4.java"));
// 讀寫資料
String line = null;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
// 釋放資源
br.close();
bw.close();
}
IO流的總結
1、IO流的繼承體系
InputStream:位元組輸入流:FileInputStream、BufferedInputStream
OutputStream:位元組輸出流FileOutputStream、BufferedOutputStream
Reader:字元輸入流InputStreamReader、FileReader、BufferedReader
Writer:字元輸出流OutputStreamWriter、FileWriter、BufferedWriter
2、厘清構造函數
1)四大基本流,他們都是直接關聯檔案或路徑名FileInputStream、FileOutputStream、FileReader、FileWriter
2)裝飾流,他們的構造函數接收本體系的頂級父類BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
3)轉換流,構造函數,接收對應的位元組流頂級父類。InputStreamReader、OutputStreamWriter
3、如何選擇具體的流?
原則:先選擇基本流。
問題1:我是位元組資料還是字元資料
位元組資料:FileInputStream\FileOutputStream
字元資料:FileReader\FileWriter
問題2:我是讀還是寫?
讀:FileInputStream\FileReader
寫:FileOutputStream\FileWriter
問題3:我是否需要高效緩沖呢?
是:我就用選擇的這個流對應體系的緩沖流包起來。
問題4:如果需要指定編碼表,那麼可以考慮用父類替換對應的便捷類。