【Java】关于Java中的各种流
1 IO流
1.1 概念
input:輸入(讀取)-----> 流:數據(字節/字符) -----> output:輸出(寫入)
輸入:把硬盤中的數據,讀取到內存中使用
輸出:把內存中的數據,寫入到硬盤中保存
內存:臨時存儲
硬盤:永久存儲
1個字符 = 2個字節
1個字節=8個二進制位
頂層父類
| 字節流 | 字節輸入流 InputStream | 字節輸出流 OutputStream |
| 字符流 | 字符輸入流 Reader | 字符輸出流 Writer |
2 字節流
2.1 字節輸出流OutputStream
它是一個抽象類
共性成員方法:
2.1.1 文件字節輸出流FileOutputStream
,extends OutputStream,把內存中的數據寫入硬盤的文件中。
構造方法:創建一個 FileOutputStream 對象。根據參數傳遞的文件/文件路徑,創建一個空文件。把 FileOutputStream 對象指向創建好的文件。
寫入數據的原理(內存 —> 硬盤)
Java程序 —> JVM —>OS —> OS調用寫數據的方法 —>把數據寫入文件中
字節輸出流的使用步驟
把a寫入a.txt文件
public abstract void write(int b)
public static void main(String[] args) throws IOException {FileOutputStream fos = new FileOutputStream("a.txt");fos.write(97);fos.close();}原理:寫數據時會把十進制97轉為二進制1100001?,硬盤中存儲的數據都是字節,1個字節等于8個比特位。文本編輯器在打開文件時會查詢編碼表,把字節轉為字符表示,97—>a。
一次寫入多個字節
public void write(byte[] b)
public void write(byte[] b, int off, int len) :把數組的一部分寫入文件
使用String類的方法把字符串轉換為字節數組。
public static void main(String[] args) throws IOException {FileOutputStream fos = new FileOutputStream(new File("b.txt"));byte[] b = "哈哈哈".getBytes();System.out.println(Arrays.toString(b));fos.write(b);fos.close();}GBK:兩個字節是一個中文
UTF8:三個字節是一個中文
數據的追加寫和換行寫
追加寫的構造方法 append=true
FileOutputStream(String name, boolean append)
FileOutputStream(File file, boolean append)
換行:win【\r\n】mac【/n】linux【/r】
2.2 字節輸入流InputStream
它是一個抽象類,是所有字節輸入流類的超類
共性方法
- int read()
- int read(byte[] b)
- void close
2.2.1 文件字節輸入流FileInputStream
作用:把硬盤中的數據,讀取到內存中使用
構造方法
- FileInputStream(String name)
- FileInputStream(File file)
構造方法的作用:
讀取數據原理(硬盤—>內存)
Java程序 —> JVM —>OS —>OS調用讀取數據的方法 —> 讀取文件
字節輸出流的使用步驟
注意:read每調用一次指針都會后移一位,如果while循環括號中不保存讀取的值,輸出的就是跳位讀取的值。
一次讀取一個字節
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("a.txt");int len = 0;while((len = fis.read())!=-1){//讀取一個字節并返回 文件末尾返回-1System.out.print((char) len);}fis.close();}一次讀取多個字節
int read(byte[] b):
注意:byte[]的作用是起緩沖作用,存儲每次讀取到的多個字節。數組長度一般定義為1024(1kb)或者1024的整數值。int返回值是每次讀取的有效字節個數。
原理:創建一個byte數組,數組元素的初始值為0。開始讀取,把讀取出的數據存入byte數組,指針移動,b.length是一次讀取的字節個數。new String()可以把byte數組轉換為字符串輸出。read方法的返回值是有效讀取字節個數。
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("a.txt");byte[] bytes = new byte[1024];int len = 0;while((len = fis.read(bytes))!=-1){System.out.print(new String(bytes,0,len));//打印byte中存的有效位}}
2.3 字節流練習-文件復制
原理
創建一個輸入流的對象,再創建一個輸出流的對象,然后輸入流對象讀取文件內容,寫入輸出流指向的對象處。
注意:應該先關閉輸出流(寫),再關閉輸入流(讀)。因為寫完了一定讀完了,但讀完了不一定寫完了。應該先開啟輸入流,再開啟輸出流,先讀后寫。
2.4 字節流讀文件存在的問題
在讀取中文字符的時候,可能不會顯示完整的字符,因為一個中文可能占用多個字節。按字節流讀取,每次只能讀取字符的一部分。所以文本文件一般用字符流進行讀寫。
GBK:1個中文2個字節
UTF8:1個中文3個字節
3 字符流
3.1 字符輸入流Reader
字符輸入流類最頂層的父類,是一個抽象類。
共性方法
- int read()
- int read(char[] c)
- void close()
3.1.1 文件字符輸入流FileReader
作用:把硬盤文件中的數據以字符的方式讀取到內存中
構造方法:創建一個FileReader對象,把FileReader對象指向要讀取的文件/文件路徑
- FileReader(String name)
- FileReader(File file)
使用步驟
一次讀取一個字符
public static void main(String[] args) throws IOException {FileReader fd = new FileReader("a.txt");int len = 0;while((len = fd.read())!=-1){System.out.print((char) len);}fd.close();}一次讀取多個字符
字符數組 —> 字符串 new String(char[], int off, int len)構造方法
public static void main(String[] args) throws IOException {FileReader fd = new FileReader("a.txt");char[] c = new char[1024];int len = 0;while((len = fd.read(c))!=-1){System.out.print(new String(c,0,len));}fd.close();}3.2 字符輸出流Writer
共性方法
- void write(int c) 寫入單個字符。
- void write(char[] cbuf) 寫入字符數組。
- abstract void write(char[] cbuf, int off, int len) 寫入字符數組的某一部分,off數組的開始索引,len寫的字符個數。
- void write(String str) 寫入字符串。
- void write(String str, int off, int len) 寫入字符串的某一部分,off字符串的開始索引,len寫的字符個數。
- void flush() 刷新該流的緩沖。
- void close() 關閉此流,但要先刷新它。
3.2.1 文件字符輸出流FileWriter
作用:內存中的字符數據寫入文件中
構造方法
- FileWriter(String name)
- FileWriter(File file)
作用:創建FileWriter對象,根據構造方法中傳遞的文件/文件路徑創建文件,會把FileWriter對象指向創建好的文件中。
使用步驟
寫入單個字符
public static void main(String[] args) throws IOException {FileWriter fw = new FileWriter("a.txt");//綁定寫入位置fw.write(97);//寫入緩沖區 字符-->字節fw.flush();//寫入文件fw.close();//釋放資源}flush和close方法的區別
close在關閉之前會先把內存緩沖區的數據刷新到文件中,但close之后不能繼續使用write方法。flush同樣也是刷新操作,但flush之后可以繼續使用write方法。
其他寫入方法
- void write(char[] c):寫入字符數組
- abstract void write(char[] c, int off, int len):寫入字符數組的某一部分
- void write(String str):寫入字符串
- void write(String str, int off, int len):寫入字符串的某一部分
續寫和換行寫
同字節流輸出
4 IO流的異常處理
在JDK1.7之前可以使用try-catch-finally處理流中的一次
public static void main(String[] args) {FileWriter fw = null;try{fw = new FileWriter("F:\\c.txt",true);for (int i = 0; i < 10; i++) {fw.write("哈哈哈"+i);fw.write("\r\n");}}catch (IOException e){e.printStackTrace();}finally {if(fw != null){try {fw.close();} catch (IOException e) {e.printStackTrace();}}}}JDK7的新特性:可以在try后增加一個()( )(),括號中可以定義流對象,那么這個流對象的作用域就在try中有效,try執行完后,流對象自動釋放,不用寫finally【常用】
public static void main(String[] args) {try(FileWriter fw = new FileWriter("F:\\c.txt",true)){for (int i = 0; i < 10; i++) {fw.write("哈哈哈"+i);fw.write("\r\n");}}catch (IOException e){e.printStackTrace();}System.out.println("哈哈哈哈哈");}JDK9的新特性,try前面可以定義對象,在try后邊的()中可以引入流對象的名稱。try執行完畢后,流對象可以釋放掉,不需要寫finally。【不常用】
public static void main(String[] args) {FileReader fr = new FileReader("c.txt");FileWriter fw = new FileWriter("d.txt");try(fr;fw){char[] c = new char[1024];int len = 0;while((len = fr.read(c))!=-1){fw.write(c);}fw.flush();}catch (IOException e){e.printStackTrace();}System.out.println("哈哈哈哈哈");}5 屬性集
java.util.Properties 繼承于Hashtable,來表示一個持久的屬性集。是唯一一個和IO流相結合的集合。
它使用鍵值結構存儲數據,每個鍵及其對應值都是一個字符串。該類也被許多Java類使用,比如獲取系統屬性時, System.getProperties 方法就是返回一個Properties 對象。
使用Properties存儲數據并遍歷取出
操作字符串的特有方法
- Object setProperty(String key, String value):相當于map.put(k, v)
- String getProperty(String key):相當于map.get(k)
- Set <String> stringPropertyNames():相當于map.keySet()
- 可以使用store方法把集合中的臨時數據,持久化寫入硬盤中存儲
- void store(OutputStream out, String comments):字節輸出流,不能寫中文。
- void store(Writer writer, String comments):字符輸出流,可以寫中文。
comments是注釋,用來解釋保存的文件是做什么的,不可以使用中文,默認是Unicode編碼,一般使用空字符串
使用步驟
- 可以使用load方法,把硬盤中保存的文件,讀取到集合中使用
- void load(InputStream in):字節輸入流,不能讀取含有中文的鍵值對
- void load(Reader r):字符輸入流,可以讀取含有中文的鍵值對
使用步驟
注意
- 鍵值對文件中,鍵和值默認的連接符可以使用=、空格
- 可以使用#來注釋,被注釋的鍵值對不會再讀取
- 鍵和值默認都是字符串,不用再加引號
6 緩沖流
緩沖流,也叫高效流,是對4個基本的FileXxx 流的增強,所以也是4個流,按照數據類型分類:
字節緩沖流: BufferedInputStream , BufferedOutputStream
字符緩沖流: BufferedReader , BufferedWriter
緩沖流的基本原理,是在創建流對象時,會創建一個內置的默認大小的緩沖區數組,通過緩沖區讀寫,減少系統IO次數,從而提高讀寫的效率。
6.1 字節緩沖流
6.1.1 字節緩沖輸出流BfferedOutputStream
繼承自OutputStream,可使用父類共性方法(見2.1)
構造方法:
- BufferedOutputStream(OutputStream out):創建一個新的緩沖輸出流,以數據寫入指定的底層輸出流。
- BufferedOutputStream(OutputSteam out, int size):創建一個新的緩沖輸出流,以將具有指定緩沖區大小的數據寫入指定的底層輸出流。
使用步驟
6.1.2 字節緩沖輸入流BfferedInputStream
繼承自InputStream,可使用父類共性方法(見2.2)
構造方法
- BufferedInputStream(InputStream in):創建一個新的緩沖輸入流,保存參數輸入流in。
- BufferedInputStream(InputSteam in, int size):創建一個具有指定緩沖區大小的BufferedInputStream保存其參數,即輸入流。
使用步驟
6.1.3 基本和緩沖效率比較
public static void main(String[] args) throws IOException {long s = System.currentTimeMillis();FileInputStream fis = new FileInputStream("D:\\1.png");FileOutputStream fos = new FileOutputStream("E:\\1.png");BufferedInputStream bis = new BufferedInputStream(fis);BufferedOutputStream bos = new BufferedOutputStream(fos);byte[] bytes = new byte[1024];int len = 0;while((len=bis.read(bytes))!=-1){bos.write(bytes,0,len);}bos.close();bis.close();long e = System.currentTimeMillis();System.out.println(e-s);}文件復制4MB的ppt的效率比較:
基本流(一次讀一個字節):17757 ms
緩沖流(一次讀一個字節):121 ms
基本流 + 數組緩沖區(一次讀多個字節):27 ms
緩沖流 + 數組緩沖區(一次讀多個字節):9 ms
6.2 字符緩沖流
6.2.1 字符緩沖輸出流BufferedWriter
繼承自Writer,可使用父類共性方法(見3.2)
構造方法
- BufferedWriter(Writer out):創建一個使用默認大小輸出緩沖區的緩沖字符輸出流。
- BufferedWriter(Writer out, int size):創建一個使用指定大小輸出緩沖區的緩沖字符輸出流。
特有方法
- void newLine():寫入一個行分隔符,它會根據不同的操作系統,獲取不同的行分隔符。
使用步驟
6.2.2 字符緩沖輸入流BufferedReader
繼承自Reader,可使用父類共性方法(見3.1)
構造方法
- BufferedReader(Reader in):創建一個使用默認大小輸入緩沖區的緩沖字符輸入流。
- BufferedReader(Reader in, int size):創建一個使用指定大小輸入緩沖區的緩沖字符輸入流。
特有方法
- String readLine():讀取一行文本,讀取一行數據,流末尾返回null,readLine不讀換行符
使用步驟
6.3 緩沖流練習-文本排序
b.txt內容:
7.77777777777777
3.張三3333333333
5.王五5555555555
1.大一1111111111
4.李四4444444444
2.小二2222222222
6.六六6666666666
7 轉換流
7.1 字符編碼和字符集
字符編碼
計算機中儲存的信息都是用二進制數表示的,而我們在屏幕上看到的數字、英文、標點符號、漢字等字符是二進制數轉換之后的結果。按照某種規則,將字符存儲到計算機中,稱為編碼 。反之,將存儲在計算機中的二進制數按照某種規則解析顯示出來,稱為解碼 。比如說,按照A規則存儲,同樣按照A規則解析,那么就能顯示正確的文本f符號。反之,按照A規則存儲,再按照B規則解析,就會導致亂碼現象。
字符編碼Character Encoding : 就是一套自然語言的字符與二進制數之間的對應規則。
字符集
字符集 Charset :也叫編碼表。是一個系統支持的所有字符的集合,包括各國家文字、標點符號、圖形符號、數字等。計算機要準確的存儲和識別各種字符集符號,需要進行字符編碼,一套字符集必然至少有一套字符編碼。常見字符集有ASCII字符集、GBK字符集、Unicode字符集等。
ASCII是最基本的編碼表,GBK是中文編碼表
Unicode是萬國碼,兼容各種語言,是應用中優先采用的編碼。
7.2 編碼常見問題
例如:在IDEA中,使用FileReader 讀取項目中的文本文件。由于IDEA的設置,都是默認的UTF-8編碼,所以沒有任何問題。但是,當讀取Windows系統中創建的文本文件時,由于Windows系統的默認是GBK編碼,就會出現亂碼。
解決方法:使用轉換流
7.3 轉換流相關內容
字節轉換為字符(解碼)
FileInputStream —> 查詢編碼表 —> FileReader
7.3.1 OutputStreamWriter
字符流通向字節流的橋梁
可以指定編碼格式
寫入你想寫入的編碼格式的文件中
構造方法
- OutputStreamWriter(OutputStream out):創建使用默認字符編碼的OutputStreamWriter(utf8)
- OutputStreamWriter(OutputStream out, String charSet):創建使用指定字符編碼的OutputStreamWriter
使用步驟
你好 GBK 4字節 UTF8 6字節
public static void main(String[] args) throws IOException {OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");osw.write("你好");osw.close();}7.3.2 InputStreamReader
字節流通向字符流的橋梁
可以指定編碼格式
讀取字節解碼為字符 把看不懂的變成看得懂的
構造方法
- InputStreamReader(InputStream in):創建使用默認字符編碼的OutputStreamWriter(utf8)
- InStreamReader(InputStream in, String charSet):創建使用指定字符編碼的InputStreamReader
使用步驟
注意:如果文件編碼和指定編碼不一致,則會發生亂碼
public static void main(String[] args) throws IOException {InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8.txt"),"utf-8");int len = 0;while((len = isr.read())!=-1){System.out.print((char)len);}isr.close();}7.4 練習:轉換文件編碼
將GBK編碼的文本文件,轉換為UTF8編碼的文本文件
public static void main(String[] args) throws IOException {InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"gbk");OutputStreamWriter osr = new OutputStreamWriter(new FileOutputStream("gbk2utf8.txt"),"utf-8");int len = 0;while((len = isr.read()) != -1){osr.write(len);}osr.close();isr.close();}8 序列化和反序列化
8.1 概念
ObjectOutputStream對象的序列化流
把對象以流的方式寫入到文件中保存,叫寫對象,也叫對象的序列化。對象中包含的不僅是字符,使用字節流。
ObjectInputStream對象的反序列化流
把文件中的對象,以流的方式讀取出來,叫讀對象,也叫對象的反序列化。文件保存的都是字節,使用字節流。
8.2 對象的序列化流ObjectOutputStream
構造方法
- ObjectOutputStream(OutputStream out)
特有方法
- void writeObject(Object obj)
使用步驟
序列化和反序列化時,會拋出NotSerializableException沒有序列化異常。
類通過實現Serializable接口以啟動序列化功能,沒有實現此接口的類無法使其任何狀態序列化或反序列化。
Serializable接口也叫標記型接口,進行序列化和反序列化,必須要實現Serializable接口,給類添加標記。在使用時,會檢測是否有標記。
使用前提
Person類的實現 要加上implement Serializable
8.3 對象的反序列化流ObjectInputStream
構造方法
- ObjectInputStream(InputStream in)
特有方法
- void readObject(Object obj) 讀取對象
使用步驟
readObject方法聲明拋出了ClassFoundException,class文件找不到異常,當不存在對象的class文件時拋出異常
使用前提
Person類的實現 要加上implement Serializable
對象的class類文件要存在
【注意!!!】
InvalidClassException異常:序列化之后對類進行更改之后,不重新序列化,而是直接反序列化會出現異常。
因為javac編譯器會把java文件編譯生成class文件。類如果實現了Serializable接口,就會根據類的定義給類的class文件添加一個序列化號serialVersionUID,輸出的文件也會寫入這個ID,反序列化的時候,會使用class文件的序列號和輸出文件的序列號比較,如果ID一致,則反序列化一致,否則會拋出InvalidClassException異常。
而修改了類的定義之后,會給class文件重新編譯生成一個新的序列號,但是輸出文件的序列號沒有改。
解決方案:手動給類加一個序列號。無論是否修改類都不再修改序列號。
自定義類中添加成員常量。
private static final long serialVersionID = 1L;瞬態關鍵字transient
static靜態關鍵字:靜態優先于非靜態加載到內存中,(靜態優先于對象),所以static修飾的成員變量是不能被序列化的,序列化的都是對象。
【在上例中,如果Person類的age成員變量是靜態的,序列化和非序列化age將永遠是初始值0,無法更改】
transient瞬態關鍵字:被它修飾的成員變量不能被序列化。如果想要對象中的成員變量不被序列化,可以使用瞬態關鍵字。
8.4 練習:序列化集合
當我們想在文件中保存多個對象的時候,可以把對象存在集合中,對集合進行序列化和反序列化。
public static void main(String[] args) throws IOException, ClassNotFoundException {ArrayList<Person> list = new ArrayList<>();list.add(new Person("張三",15));list.add(new Person("李四",19));list.add(new Person("王五",20));ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("PersonList.txt"));oos.writeObject(list);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("PersonList.txt"));Object o = ois.readObject();ArrayList<Person> list2 = (ArrayList<Person>) o;for (Person p : list2){System.out.println(p);}ois.close();oos.close();}9 打印流
9.1 PrintSteam類
平時我們在控制臺打印輸出,是調用print方法和println方法完成的,這兩個方法都來自于java.io.PrintStream類,該類能夠方便地打印各種數據類型的值,是一種便捷的輸出方式。
特點:
- 不會拋出IO異常
- 只負責輸出,不負責讀取
- 特有方法print、println
構造方法:
- PrintStream(File file):輸出目的地是一個文件
- PrintStream(OutputStream out):輸出目的地是一個字節輸出流
- PrintStream(String path):輸出目的地是一個文件路徑
注意:
- 如果用繼承父類的write方法寫數據,查看數據的時候就會查詢編碼表,再打印
- 如果使用特有方法print和println則會原樣輸出
可以改變輸出語句的目的地,即打印流的流向。
輸出語句,默認在控制臺輸出,使用System.setOut方向改變輸出語句的目的地改為參數中傳遞的打印流的目的地。
總結
以上是生活随笔為你收集整理的【Java】关于Java中的各种流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RabbitMQ 简介和使用
- 下一篇: 提供一个Android原生的Progre