圖片來源于互聯網
一、IO流概述
IO流用于處理設備之間的數據傳輸問題。Java對數據的操作,通過流的形式。操作存儲設備中的數據,流技術實現,程序和設備之間建立連接通道,數據流。I -> Input 輸入 O -> Output輸出
- 字符流: 開始JDK1.1 字符流專門用于操作文本文件,記事本可以打開的,人可以看懂的文件。 每次操作1個字符 16個二進制,去查詢本機默認的編碼表。方便Java程序操作純文本。( .txt .java .html .xml .css )
- 字節流:開始JDK1.0 字節流操作文件是任意文件。每次操作1個字節8個二進制。不查詢編碼表 。
二、IO流的分類
-
字符輸出流: 寫文本文件的,抽象基類java.io.Writer
寫的方法
write
,很多重載形式,寫字符數組,單個字符,字符串,字符數組一部分,字符串的一部分。flush
刷新流的緩沖close
關閉流對象
-
字符輸入流:讀取文本文件的,抽象基類java.io.Reader
讀的方法
read
,很多重載形式,讀取單個字符,字符數組,字符數組一部分。close
關閉流對象 -
字節輸出流:寫任意的文件,抽象基類java.io.OutputStream
寫的方法
write
,很多重載形式,寫單個字節,字節數組,字節數組一部分。close
關閉流對象 -
字節輸入流:讀取任意文件,抽象基類java.io.InputStream
讀的方法
read
,很多重載形式,讀取單個字節,字節數組,字節數組一部分。close
關閉流對象
IO流中的所有類的命名法則:后綴都是父類名,前面都是可以操作的文件名,流向名
FileInputStream
FileReader
ObjectInputStream
InputStreamReader
三、書寫注意
- IO使用,導入java.io包
- IO流類中的方法,大多數都會拋出異常,調用者
try ...throws
- IO流對象,沒有實際的功能操作文件,依賴操作系統來實現功能,流對象用完后,一定要關閉資源
四、字符流輸出
1. 寫文件
數據輸出是java.io.Writer類(抽象的),具體操作找子類 FileWriter 來寫入字符文件的便捷類。
- 創建子類對象,需要使用子類的構造器,傳遞String文件名
- 子類對象調用父類方法
write()
寫文件 - 關閉資源(一旦流對象關閉,不能再次使用)
注意:創建對象,調用方法寫,write方法是把數據寫在了內存中,flush刷新該流的緩沖,把數據從內存刷到目的文件。字符流寫文件必須刷新,如果不刷新數據可能會留在內存中,但是只要進行了刷新數據肯定走向目的文件
2. 追加的形式寫文件
當我們不能覆蓋寫入的時候,就需要進行后續的追加。利用FileWriter類的構造方法,傳遞true
可以實現文件的追加寫入
三種寫入的方法:
- 寫單個的字符
write(int i)
- 寫字符數組
write(char[] c)
- 寫字符數組一部分
write(char[] c,int start,int length)
demo:
package io;
import java.io.*;
public class FileWriteDemo {
public static void main(String[] args) throws IOException {
//構造方法拋異常
FileWriter write = new FileWriter("test.txt");
//write方法,三種重載形式(string s) (int i ) (char [])
write.write("this is a test");
write.flush();
write.close();
//打開進行追加,文件存在時,不會覆蓋原來的內容
FileWriter fw = new FileWriter("test.txt",true);
fw.write("ok, append something");
fw.flush();
fw.close();
}
}
五、字符流讀取文件
讀取文件是java.io.Reader類,也是抽象類,具體使用要找子類,字符流讀取使用子類FileReader。
- 創建子類對象,需要使用子類構造器,傳遞String文件名
- 在調用父類方法
read
讀取文件 - 關閉資源
一般兩種讀取的方式:
- 讀取單個字符
int read()
每次讀取1個字符,返回碼值,read
方法每執行一次,自動的向后讀取一個字符,讀取到文件末尾,返回-1。 - 數組讀取
int read(char[])
,一次性讀取數組長度個字符,返回讀取的長度,到達文件末尾時返回-1
demo:
package io;
/*
* 字符輸入流
*
* 1. int reader() 讀取單個字符,返回字符對應的int值,每次讀取,指針就會向后移動,指向下一個字符,到文件末尾時,返回-1
* 2. int reader(char []) 讀取一個數組的長度,具體長度由數組的長度決定,返回讀取的長度,到文件末尾時,返回-1
*
*/
import java.io.*;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("test.txt");
System.out.println((char)fr.read());
System.out.println(fr.read());
System.out.println(fr.read());
System.out.println(fr.read());
fr.close();
method();
}
//讀取數組
public static void method ()throws IOException {
FileReader fe = new FileReader("test.txt");
char[] ch = new char[2];
int i = 0;
while((i = fe.read(ch)) != -1){
String s = new String(ch,0,i);
System.out.print(s);
}
fe.close();
}
}
六、緩沖區
緩沖區是為了提高流的讀寫效率而出現,比如BufferedWriter,也是Writer的子類,Writer類的方法都可以用的。但是使用緩沖區對象,必須有一個流對象配合使用。
1. 字符輸出流
BufferedWriter類的構造方法BufferedWriter(Writer w)
參數是一個字符輸出流,將輸出流FileWriter類的對象傳遞給BufferedWriter的構造方法 BfferedWriter(new FileWriter)
,BufferedWriter這個緩沖區,將提高FileWriter的寫如效率。
緩沖區特有方法 void newLine()
文本中,寫換行。可以寫\r\n
還是用newLine()
,如果需要文本的換行,需要使用newLine()
實現,這個方法具有跨平臺性。
Windows,換行\r\n Linux換行\n . 如果安裝的JDK是Windows版本的,newLine()方法中寫的就是\r\n, 安裝的JDK是Linux版本,newLine()方法寫的就是\n
Demo:
package io;
/*
* 緩沖區demo
*/
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException{
//創建字符輸出流
FileWriter fw = new FileWriter("E:\\test.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("test!\r132");
bw.newLine();//跨平臺性 win下是\r\n linux下是\n
bw.write("new test!");
bw.flush();
bw.close();
}
}
2. 字符輸入流
BufferedReader類也是Reader的子類,Reader的方法當然都可以用,需要一個輸入流對象,配合使用。
BufferedReader構造方法BufferedReader(Reader r)
參數是一個字符輸入流,方法String readLine()
讀取文本一行,返回字符串。
demo:
package io;
import java.io.*;
/*
* BufferedReader
* String readerLine() 一次讀取一行
*/
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("E:\\test.txt"));
String s = null;
while( (s = br.readLine()) != null){
System.out.println(s.length() +"..." + s);
}
br.close();
}
}
練習demo(復制一個文件,使用緩沖區實現):
package io;
import java.io.*;
/*
* 一個練習,buffered讀行,寫行,換行
* 處理異常
* 目標文件:e:\temp.html
*/
public class CopyTextByBuffered {
public static void main(String[] args) {
BufferedReader bfr = null;
BufferedWriter bfw = null;
try{
//初始化需要用的文件和緩沖區的讀取和寫入的對象
bfr = new BufferedReader(new FileReader("E:\\temp.html"));
bfw = new BufferedWriter(new FileWriter("E:\\newTemp.html"));
String s = null;
while((s = bfr.readLine()) != null){
bfw.write(s);
bfw.newLine();
bfw.flush();
}
}catch(IOException e){
//
throw new RuntimeException("文件copy失敗");
}finally{ //關閉緩沖區
try{
bfw.close();
}catch(IOException e){
throw new RuntimeException("關閉文件寫入緩沖區異常");
}
try{
bfr.close();
}catch(IOException e){
throw new RuntimeException("關閉文件讀取緩沖區異常");
}
}
}
}
七、裝飾設計模式
裝飾設計模式出現的意義就是增強原有對象的功能。舉個例子,BufferedReader,BufferedWriter 裝飾類的出現,就是增強了原有的流對象Reader和Writer的功能,裝飾設計在java的IO中使用相當廣泛。
說道這里,那繼承覆蓋和裝飾的區別在哪里,舉個例子進行說明。
現在有一個父類Reader,下面有四個子類分別是:
- 文本類 TextReader
- 視頻類 VideoReader
- 音樂類 SoundReader
- 游戲類 GameReader
如果此時因為這四個類的功能太少要進行擴展,采用繼承的話就需要再寫四個子類來進行繼承和重寫和加強,因此就會有八個類出現。這對開發和新手接觸都是相當麻煩的,要額外的增加相當多的成本。那此時如果采用裝飾類的話,只需要一個BufferedReader裝飾類將繼承自Reader的所有類都裝飾起來,簡單簡潔,適合新手接觸和開發。
八、實現readerLine()練習
依照BufferedReader類的readLine方法原理,自己進行實現,要求功能上和原方法相同。
Demo:
package io;
/*
* 自己實現一個類似于BufferReader 里面的 readerline方法
* 只是練習,直接把異常拋出去,沒有做相應的處理
*/
import java.io.*;
public class ReaderLineTest {
public static void main(String[] args) throws IOException{
//初始化一個FileReader對象
FileReader fr = new FileReader("E:\\test.txt");
//講文件內的內容全部打印出來,使用的文件是BufferedWriter中創建的文件
String s = null;
while((s = MeReaderLine(fr)) != null){
System.out.println(s);
}
fr.close();
}
/*
* 傳入一個流對象,返回一個String
*/
public static String MeReaderLine(FileReader fr) throws IOException{
StringBuffer bf = new StringBuffer();
if(fr == null)
return null;
int temp = 0;
try{
while((temp = fr.read()) != -1){
if(temp == 13)
continue;
if(temp == 10)
return bf.toString();
bf.append((char)temp);
}
if(bf.length() == 0)
return null;
else
return bf.toString();
}catch(IOException e){
throw new RuntimeException("讀取失敗");
}
}
}
九、字節流輸入輸出
1. 字節輸入流--InputStream
InputStream是字節輸入流,可讀取任意文件。read
可 讀取字節數組,可讀單個字節。此類是抽象類,不能直接用,所以找子類 FileInputStream。
-
FileInputStream()
構造方法,傳遞String的文件名 -
read()
方法讀取文件結尾返回-1 -
read(字節數組)
利用數組作為緩沖,提高流的讀取效率
根據字符流的原理,字節流中的read()讀取到的字節存儲到字節數組中,返回讀取一次的字節數中有效字節個數,文件末尾返回-1
-
int available()
,返回字節輸入流中封裝的文件的字節數
2. 字節輸出流--OutpuStream
OutpuStream字節輸出流,可以寫入任意文件。write(byte) \write(byte[])
寫字節數組或者單個字節。OutpuStream是抽象類,不能直接用,實現找子類 FileOutputStream。FileOutputStream()
構造方法,傳遞String
的文件名。
字節流寫數據的時候,不需要刷新,但是要關閉
3. 字節流緩沖區(不是很重要,會用就好了)
字節流緩沖區和字符流緩沖區用法很像,注意字節流是沒有行概念的。
-
BufferedOutputStream()
構造方法,傳遞字節輸出流,寫入的方法寫單個字節,字節數組,沒有行!! -
BufferdInputStream()
構造方法,傳遞字節輸入流,讀取的方法,單個字節,字節數組
下面是一個字節流緩沖區復制文件的demo,并沒有使用buffered...:
package io;
/*
* 使用字節流,實現任意文件的copy
*/
import java.io.*;
public class CopyFileByFileStream {
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
long s = System.currentTimeMillis();
try{
//初始化字節流輸入輸出對象
fis = new FileInputStream("E:\\迅雷下載\\VSCodeSetup-stable.exe");
fos = new FileOutputStream("E:\\vscode.exe");
//byte[]用來緩存字節流數據,1024剛好1kb,效率和內存占用上都還行
byte[] by = new byte[1024];
int len = 0;
while((len =fis.read(by) )!= -1){
fos.write(by,0,len);
}
//下面為異常處理,在finally中關閉流對象即可
}catch(IOException e){
throw new RuntimeException("文件復制失敗");
}finally{
try{
if(fis != null)
fis.close();
}catch(IOException e){
throw new RuntimeException("源文件關閉失敗");
}
try{
if(fos != null)
fos.close();
}catch(IOException e){
throw new RuntimeException("新文件關閉失敗");
}
}
long e = System.currentTimeMillis();
System.out.println(e-s);
}
}
十、鍵盤輸入
個人感覺java的鍵盤輸入其實和C/C++還是有很大區別的,通過一個demo進行練習和實現,具體的實現思路和方法都在下面的這個demo中,核心利用的是System.in返回的BufferedInputStream進行讀取輸入的字符。
其實字符流的read()方法是阻塞式的,之前一直因為直接使用文件進行讀取,所以程序并不會停下來。現在直接調用read方法,因為沒有任何的輸入源,所以程序就會停下來,等待輸入,鍵盤輸入后才會繼續執行。
package io;
/*
* 鍵盤輸入練習
* 使用StringBuffer實現內容的不定長讀取
* 1.while永真循環,遇到回車結束,判斷\n實現
* 2.while永真循環,用到規定的字符over結束,用endsWith判斷實現
* 3.使用轉換流實現 InputStramReader
*/
import java.io.*;
public class TranseDemo {
public static void main(String[] args) throws IOException{
//getByKeyboard();
getBygetByKeyboardAndInputStramReader();
}
/*
* 第3種、轉換流思路實現
* 規定:over結尾,結束read
* 以over結尾的同時,在over之前如果有字符也予以read
*/
public static void getBygetByKeyboardAndInputStramReader() throws IOException{
/*
* System.in返回一個BufferedInputStream對象,也就是字節流的子類
* 把System.in返回的字節流對象傳給InputStreamReader的構造方法,得到轉換流
* 把轉換流傳入BufferedReader的構造方法中就得到了BufferedReader的實例對象br
*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine()) != null){
if(line.endsWith("over")){
if(line.length() != 4){
System.out.println(line.substring(0, line.length()-4));
}
break;
}
System.out.println(line);
}
}
/*
* 1.2思路實現
*/
public static void getByKeyboard() throws IOException{
InputStream in = System.in;
StringBuffer sb = new StringBuffer();
int len = 0;
while(true){
len = in.read();
if(len == 13)
continue;
if(len == 10){
if(sb.toString().endsWith("over"))
break;
System.out.println(sb.toString());
sb.delete(0, sb.length());
}
else{
sb.append((char)len);
}
}
}
}
十一、轉換流
控制臺輸出System.out 引用類型的成員變量,返回一個PrintStream,這個類的名字后綴是一個Stream,所以是字節流,繼承OutputStream,肯定是一個字節輸出流對象。剛才我們的程序,讀取的控制臺輸入,用的是字符流的readLine讀取的鍵盤輸入,將讀取到的字符,輸出到System.out中去,肯定不行,readLine讀取到的是字符數據, System.out返回的是字節輸出流,所以想將接受到的數據打印回控制臺,還是需要轉換流。
OutputStreamWriter , 繼承Writer,也是一個字符流,字符流向字節的橋梁。
構造方法,傳遞字節輸出流OutputStreamWriter(OutputStream out)構造器中,可以傳遞任意的字節輸出流
下面給出一個demo,實現控制臺的輸入輸出以及其中的轉換流操作等等。
package io;
/*
* 轉換流demo:
* 字節流 -> 字符流 -> 程序處理字符流后 -> 字流節
*
* 字符數據輸出到字節流中,使用OutputStreamWriter
*
* 程序直接全部使用匿名類對象,要改變輸入源和輸出源
* 只需要更改InputStreamReader構造方法傳入的參數就OK
* 因為是示例程序,所以直接把IOException扔出去了
*
* 注意:此demo外部的輸入輸出都是字節流,內部使用兩次轉換流
*/
import java.io.*;
public class TranceDemo2 {
public static void main(String[] args) throws IOException {
/*
* 字節流-》字符流
* System.in返回一個BufferedInputStream對象,也就是字節流的子類
* 把System.in返回的字節流對象傳給InputStreamReader的構造方法,得到轉換流
* 把轉換流傳入BufferedReader的構造方法中就得到了BufferedReader的實例對象br
*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
/* 字符流-》字節流
* System.out返回PrintStream,是OutputStream的子類,也就是字節輸出流
* PrintStream傳入OutputStreamWriter的構造方法,得到一個轉換流,此轉換流將輸出的字符流轉為為字節流
* 最后使用BufferedWriter裝飾類操作
*/
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = br.readLine()) != null){
if(line.endsWith("#")){
if(line.length() != 1){
bw.write(line.substring(0, line.length()-1));
bw.newLine();
bw.flush();
}
break;
}
else{
bw.write(line);
bw.newLine();
bw.flush();
}
}
//關閉流對象
br.close();
bw.close();
}
}
十二、轉換流編碼
當流對象沒有指定查詢哪一個碼表,默認走操作系統的中GBK。
- 轉換流OutputStreamWriter構造方法中,寫一個字節輸出流,寫編碼表的名字(String)不區分大小寫,轉換流會將文本數據,以指定的編碼形式寫入文件
- 轉換流InputStreamReader構造方法中,寫一個字節輸讓流,寫編碼表的名字(String)不區分大小寫,轉換流會將文本數據,以指定的編碼形式讀取文件
demo
package io;
/*
* 轉換流的編碼效果
*
* 1.實現多種編碼輸出到文件
* 2.實現多種編碼下的轉換流讀取
*/
import java.io.*;
public class TranseEncodingDemo {
public static void main(String[] args) throws IOException {
writeWithGBK();
writeWithUTF();
readWithUTF();
}
/*
* 在win下GBK實際上是默認的讀取方式
* 單還是顯示實現進行對比
* 注意:傳入的編碼String不區分大小寫
*/
public static void writeWithGBK() throws IOException{
//初始化一個轉換流對象
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByGBK.txt"),"GBK");
ow.write("這是一個測試!");
ow.flush();
ow.close();
}
/*
* 實現UTF-8的編碼輸出到文件
* 注意:傳入的編碼String不區分大小寫
*/
public static void writeWithUTF() throws IOException{
//初始化一個轉換流對象
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByUTF.txt"),"UTF-8");
ow.write("這是一個測試!");
ow.flush();
ow.close();
}
/*
* 讀取gbk就不寫了,直接實現讀取utf8
* 按照默認的gbk編碼讀取輸出:榪欐槸涓?涓祴璇曪紒 (亂碼了)
* 按照指定的編碼utf-8輸出:這是一個測試! (正確)
*/
public static void readWithUTF() throws IOException{
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\textByUTF.txt"),"UTF-8");
char[] ch = new char[20];
int len = isr.read(ch);
System.out.println(new String(ch).substring(0, len).toString());
isr.close();
}
}
十三、字符的編碼和解碼
常見的編碼表有以下幾種:
- 拉丁文 iso8859-1,用于 java網絡服務器Tomcat
- GBK 中文編碼表,2個字節 對應1個漢字 20000個漢字
- UTF-8 3個字節對應1個漢字
漢字進行編碼 :
byte[] b = "漢字內容".getBytes("編碼類型");
漢字進行解碼:
String s = new String(byte[] ,"編碼類型");
demo
package io;
/*
* 漢字的編碼和解碼練習
* 編碼
* utf-8 : -26 -99 -88 -26 -81 -108 -24 -67 -87
* gbk : -47 -18 -79 -56 -48 -7
* Unicode: -2 -1 103 104 107 -44 -113 105
*
* 解碼和編碼會有異常,懶得處理,直接拋了
*/
public class EncodingDemo {
public static void main(String[] args) throws Exception {
String s = "楊比軒";
byte[] by = s.getBytes("gbk");
for(byte b : by){
System.out.print(b + " ");
}
System.out.println("\n" + decoding(by,"gbk"));
}
/*
* 解碼方法
*/
public static String decoding(byte[] by, String coding) throws Exception{
return new String(by,coding);
}
}
附:
java--IO(二)