Java筆記:IO流
IO概述
資料的傳輸,可以看作一種資料的流動,按照流動的放心,以記憶體為基準,分為輸入input和輸出output,即流向記憶體是輸入流,流出記憶體是輸出流
java中I/O操作,主要是指使用java.io包下的內容,進行輸入輸出操作,輸入也被叫做讀取資料,輸出也被叫做寫出資料
IO分類
根據資料的流向分為:輸入流和輸出流
- 輸入流:把資料從其他裝置上讀取到記憶體的流
- 輸出流:把資料從記憶體中寫出到其他裝置上的流
根據資料的型別分為:位元組流和字元流
- 位元組流:以位元組為單位,讀寫資料的流
- 字元流:以字元為單位,讀寫資料的流
頂級父類
流 | 輸入流 | 輸出流 |
---|---|---|
位元組流 | 位元組輸入流InputStream | 位元組輸出流OutputStream |
字元流 | 字元輸入流Reader | 字元輸出流Writer |
分類概述
- OutputStream:輸出流的抽象類
- FileOutputStream:位元組輸出流
- ObjectOutputStream:序列化流
-
FilterOutputStream :自帶過濾器的輸出流
———- BufferedOutputStream:位元組緩衝輸出流
———- PrintStream:列印流 - InputStream:輸入流的抽象類
- FileInputStream:位元組輸入流
- ObjectInputStream:反序列化流
- FilterInputStream:自帶過濾器的輸入流 —–> BufferedInputStream:位元組緩衝輸入流
- Writer:字元輸出流的抽象類
- BufferedWriter:字元緩衝輸出流
- OutputStreamWriter:轉換編碼的字元輸出流 —–> FileWriter :字元輸出流
- Reader:字元輸入流的抽象類
- BufferedReader:字元緩衝輸入流
- InputStreamReader:轉換編碼的字元輸入流 —–> FileReader:字元輸入流
位元組流
一切文字資料在儲存時,都是以二進位制數字的形式儲存,都是一個一個的位元組,在傳輸時也一樣如此,所以,位元組流可以傳輸任意檔案資料,在操作流的時候,要注意無論使用任何流物件,底層傳輸的始終為二進位制資料
位元組輸出流【OutputStream】
java.io.OutputStream 抽象類是所有位元組輸出流的超類,將指定的位元組資訊寫出到目的地,它定義了位元組輸出流的基本共性功能方法
public void close()
:關閉此輸出流並釋放與此流相關聯的所有系統資源public void flush()
:重新整理此輸出流並強制任何緩衝的輸出位元組被寫出public void write(byte[] b)
:將b.length位元組從指定的位元組陣列寫入此輸出流public void write(byte[] b,int off,int len)
:從指定的位元組陣列寫入len位元組,從偏移量off開始輸出到此輸出流public abstract void write(int b)
:將指定的位元組輸出流
close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源
FileOutputStream類
java.io.FileOutputStream類是檔案輸出流,用於將資料寫出到檔案
構造方法
public FileOutputStream(File file)
:建立檔案輸出流以寫入由指定的File物件表示的檔案public FileOutputStream(String name)
:建立檔案輸出流以指定的名稱寫入檔案
當建立一個流物件的時候,必須傳入一個檔案路徑,若在該路徑下不存在這個檔案,便會建立該檔案,如果有這個檔案會清空這個檔案的資料
程式碼:
public class FileOutputStreamConstructor throws IOException{
public static void main(String[] args){
//使用File物件建立流物件
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
//使用檔名稱建立流物件
FileOutputStream fos1 = new FileOutputStream("b.txt");
}
}
寫出位元組資料
1.寫出位元組:write(int b)方法,每次可以寫出一個位元組資料:public class FOSWrite{
public static void main(String[] args) throws IOException{
//使用檔名稱建立流物件
FileOutputStream fos = new FileOutputStream("fos.txt");
//寫出資料
fos.write(97);
fos.write(97);
fos.write(97);
//關閉資源
fos.close();
//輸出abc
}
}
雖然引數為int型別四個位元組,但是隻會保留一個位元組的資訊寫出
流操作完畢後必須呼叫close方法釋放系統資源
2.寫出位元組陣列:write(byte[] b) 每次可以寫出陣列中的資料,程式碼:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用檔名稱建立流物件
FileOutputStream fos = new FileOutputStream("fos.txt");
//字串轉換為位元組陣列
byte[] b = "張三丰".getBytes();
//寫出位元組陣列資料
fos.write(b);
//關閉資源
fos.close();
//輸出張三丰
}
}
3.寫出指定長度位元組陣列:write(byte[] b,int off,int len),每次寫出從off索引開始len個位元組,程式碼:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用檔名稱建立流物件
FileOutputStream fos = new FileOutputStream("fos.txt");
//字串轉換為位元組陣列
byte[] b = "abcde".getBytes();
//寫出從索引2開始,2個位元組長度
fos.write(b,2,2);
//關閉資源
fos.close();
//輸出cd
}
}
資料追加續寫
以上程式碼每次程式執行都會建立輸出流物件,清空目標檔案中的資料,其實還有保留目標檔案中的資料,新增新資料的方法public FileOutputStream(File file,boolean append)
:建立檔案輸出流以寫入由指定的File物件表示的檔案public FileOutputStream(String name,boolean append)
:建立檔案輸出流以以指定的名稱寫入檔案
這兩個構造方法引數中都需要傳入一個布林值,true表示追加資料,flase表示清空原有資料,這樣建立的輸出流物件就可以指定是否追加續寫了,程式碼:
public class FOSwrite{
public static void main(Srting[] args) throws IOException{
//使用檔名稱建立流物件
FileOutputStream fos = new FileOutputStream("fos.txt",true);
//字串轉換為位元組陣列
byte[] b = "abcde".getBytes();
fos.write(b);
//關閉資源
fos.close();
//輸出cdabcde
}
}
寫出換行
在windows系統中換行符號是\r\n,可以指定追加續寫
- 回車符\r和換行符\n
回車符:回到一行的開頭
換行符:下一行- 系統中的換行
windows系統裡,每行結尾是回車加換行即\r\n
Linux系統裡每行結尾只有換行即\n
Mac系統裡,每行結尾是回車即\r,從Mac OS X開始於Linux統一
位元組輸入流【InputStream】
java.io.InputStream 抽象類是表示位元組輸入流的所有類的超類,可以讀取位元組資訊到記憶體中,它定義了位元組輸入流的基本共性功能方法
public void close()
:關閉此輸入流並釋放與此流相關聯的所有系統資源public abstract int read()
:從輸入流讀取資料的下一個位元組public int read(byte[] b)
:從輸入流中讀取一些位元組數,並將它們儲存到位元組陣列b中
close方法,當完成流的操作時,必須呼叫此方法,釋放系統資源
FileInputStream類
java.io.FileInputStream 類是檔案輸入流類,從檔案中讀取位元組 **構造方法** - `public FileInputStream(File file)` :通過開啟與實際檔案的連線來建立一個FileInputStream,該檔案由檔案系統中的File物件file命名 - `public FileInputStream(String name)` :通過開啟與實際檔案的連線來建立一個FileInputStream,該檔案由檔案系統中的路徑名name命名 當建立一個流物件時,必須傳入一個檔案路徑,此路徑下沒有存在該檔案就會丟擲FileNotFoundException 構造舉例:public class FileInputStreamConstructor{
public static void main(String[] args)throws IOException{
//使用File物件建立流物件
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
}
}
讀取位元組資料
1.讀取位元組:read方法,每次可以讀取一個位元組的資料,提升為int型別,讀取到檔案末尾時返回-1,程式碼使用:public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
read = fis.read();
System.out.println((char)read);
//末尾處返回-1
read = fis.read();
System.out.println((char)read);
fis.close();
}
]
迴圈改進讀取方式:
public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int b;
while((b = fis.read()) != -1){
System.out.println((char)b);
}
fis.close();
}
}
2.使用位元組陣列讀取:read(byte[] b),每次讀取b的長度位元組到陣列中,返回讀取到的有效位元組個數,讀取到末尾時返回-1,程式碼使用:雖然讀取了一個位元組,但是會自動提升為int型別
流操作完畢之後必須執行close方法
public class FISRead{
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("read.txt");
int len;
byte[] b = new byte[2];
while((len = fis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
fis.close();
}
}
使用陣列讀取,每次讀取多個位元組,減少了系統間IO操作次數,從而提高了讀寫效率,建議開發中使用
流的關閉原則,先開後關,後開先關
字元流
使用位元組流讀取文字檔案的時候,可能會遇到一些問題,例如遇到中文字元時可能不會顯示完整的字元,因為一箇中文字元可能佔用多個位元組儲存,所以java提供了一些字元流類,以字元為單位讀寫資料,專門用於處理文字檔案字元輸入流【Reader】
java.io.Reader 抽象類時表示用於讀取字元流的所有類的超類,可以讀取字元資訊到記憶體中,它定義了字元輸入流的基本共性功能方法public void close()
:關閉此流以及相關聯的任何系統資源public int read()
:從輸入流讀取一個字元public int read(char[] cbuf)
:從輸入流中讀取一些字元,並存儲到字元陣列cbuf中
FileReader類
java.io.FileReader 類就是讀取字元檔案的便利類,構造時使用系統預設的字元編碼和預設位元組緩衝區
字元編碼:位元組與字元的對應規則,windows系統的中文編碼預設時GBK編碼表,idea中為UTF-8
位元組緩衝區:一個位元組陣列,用來臨時儲存位元組資料
構造方法
FileReader(File file)
:建立一個新的FileReader,給定要讀取的File物件FileReader(String fileName)
:建立一個新的FileReader,給定要讀取的檔案的名稱
當建立一個流物件的時候,必須傳入一個檔案路徑
構造:
public class FileReaderConstructor{
public static void main(String[] args) throws IOException{
File file = new File("a.txt");
FileReader fr = new FileReader(file);
FileReader fr1 = new FileReader("b.txt");
}
}
讀取字元資料
1.讀取字元:使用read方法每次可以讀取一個字元,並且提升為int型,讀取到檔案末尾時返回-1,可以利用迴圈來讀取
public class FRRead{
public static void main(String[] args) throws IOException{
//建立物件
FileReader fr = new FileReader("read.txt");
//定義一個區域性變數儲存資料
int b;
//迴圈讀取
while((len = fr.read()) != -1){
System.out.println((char)b);
}
fr.close();
}
}
2.使用字元陣列讀取:read(char[] cbuf):每次讀取多個字元到陣列中,返回讀取到的有效字元個數,讀取到末尾時返回-1
public class FRRead{
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("read.txt");
int len;
char[] cbuf = new char[1024];
while((len = fr.read(cbuf)) != -1){
System.out.println(new String(cbuf,0,len))
}
fr.close();
}
}
字元輸出流【Writer】
java.io.Writer 抽象類是表示用於寫出字元流的所有類的超類,將指定的字元資訊寫出到目的地,它定義了位元組輸出流的基本共性功能方法
void write(int c)
:寫入單個字元void writer(char[] cbuf)
:寫入字元陣列abstract void write(char[] cbuf,int off,int len)
:寫入字元陣列的某一部分,off陣列的開始索引,off陣列的開始索引,len寫的字元個數void write(String str)
寫入字串void write(String ,int off,int len)
:寫入字串的某一部分,off字串的開始索引,len寫的字元個數void flush()
:重新整理該流的緩衝void close()
:關閉此流,並先重新整理
FileWriter類
java.io.FileWriter 類是寫出字元到檔案的便利類,構造時使用系統預設的字元編碼和預設位元組緩衝區
構造方法
FileWriter(File file)
:建立一個新的FileWriter,給定要讀取的File物件FileWriter(String fileName)
:建立一個新的FileWriter,給定要讀取的檔案的名稱
當你建立一個流物件的時候,必須傳入一個檔案路徑
舉例
public class FileWriterConstructor{
public static void main(String[] args){
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);
FileWriter fw1 = new FileWriter("b.txt");
}
}
基本寫出資料
使用write方法可以一次寫出一個位元組,字元陣列或者字串
關閉和重新整理
因為內建緩衝區的原因,如果不關閉輸出流,就無法寫出字元到檔案中,但是關閉流物件,就無法繼續寫出資料,那麼就要使用flush方法
- flush:重新整理緩衝區,並可以繼續使用流物件
- close:先重新整理緩衝區,然後通知系統釋放資源,不可繼續使用流物件
即便呼叫了flush方法,在操作的最後還是要呼叫close方法釋放資源
字元流只能操作文字檔案,不能操作圖片,視訊等非文字檔案
當只是單純讀寫文字檔案的時候使用字元流,其他情況使用位元組流
IO異常的處理
**JDK7之前的處理方式** 實際開發中不能通過丟擲異常的方式來處理異常,建議使用try..catch…finally程式碼public class HandleExcepetion1{
public static void main(String[] args){
//宣告變數
FileWriter fw = null;
try{
//建立流物件
fw = new FileWriter("fw.txt");
//寫出資料
fw.write("123456");
} catch (IOException e){
e.printStackTrace();
} finally{
try{
if(fw != null){
fw.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
**JDK7的處理** 可以使用JDK7的優化後的try-with-resource語句,該語句確保了每個資源在語句結束時關閉,所謂的資源是指程式完成後必須關閉的物件 格式:
try(建立流物件,多個物件之間使用;分隔){
//讀寫資料
} catch (IOException e){
e.printStackTrace();
}
演示
public class HandleException2{
public static void main(String[] args){
//建立流物件
try(FileWriter fw= new FileWriter("fw.txt");){
//寫出資料
fw.write("123456");
} catch (IOException e){
e.printStackTrace();
}
}
}
緩衝流
除了以上一些基本流以外,還有其他功能更強大的流,比如能夠高效讀寫的緩衝流,轉換編碼的轉換流,持久儲存物件的序列化流等等,而這些流都是在基本的流物件基礎之上建立而來的,相當於對基本流物件的一種增強。概述
緩衝流,也叫高效流,是對基本的四個Filexxx流的增強,所以也是四個流,按照資料型別分類:- 位元組緩衝流:BufferedInputStream,BufferedOutputStream
- 字元緩衝流:BufferedReader,BufferedWriter
緩衝流的基本原理,是在建立流物件的時候,建立一個內建的預設大小的緩衝區陣列,通過緩衝區讀寫,減少系統IO次數,從而提高讀寫的效率
位元組緩衝流
構造方法:
- public BufferedInputStream(InputStream in)
:建立一個新的緩衝輸入流
- public BufferedOutputStream(OutputStream out)
:建立一個新的緩衝輸出流
構造舉例:
//建立位元組緩衝輸入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//建立位元組緩衝輸出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
字元緩衝流
構造方法
public BufferedReader(Reader in)
:建立一個新的緩衝輸入流public BufferedWriter(Writer out)
:建立一個新的緩衝輸出流
構造舉例
//建立字元緩衝輸入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//建立字元緩衝輸出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
特有方法
字元緩衝流的基本方法與普通字元流的呼叫方法一致,但是還有一些特有方法
- BufferedReader:
public String readLine()
:讀一行文字 - BufferedWriter:
public void newLine()
:寫一行行分隔符
轉換流
編碼引出的問題
在IDEA中使用FileReader讀取文字檔案時,預設使用的是UTF-8編碼,而windows系統建立檔案時,預設使用GBK編碼,那麼讀取的時候會產生亂碼
InputStreamReader類
轉換流java.io.InputStream,作為Reader得子類是從位元組流到字元流得橋樑,它能夠讀取位元組並使用指定得字符集將其解碼為字元,它得字符集可以由名稱指定,也可以接收平臺得預設字符集
構造方法
InputStreamReader(InputStream in)
:建立一個使用預設字符集得字元流InputStreamReader(InputStream in,String charsetName)
:建立一個使用預設字符集的字元流
構造舉例:
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"),"GBK");
OutputStreamWriter類
轉換流java.io.OutputStreamWriter,作為Writer的子類,是字元流到位元組流的橋樑,使用指定的字符集將字元編碼為位元組,它的字符集可以由名稱指定,也可以接受平臺的預設字符集
構造方法
OutputStreamWriter(OutputStream out)
:建立一個使用預設字符集的字元流OutputStreamWriter(OutputStream out,String charsetName)
:建立一個使用指定字符集的字元流
構造舉例
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("out.txt"),"GBK");
序列化
java提供了一種物件序列化的機制,用一個位元組序列可以表示一個物件,該位元組序列包含該物件的資料,物件的型別和物件中儲存的屬性等資訊。位元組序列寫出到檔案之後,相當於檔案中持久儲存了一個物件的資訊
反之,該位元組序列還可以從檔案中讀取回來,重構物件,對它進行反序列化,讀取資訊並且在記憶體中建立物件
ObjectOutputStream類
java.io.ObjectOutputStream類,將java物件的原始資料型別寫出到檔案,實現物件的持久儲存
構造方法:
public ObjectOutputStream(OutputStream out)
:建立一個指定OutputStream的ObjectOutputStream
構造舉例
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
序列化操作
一個物件如果想被序列化必須滿足兩個條件
- 該類必須實現java.io.Serializable介面,該介面是一個標記介面,不實現此介面的類將不會使任何狀態序列化或反序列化,會丟擲NotSerializableException
- 該類的屬性必須是可序列化的,如果有一個屬性不需要可序列化,則該屬性必須註明是瞬態的,使用transient修飾
public class Employee implements Serializable{
public String name;
public transient int age;//瞬態修飾成員無法被序列化
public void method(){
System.out.println("===");
}
}
寫出物件方法
public final void writeObject(Obkect obj)
:寫出指定的物件
public class SerializeDemo{
public static void main(String args){
Employee e = new Employee();
e.name = "11";
e.age = 18;
try{
//建立序列化物件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
//寫出物件
oos.writeObject(e);
//釋放資源
oos.close();
} catch(IOException i){
i.printStackTrace();
}
}
}
ObjectInputStream類
java.io.ObjectInputStream 反序列化流,將之前使用ObjectOutputStream序列化的原始資料恢復成物件
構造方法
public ObjectInputStream(InputStream in)
:建立一個指定InputStream的ObjectInputStream
反序列化操作1
如果能找到一個物件的class檔案,就可以進行反序列化操作,呼叫ObjectInputStream讀取物件的方法:
public final Object readObject()
:讀取一個物件
public class DeserializeDemo{
public static void main(String[] args){
Employee e = null;
try{
//建立反序列化流
FileInputStream fileIn = new FileInputStream("employee.txt");
ObjectInputStream ois = new ObjectInputStream(fileIn);
//讀取一個物件
e = (Employee) ois.readObject();
//釋放資源
in.close();
fileIn.close();
} catch(IOException i){
i.printStackTrace();
return;
} catch(ClassNotFoundException c){
c.printStackTrace();
return;
}
System.out.println("Name:" + e.name);
System.out.println("Name:" + e.age);
}
}
對於JVM可以反序列化物件,它必須是能夠找到class檔案的類,如果找不到該類的class檔案,則丟擲一個ClassNotFoundException 異常
反序列化操作2
另外,當JVM反序列物件時,能找到class檔案,但是class檔案在序列化物件之後發生了改變,那麼反序列化操作也會失敗,丟擲InvalidClassException異常,原因如下
- 該類的序列版本號與從流中讀取的類描述符的版本號不匹配
- 該類包含未知資料型別
- 該類沒有可訪問的無引數構造方法
Serializable 介面給需要序列化的類,提供了一個序列版本號,serialVersionUID該版本號的目的在於驗證序列化的物件和對應類是否版本匹配
public class Employee implements Serializable{
//加入序列版本號
private static final long serialVersionUID = 1L;
public String name'
public int age;
//新增新的屬性,重新編譯,可以反序列化,該屬性賦為預設值
public int eid;
public void method(){
System.out.println("===");
}
}
列印流
概述
print 和 println的方法都來自於java.io.PrintStream類
PrintStream類
構造方法
public PrintStream(String fileName)
:使用指定的檔名建立一個新的列印流
舉例
PrintStream ps = new PrintStream("ps.txt");
改變列印流向
System.out 就是PrintStream 型別的,只不過它的流向是系統規定的,列印在控制檯上,不過既然是流物件,就可以改變它的流向
public class PrintDemo{
public static void main(String[] args){
//呼叫系統列印流,控制檯直接輸出97
System.out.println(97);
//建立列印流,指定檔案的名稱
PrintStream ps = new PrintStream("ps.txt");
//設定系統的列印流流向,輸出到ps.txt
System.setOut(ps);
//呼叫系統的列印流 ps.txt中輸出97
System.out.println(97);
}
}