進入java IO部分的學習,首先學習IO基礎,內容如下。需要了解流的概念、分類還有其他一些如集合與文件的轉換,字符編碼問題等,這次先學到字節流的讀寫數據,剩余下次學完。
一、IO基礎
1、背景
1.1 數據存儲問題
變量、數組、對象和集合中存儲的數據是暫時存在的,一旦程序結束它們就會丟失。
解決:為了永久保存程序創建的數據,需要將其保存到磁盤文件中。
1.2 流與IO
1)流
是一種抽象概念,是對數據傳輸的總稱。即數據在設備間的傳輸稱為流,流的本質是數據傳輸
- Java 中所有數據都是用流讀寫的。
- 流是一組有序的數據序列(以輸入流的形式獲取,輸出流的形式輸出),將數據從一個地方帶到另一個地方。(可類比水管里水的流動)
- 輸入:將數據從各種輸入設備(包括文件、鍵盤等)中讀取到內存中
- 輸出:將數據寫入到各種輸出設備(比如文件、顯示器、磁盤等)
- 流相關的類都封裝在 java.io 包中,且每個數據流都是一個對象。
可分為輸入和輸出兩種模式
輸入流:文件、網絡、數據庫及其他數據源—(輸入流)—>目的地
輸出流:源—(輸出流)—>文件、網絡、數據庫及其他數據源
2)Java 的 I/O(輸入/輸出)技術
作用:處理設備間數據傳輸問題
將數據保存到文本文件和二進制文件中, 以達到永久保存數據的要求。常見應用:文件復制;文件上傳;文件下載
2、流的分類
2.1 按流的方向
1)輸入流(input)
用于讀數據
所有輸入流類都是 InputStream 抽象類(字節輸入流)和 Reader 抽象類(字符輸入流)的子類
InputStream(字節輸入流)結構------>了解即可
FileInputStream 文件輸入流
PipedInputStream 管道輸入流
ObjectInputStream 對象輸入流FilterInputStream 過濾器輸入流
- PushBackInputStream 回壓輸入流
- BufferedInputStream 緩沖輸入流
- DataInputStream 數據輸入流SequenceInputStream 順序輸入流
ByteArrayInputStream 字節數組輸入流
StringBufferInputStream 緩沖字符串輸入流
InputStream 類常用方法------>掌握
read()方法(重載)3個
- int read():從輸入流讀入一個 8 字節的數據,將它轉換成一個 0~ 255 的整數,返回一個整數,如果遇到輸入流的結尾返回 -1
- int read(byte[] b):從輸入流讀取若干字節的數據保存到參數 b 指定的字節數組中,返回的字節數表示讀取的字節數,如果遇到輸入流的結尾返回 -1
- int read(byte[] b,int off,int len):從輸入流讀取若干字節的數據保存到參數 b 指定的字節數組中,其中 off 是指在數組中開始保存數據位置的起始下標,len 是指讀取字節的位數。返回的是實際讀取的字節數,如果遇到輸入流的結尾則返回 -1close():關閉數據流,當完成對數據流的操作之后需要關閉數據流
available():返回可以從數據源讀取的數據流的位數
skip(long n):從輸入流跳過參數 n 指定的字節數目markSupported():判斷輸入流是否可以重復讀取
mark(int readLimit):如果輸入流可以被重復讀取,從流的當前位置開始設置標記,readLimit 指定可以設置標記的字節數
reset():使輸入流重新定位到剛才被標記的位置,這樣可以重新讀取標記過的數據
兩點注意:
最后 3 個方法一般結合使用,先用 markSupported() 判斷,如果可以重復讀取,則用 mark(int readLimit) 方法進行標記,標記完成后可以用 read() 方法讀取標記范圍內的字節數,最后用 reset() 方法使輸入流重新定位到標記的位置,繼而完成重復讀取操作。
Java 中的字符是 Unicode 編碼(雙字節),而 InputerStream 是用來處理單字節的,在處理字符文本時不是很方便。這時可以使用 Java 的文本輸入流 Reader 類,該類是字符輸入流的抽象類,即所有字符輸入流的實現都是它的子類,該類的方法與 InputerSteam 類的方法類似,不再贅述。
2)輸出流(Output)
用于寫數據
所有輸出流類都是 OutputStream 抽象類(字節輸出流)和 Writer 抽象類(字符輸出流)的子類
OutputStream(字節輸出流)結構------>了解即可
FileOutputStream 文件輸出流
PipedOutputStream 管道輸出流
ObjectOutputStream 對象輸出流
FilterOutputStream 過濾器輸出流
PrintStream 打印輸出流
BufferedOutputStream 緩沖輸出流
DataOutputStream 數據輸出流
ByteArrayOutputStream 字節數組輸出流
OutputStream 類常用方法------>掌握
write()方法(重載)3個
- int write(b):將指定字節的數據寫入到輸出流
- int write(byte[] b):將指定字節數組的內容寫入輸出流
- int write(byte[] b,int off,int len):將指定字節數組從 off 位置開始的 len 字節的內容寫入輸出流
close():關閉數據流
flush():刷新數據流
2.2 按數據單位
一般IO流是按數據單位(即數據類型)來分的
流的使用:
- 如果數據通過window自帶的記事本軟件打開,還可以讀懂里面的內容,就用字符流,否則用字節流
- 如果不知道使用哪種類型的流,就用字節流(字節流是萬能的流)
1)字節流
寫數據:
字節流抽象基類(父類)
InputStream:是字節輸入流的所有類的超類(父類)
OutputStream:是字節輸出流的所有類的超類
子類名特點:子類名稱都是以其父類名作為子類名的后綴 (可參考上一節的輸入流和輸出流內容)
FileOutputStream:文件輸出流用于將數據寫入File
FileOutputStream(String name):創建文件輸出流以指定的名稱寫入文件
使用字節輸出流寫數據的步驟:(創建對象–>寫數據–>釋放資源)
1、創建字節輸出流對象(做了3件事情)
- 調用系統功能創建了文件
- 創建字節輸出流對象
- 讓字節輸出流對象指向文件2、調用字節輸出流對象的寫數據方法
3、釋放資源(關閉此文件輸出流并釋放與此流相關聯的任何系統資源)
示例1
import java.io.FileOutputStream;
import java.io.IOException;public class FileOutputStreamDemo1 {public static void main(String[] args) throws IOException { // 拋出異常// 創建字節輸出流對象// FileOutputStream(String name):創建文件輸出流以指定的名稱寫入文件FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");/*做了三件事情:1:調用系統功能創建了文件2:創建字節輸出流對象3:讓字節輸出流對象指向創建好的文件*///void write(int b):將指定的字節寫入此文件輸出流fos.write(97); // afos.write(57); // 9 (是字符不是數字)fos.write(55); // 7 (是字符不是數字)// 最后都要釋放資源// void close():關閉此文件輸出流并釋放與此流相關聯的任何系統資源fos.close();}
}
運行結果(會生成一個fos.txt文件,打開查看內容如下)
a97
字節流寫數據的三種方式(write()方法的重載–3個)
int write(b):將指定字節的數據寫入到輸出流
int write(byte[] b):將指定字節數組的內容寫入輸出流
int write(byte[] b,int off,int len):將指定字節數組從 off 位置開始的 len 字節的內容寫入輸出流
示例2
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;public class FileOutputStreamDemo2 {public static void main(String[] args) throws IOException { // 拋出異常/*** 1.創建對象*/// 創建字節輸出流對象// FileOutputStream(String name):創建文件輸出流以指定的名稱寫入文件FileOutputStream fos1=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");/*** 2.寫數據(3種方式)*/// void write(int b):將指定的字節寫入此文件輸出流
// fos1.write(97);
// fos1.write(98);
// fos1.write(99);
// fos1.write(100);
// fos1.write(101); // 結果:abcde// void write(byte[] b):將指定字節數組的內容寫入輸出流
// byte[] bys={97,98,99,100,101}; //方法1:創建數組byte[] bys="abcde".getBytes(); //方法2:getBytes()方法
// fos1.write(bys); // 結果:abcde// void write(byte[] b,int off,int len):將指定字節數組從 off 位置開始的 len 字節的內容寫入輸出流
// fos1.write(bys,0,bys.length); // 結果:abcdefos1.write(bys,1,3); // 結果:bcd/*** 3.釋放資源*/fos1.close();}
}
getBytes()方法使用注意:
getBytes(String charsetName): 使用指定的字符集將字符串編碼為 byte 序列,并將結果存儲到一個新的 byte 數組中。getBytes(): 使用平臺的默認字符集將字符串編碼為 byte 序列,并將結果存儲到一個新的 byte 數組中。
字節流寫數據的兩個小問題
a、實現換行(添加換行符)
windows:\r\n
linux:\n
mac:\r
b、實現追加寫入
public FileOutputStream(String name,boolean append)創建文件輸出流以指定名稱寫入文件。若第二個參數為true,則字節將寫入文件的末尾而不是開頭(開頭覆蓋,末尾追加)
- 默認是false,即寫在開頭,這樣每運行一次都會覆蓋原內容,追加寫入時要改為true。
示例3
import java.io.FileOutputStream;
import java.io.IOException;public class FileOutputStreamDemo3 {public static void main(String[] args) throws IOException {// 創建字節輸出流對象//boolean append為true實現追加(將字節寫入末尾而不是開頭)FileOutputStream fos3=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt",true);// 寫數據for(int i=0;i<10;i++){fos3.write("hello".getBytes()); // getBytes()方法fos3.write("\r\n".getBytes()); // 每寫完一次做一個換行}// 釋放資源fos3.close();}
}
補充:
try-catch異常處理(一般文件相關的異常直接用throws拋出即可,但try-catch異常捕獲方式也要了解,不懂的可以回去看異常處理部分)
finally:在異常處理時提供finally塊來執行所有清除操作。如IO流中的釋放資源。特點:被finally控制的語句一定會執行,除非JVM退出
示例4
import java.io.FileOutputStream;
import java.io.IOException;public class FileOutputStreamDemo4 {public static void main(String[] args) {FileOutputStream fos4 = null; // 初始化文件對象為null(全局+初始化)try {// 創建對象
// fos4 = new FileOutputStream("Z:\\Ultimate JavaCode\\src\\test6\\fos4.txt"); //文件不存在會拋出FileNotFoundException異常fos4 = new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");// 寫入數據fos4.write("javaEE".getBytes());// 關閉資源fos4.close();// 若寫入操作存在異常,則直接跳到catch捕獲,不會執行關閉資源操作,這時需要引入finally} catch (IOException e) { // 捕獲異常e.printStackTrace(); //存在異常時,追蹤異常信息} finally { // 加入finally實現釋放資源if(fos4!=null){ // 文件不為null時,才需要釋放資源(防止文件為null時關閉資源出現空指針異常NullPointerException)try{fos4.close(); // 釋放資源}catch (IOException e){e.printStackTrace();}}}}
}
讀數據:
字節流讀數據(一次讀一個字節數據)
FileInputStream:從文件系統中的文件獲取輸入字節
- FileInputStream(String name):通過打開與實際文件的連接來創建一個FileInputStream,該文件由文件系統中的路徑名name命名使用字節輸入流讀數據的步驟:(創建對象-->讀數據-->釋放資源)
1、創建字節輸入流對象(做了3件事情)
- 調用系統功能創建了文件
- 創建字節輸出流對象
- 讓字節輸出流對象指向文件
2、調用字節輸入流對象的讀數據方法
3、釋放資源(關閉此文件輸出流并釋放與此流相關聯的任何系統資源)
字節流讀數據的3種方式(read()方法重載–3個)
int read():從輸入流讀入一個 8 字節的數據,將它轉換成一個 0~ 255 的整數,返回一個整數,如果遇到輸入流的結尾返回 -1
int read(byte[] b):從輸入流讀取若干字節的數據保存到參數 b 指定的字節數組中,返回的字節數表示讀取的字節數,如果遇到輸入流的結尾返回 -1
int read(byte[] b,int off,int len):從輸入流讀取若干字節的數據保存到參數 b 指定的字節數組中,其中 off 是指在數組中開始保存數據位置的起始下標,len 是指讀取字節的位數。返回的是實際讀取的字節數,如果遇到輸入流的結尾則返回 -1
案例1
import java.io.FileInputStream;
import java.io.IOException;public class FileInputStreamDemo1 {public static void main(String[] args) throws IOException {// 創建對象FileInputStream fis=new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");// 讀數據
// int bys= fis.read();
// System.out.println(bys); // 結果:98(文件的字符是'b')
// System.out.println((char)bys); // 大轉小,強制轉換,顯示原字符(結果:b)/*字節流讀數據標準代碼如下1.fis.read():讀數據2.by=fis.read():將讀取的數據賦值給by3.by!=-1:判斷讀取到的數據是否是-1(到達文件末尾)*/int by; // 類型聲明while((by=fis.read())!=-1){System.out.print((char)by); // 強轉顯示原字符(結果:bcd),注意用print而不是println}// 釋放資源fis.close();}
}
案例2:字節流復制文本文件
需求:把文本文件的內容從一個文件中讀取出來(數據源),然后寫入到另一個文件中(目的地)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class FileInputStreamDemo2{public static void main(String[] args) throws IOException {// 創建對象FileInputStream fis=new FileInputStream("D:\\文本復制案例.txt"); // 從指定路徑下讀取要復制的文本文件(數據源)FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");//目的地// 先從數據源(fis)讀取,后寫入目的地(fos)int by;while((by=fis.read())!=-1){ // 一次讀取一個字節,直到讀完(到文件末尾)fos.write(by); // 一次寫入一個字節}// 釋放資源fos.close();fis.close(); // 先創建的后釋放}
}
案例3
需求:字節流讀數據(一次讀一個字符數組),用到int read(byte[] b)構造方法
import java.io.FileInputStream;
import java.io.IOException;public class FileInputStreamDemo3 {public static void main(String[] args) throws IOException {// 創建對象FileInputStream fis=new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");byte[] bys=new byte[1024]; // 1024及其整數倍int len;// 讀取數據while((len=fis.read(bys))!=-1){ // 一次讀一個字符數組,用到int read(byte[] b)構造方法System.out.println(new String(bys,0,len)); // new String方法轉換為字符串輸出到控制臺}// 釋放資源fis.close();}
}
字節緩沖流
BufferedOutputStream:該類實現緩沖輸出流。 通過設置這樣的輸出流,應用程序可以將字節寫入基礎輸出流,而不必為寫入的每個字節調用底層系統。
BufferedInputStream:創建BufferedInputStream將創建一個內部緩沖區數組。當從流中讀取或跳過字節時內部緩沖區將根據需要從所包含的輸入流中重新填充,一次很多字節。
構造方法
字節緩沖輸出流:BufferedOutputStream(OutputStream out)
字節緩沖輸入流:BufferedInputStream(InputStream in)問題:構造方法里需要的是字節流而不是具體的文件或路徑?
原因:字節緩沖流僅僅提供緩沖區,而真正的讀寫數據還得依靠基本的字節流對象進行操作。
示例
import java.io.*;public class BufferStreamDemo1 {public static void main(String[] args) throws IOException {
// FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
// BufferedOutputStream bos=new BufferedOutputStream(fos);// 創建字節緩沖輸出流對象BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt"));// 操作1:寫數據bos.write("hello\n".getBytes());bos.write("javaEE\n".getBytes());// 創建字節緩沖輸入流對象(用于讀數據)BufferedInputStream bis=new BufferedInputStream(new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt"));// 操作2:讀數據// 方法1:一次讀取一個字節數據
// int by;
// while((by=bis.read())!=-1){
// System.out.println((char)by);
// }// 方法2:一次讀取一個字節數組byte[] bys=new byte[1024]; // 1024及其整數倍int len;while((len=bis.read(bys))!=-1){System.out.println(new String(bys,0,len)); // 轉換為字符串輸出到控制臺}// 釋放資源bos.close();}
}
運行結果(打開fos.txt文件,顯示如下)
hello
javaEE