黑馬程序員_IO流入門講解

IO流入門講解        

IO流:用於處理設備上數據。

流:可以理解數據的流動,就是一個數據流。IO流最終要以對象來體現,對象都存在IO包中。

流也進行分類:

1:輸入流(讀)和輸出流(寫)。

2:因爲處理的數據不同,分爲字節流和字符流。 

字節流:處理字節數據的流對象。設備上的數據無論是圖片或者dvd,文字,它們都以二進制存儲的。二進制的最終都是以一個8位爲數據單元進行體現,所以計算機中的最小數據單元就是字節。意味着,字節流可以處理設備上的所有數據,所以字節流一樣可以處理字符數據。

那麼爲什麼要有字符流呢?因爲字符每個國家都不一樣,所以涉及到了字符編碼問題,那麼GBK編碼的中文用unicode編碼解析是有問題的,所以需要獲取中文字節數據的同時指定的編碼表纔可以解析正確數據。爲了方便於文字的解析,所以將字節流和編碼表封裝成對象,這個對象就是字符流。只要操作字符數據,優先考慮使用字符流體系。

注意:流的操作只有兩種:讀和寫。

流的體系因爲功能不同,但是有共性內容,不斷抽取,形成繼承體系。該體系一共有四個基類,而且都是抽象類。

字節流:InputStream  OutputStream

字符流:Reader  Writer

在這四個系統中,它們的子類,都有一個共性特點:子類名後綴都是父類名,前綴名都是這個子類的功能名稱。

--------------------------------------------------------------------------------------------------------------------

public static void main(String[] args) throws IOException { //讀、寫都會發生IO異常

/*

1:創建一個字符輸出流對象,用於操作文件。該對象一建立,就必須明確數據存儲位置,是一個文件。

2:對象產生後,會在堆內存中有一個實體,同時也調用了系統底層資源,在指定的位置創建了一個存儲數據的文件。

3:如果指定位置,出現了同名文件,文件會被覆蓋。

*/

FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException

/*

調用Writer類中的write方法寫入字符串。字符串並未直接寫入到目的地中,而是寫入到了流中,(其實是寫入到內存緩衝區中)。怎麼把數據弄到文件中?

*/

fw.write("abcde");

fw.flush(); // 刷新緩衝區,將緩衝區中的數據刷到目的地文件中。

fw.close(); // 關閉流,其實關閉的就是java調用的系統底層資源。在關閉前,會先刷新該流。

}

close()flush()的區別:

flush():將緩衝區的數據刷到目的地中後,流可以使用。

close():將緩衝區的數據刷到目的地中後,流就關閉了,該方法主要用於結束調用的底層資源。這個動作一定做。

--------------------------------------------------------------------------------------------------------------------

io異常的處理方式:io一定要寫finally

FileWriter寫入數據的細節:

1window中的換行符:\r\n兩個符號組成。 linux\n

2:續寫數據,只要在構造函數中傳入新的參數true

3:目錄分割符:window \\  /

public static void main(String[] args) {

FileWriter fw = null;

try {

fw = new FileWriter("demo.txt",true);

fw.write("abcde");

}

catch (IOException e ){

System.out.println(e.toString()+"....");

}

finally{

if(fw!=null)

try{

fw.close();

}

catch (IOException e){

System.out.println("close:"+e.toString());

}

}

}

--------------------------------------------------------------------------------------------------------------------

FileReader使用Reader體系,讀取一個文本文件中的數據。返回 -1 ,標誌讀到結尾。

import java.io.*;

class  FileReaderDemo {

public static void main(String[] args) throws IOException {

/*

創建可以讀取文本文件的流對象,FileReader讓創建好的流對象和指定的文件相關聯。

*/

FileReader fr = new FileReader("demo.txt");

int ch = 0;

while((ch = fr.read())!= -1) { //條件是沒有讀到結尾

System.out.println((char)ch); //調用讀取流的read方法,讀取一個字符。

}

fr.close();

}

}

--------------------------------------------------------------------------------------------------------------------

讀取數據的第二種方式:第二種方式較爲高效,自定義緩衝區。

import java.io.*;

class FileReaderDemo2 {

public static void main(String[] args) throws IOException {

FileReader fr = new FileReader("demo.txt"); //創建讀取流對象和指定文件關聯。

//因爲要使用read(char[])方法,將讀取到字符存入數組。所以要創建一個字符數組,一般數組的長度都是1024的整數倍。

char[] buf = new char[1024];

int len = 0;

while(( len=fr.read(buf)) != -1) {

System.out.println(new String(buf,0,len));

}

fr.close();

}

}

--------------------------------------------------------------------------------------------------------------------

IO中的使用到了一個設計模式:裝飾設計模式。

裝飾設計模式解決:對一組類進行功能的增強。

包裝:寫一個類(包裝類)對被包裝對象進行包裝;

 * 1、包裝類和被包裝對象要實現同樣的接口;

 * 2、包裝類要持有一個被包裝對象;

 * 3、包裝類在實現接口時,大部分方法是靠調用被包裝對象來實現的,對於需要修改的方法我們自己實現;

--------------------------------------------------------------------------------------------------------------------

字符流:

Reader用於讀取字符流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()

     |---BufferedReader:從字符輸入流中讀取文本,緩衝各個字符,從而實現字符、數組和行的高效讀取。 可以指定緩衝區的大小,或者可使用默認的大小。大多數情況下,默認值就足夠大了。

        |---LineNumberReader:跟蹤行號的緩衝字符輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分別用於設置和獲取當前行號。

     |---InputStreamReader:是字節流通向字符流的橋樑:它使用指定的 charset 讀取字節並將其解碼爲字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺默認的字符集。

        |---FileReader:用來讀取字符文件的便捷類。此類的構造方法假定默認字符編碼和默認字節緩衝區大小都是適當的。要自己指定這些值,可以先在 FileInputStream 上構造一個 InputStreamReader

     |---CharArrayReader:

     |---StringReader:

-------------------------------------------------

Writer寫入字符流的抽象類。子類必須實現的方法僅有 write(char[], int, int)flush() 和 close()

     |---BufferedWriter:將文本寫入字符輸出流,緩衝各個字符,從而提供單個字符、數組和字符串的高效寫入。

     |---OutputStreamWriter:是字符流通向字節流的橋樑:可使用指定的 charset 將要寫入流中的字符編碼成字節。它使用的字符集可以由名稱指定或顯式給定,否則將接受平臺默認的字符集。

        |---FileWriter:用來寫入字符文件的便捷類。此類的構造方法假定默認字符編碼和默認字節緩衝區大小都是可接受的。要自己指定這些值,可以先在 FileOutputStream 上構造一個 OutputStreamWriter

     |---PrintWriter:

     |---CharArrayWriter:

     |---StringWriter:

---------------------------------

字節流:

InputStream是表示字節輸入流的所有類的超類。

     |--- FileInputStream:從文件系統中的某個文件中獲得輸入字節。哪些文件可用取決於主機環境。FileInputStream 用於讀取諸如圖像數據之類的原始字節流。要讀取字符流,請考慮使用 FileReader

     |--- FilterInputStream:包含其他一些輸入流,它將這些流用作其基本數據源,它可以直接傳輸數據或提供一些額外的功能。

        |--- BufferedInputStream:該類實現緩衝的輸入流。

        |--- Stream:

     |--- ObjectInputStream:

     |--- PipedInputStream:

-----------------------------------------------

OutputStream此抽象類是表示輸出字節流的所有類的超類。

     |--- FileOutputStream:文件輸出流是用於將數據寫入 File 或 FileDescriptor 的輸出流。

     |--- FilterOutputStream:此類是過濾輸出流的所有類的超類。

        |--- BufferedOutputStream:該類實現緩衝的輸出流。

        |--- PrintStream:

        |--- DataOutputStream:

     |--- ObjectOutputStream:

     |--- PipedOutputStream:

--------------------------------

緩衝區是提高效率用的,給誰提高呢?

BufferedWriter:是給字符輸出流提高效率用的,那就意味着,緩衝區對象建立時,必須要先有流對象。明確要提高具體的流對象的效率。

FileWriter fw = new FileWriter("bufdemo.txt");

BufferedWriter bufw = new BufferedWriter(fw);//讓緩衝區和指定流相關聯。

for(int x=0; x<4; x++){

bufw.write(x+"abc");

bufw.newLine(); //寫入一個換行符,這個換行符可以依據平臺的不同寫入不同的換行符。

bufw.flush();//對緩衝區進行刷新,可以讓數據到目的地中。

}

bufw.close();//關閉緩衝區,其實就是在關閉具體的流。

-----------------------------

BufferedReader

FileReader fr = new FileReader("bufdemo.txt");

BufferedReader bufr  = new BufferedReader(fr);

String line = null;

while((line=bufr.readLine())!=null){  //readLine方法返回的時候是不帶換行符的。

System.out.println(line);

}

bufr.close();

-----------------------------

//記住,只要一讀取鍵盤錄入,就用這句話。

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//輸出到控制檯

String line = null;

while((line=bufr.readLine())!=null){

if("over".equals(line))

break;

bufw.write(line.toUpperCase());//將輸入的字符轉成大寫字符輸出

bufw.newLine();

bufw.flush();

}

bufw.close();

bufr.close();

------------------------------

流對象:其實很簡單,就是讀取和寫入。但是因爲功能的不同,流的體系中提供N多的對象。那麼開始時,到底該用哪個對象更爲合適呢?這就需要明確流的操作規律。

流的操作規律:

1,明確源和目的。

數據源:就是需要讀取,可以使用兩個體系:InputStreamReader

數據匯:就是需要寫入,可以使用兩個體系:OutputStreamWriter

2,操作的數據是否是純文本數據?

如果是:數據源:Reader

    數據匯:Writer 

如果不是:數據源:InputStream

      數據匯:OutputStream

3,雖然確定了一個體系,但是該體系中有太多的對象,到底用哪個呢?

明確操作的數據設備。

數據源對應的設備:硬盤(File),內存(數組),鍵盤(System.in)

數據匯對應的設備:硬盤(File),內存(數組),控制檯(System.out)

4,需要在基本操作上附加其他功能嗎?比如緩衝。

如果需要就進行裝飾。

轉換流特有功能:轉換流可以將字節轉成字符,原因在於,將獲取到的字節通過查編碼表獲取到指定對應字符。

轉換流的最強功能就是基於 字節流 編碼表 。沒有轉換,沒有字符流。

發現轉換流有一個子類就是操作文件的字符流對象:

InputStreamReader

|--FileReader

OutputStreamWriter

|--FileWrier

想要操作文本文件,必須要進行編碼轉換,而編碼轉換動作轉換流都完成了。所以操作文件的流對象只要繼承自轉換流就可以讀取一個字符了。

但是子類有一個侷限性,就是子類中使用的編碼是固定的,是本機默認的編碼表,對於簡體中文版的系統默認碼錶是GBK

FileReader fr = new FileReader("a.txt");

InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");

以上兩句代碼功能一致,

如果僅僅使用平臺默認碼錶,就使用FileReader fr = new FileReader("a.txt"); //因爲簡化。

如果需要制定碼錶,必須用轉換流。

轉換流 字節流+編碼表。

轉換流的子類File = 字節流 默認編碼表。

凡是操作設備上的文本數據,涉及編碼轉換,必須使用轉換流。

-----------------------------------------------------------------------------------------------

File類:將文件系統中的文件和文件夾封裝成了對象。提供了更多的屬性和行爲可以對這些文件和文件夾進行操作。這些是流對象辦不到的,因爲流只操作數據。

File類常見方法:

1:創建。

boolean createNewFile():在指定目錄下創建文件,如果該文件已存在,則不創建。而對操作文件的輸出流而言,輸出流對象已建立,就會創建文件,如果文件已存在,會覆蓋。除非續寫。

boolean mkdir():創建此抽象路徑名指定的目錄。

boolean mkdirs():創建多級目錄。 

2:刪除。

boolean delete():刪除此抽象路徑名錶示的文件或目錄。

void deleteOnExit():在虛擬機退出時刪除。

注意:在刪除文件夾時,必須保證這個文件夾中沒有任何內容,纔可以將該文件夾用delete刪除。

window的刪除動作,是從裏往外刪。注意:java刪除文件不走回收站。要慎用。

3:獲取.

long length():獲取文件大小。

String getName():返回由此抽象路徑名錶示的文件或目錄的名稱。

String getPath():將此抽象路徑名轉換爲一個路徑名字符串。

String getAbsolutePath():返回此抽象路徑名的絕對路徑名字符串。

String getParent():返回此抽象路徑名父目錄的抽象路徑名,如果此路徑名沒有指定父目錄,則返回 null。

long lastModified():返回此抽象路徑名錶示的文件最後一次被修改的時間。

File.pathSeparator:返回當前系統默認的路徑分隔符,windows默認爲 “;”。

File.Separator:返回當前系統默認的目錄分隔符,windows默認爲 “\

4:判斷:

boolean exists():判斷文件或者文件夾是否存在。

boolean isDirectory():測試此抽象路徑名錶示的文件是否是一個目錄。

boolean isFile():測試此抽象路徑名錶示的文件是否是一個標準文件。

boolean isHidden():測試此抽象路徑名指定的文件是否是一個隱藏文件。

boolean isAbsolute():測試此抽象路徑名是否爲絕對路徑名。

5:重命名。

 boolean renameTo(File dest):可以實現移動的效果。剪切+重命名。

String[] list():列出指定目錄下的當前的文件和文件夾的名稱。包含隱藏文件。

如果調用list方法的File 對象中封裝的是一個文件,那麼list方法返回數組爲null。如果封裝的對象不存在也會返回null。只有封裝的對象存在並且是文件夾時,這個方法纔有效。

------------------------------------------------------------------------------------------------

遞歸:就是函數自身調用自身。

什麼時候用遞歸呢?

當一個功能被重複使用,而每一次使用該功能時的參數不確定,都由上次的功能元素結果來確定。

簡單說:功能內部又用到該功能,但是傳遞的參數值不確定。(每次功能參與運算的未知內容不確定)。

遞歸的注意事項:

1:一定要定義遞歸的條件。

2:遞歸的次數不要過多。容易出現 StackOverflowError 棧內存溢出錯誤。

其實遞歸就是在棧內存中不斷的加載同一個函數。

------------------------------------------------------------------------------------------------

Java.util.Properties:一個可以將鍵值進行持久化存儲的對象。Map--Hashtable的子類。

Map

|--Hashtable

|--Properties用於屬性配置文件,鍵和值都是字符串類型。

特點:1:可以持久化存儲數據。2:鍵值都是字符串。3:一般用於配置文件。

|-- load():將流中的數據加載進集合。

原理:其實就是將讀取流和指定文件相關聯,並讀取一行數據,因爲數據是規則的key=value,所以獲取一行後,通過 = 對該行數據進行切割,左邊就是鍵,右邊就是值,將鍵、值存儲到properties集合中。

|-- store():寫入各個項後,刷新輸出流。

|-- list():將集合的鍵值數據列出到指定的目的地。

-------------------------------------------------------------------------------------------------

以下介紹IO包中擴展功能的流對象:基本都是裝飾設計模式。

Java.io.outputstream.PrintStream:打印流

1:提供了更多的功能,比如打印方法。可以直接打印任意類型的數據。

2:它有一個自動刷新機制,創建該對象,指定參數,對於指定方法可以自動刷新。

3:它使用的本機默認的字符編碼. 

4:該流的print方法不拋出IOException。

該對象的構造函數。

PrintStream(File file)  :創建具有指定文件且不帶自動行刷新的新打印流。 

PrintStream(File file, String csn) :創建具有指定文件名稱和字符集且不帶自動行刷新的新打印流。 

PrintStream(OutputStream out) :創建新的打印流。 

PrintStream(OutputStream out, boolean autoFlush) :創建新的打印流。 

PrintStream(OutputStream out, boolean autoFlush, String encoding) :創建新的打印流。 

PrintStream(String fileName) :創建具有指定文件名稱且不帶自動行刷新的新打印流。 

PrintStream(String fileName, String csn) 

PrintStream可以操作目的:1:File對象。2:字符串路徑。3:字節輸出流。

前兩個都JDK1.5版本纔出現。而且在操作文本文件時,可指定字符編碼了。

當目的是一個字節輸出流時,如果使用的println方法,可以在printStream對象上加入一個true參數。這樣對於println方法可以進行自動的刷新,而不是等待緩衝區滿了再刷新。最終print方法都將具體的數據轉成字符串,而且都對IO異常進行了內部處理。

既然操作的數據都轉成了字符串,那麼使用PrintWriter更好一些。因爲PrintWrite是字符流的子類,可以直接操作字符數據,同時也可以指定具體的編碼。

--------------------------------------------------------

PrintWriter:具備了PrintStream的特點同時,還有自身特點:

該對象的目的地有四個:1:File對象。2:字符串路徑。3:字節輸出流。4:字符輸出流。

開發時儘量使用PrintWriter。

方法中直接操作文件的第二參數是編碼表。

直接操作輸出流的,第二參數是自動刷新。

//讀取鍵盤錄入將數據轉成大寫顯示在控制檯.

BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//源:鍵盤輸入

//目的:把數據寫到文件中,還想自動刷新。

PrintWriter out = new PrintWriter(new FileWriter("out.txt"),true);//設置true後自動刷新

String line = null;

while((line=bufr.readLine())!=null){

if("over".equals(line))

break;

out.println(line.toUpperCase());//轉大寫輸出

}

//注意:System.in,System.out這兩個標準的輸入輸出流,在jvm啓動時已經存在了。隨時可以使用。當jvm結束了,這兩個流就結束了。但是,當使用了顯示的close方法關閉時,這兩個流在提前結束了。

out.close();

bufr.close();

------------------------------------------------------------------------------------------------

SequenceInputStream:序列流,作用就是將多個讀取流合併成一個讀取流。實現數據合併。

表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。

這樣做,可以更方便的操作多個讀取流,其實這個序列流內部會有一個有序的集合容器,用於存儲多個讀取流對象。

該對象的構造函數參數是枚舉,想要獲取枚舉,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中沒有枚舉,只有自己去創建枚舉對象。

但是方法怎麼實現呢?因爲枚舉操作的是具體集合中的元素,所以無法具體實現,但是枚舉和迭代器是功能一樣的,所以,可以用迭代替代枚舉。

合併原理:多個讀取流對應一個輸出流。

切割原理:一個讀取流對應多個輸出流。

import java.io.*;

import java.util.*;

class  SplitFileDemo{

private static final String CFG = ".properties";

private static final String SP = ".part";

public static void main(String[] args) throws IOException{

File file = new File("c:\\0.bmp");

File dir = new File("c:\\partfiles");

meger(dir);

}

//數據的合併。

public static void meger(File dir)throws IOException{

if(!(dir.exists() && dir.isDirectory()))

throw new RuntimeException("指定的目錄不存在,或者不是正確的目錄");

File[] files = dir.listFiles(new SuffixFilter(CFG));

if(files.length==0)

throw new RuntimeException("擴展名.proerpties的文件不存在");

//獲取到配置文件

File config = files[0];

//獲取配置文件的信息。

Properties prop = new Properties();

FileInputStream fis = new FileInputStream(config);

prop.load(fis);

String fileName = prop.getProperty("filename");

int partcount = Integer.parseInt(prop.getProperty("partcount"));

//--------------------------

File[] partFiles = dir.listFiles(new SuffixFilter(SP));

if(partFiles.length!=partcount)

throw new RuntimeException("缺少碎片文件");

//---------------------

ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();

for(int x=0; x<partcount; x++){

al.add(new FileInputStream(new File(dir,x+SP)));

}

Enumeration<FileInputStream> en = Collections.enumeration(al);

SequenceInputStream sis = new SequenceInputStream(en);

File file = new File(dir,fileName);

FileOutputStream fos = new FileOutputStream(file);

byte[] buf = new byte[1024];

int len = 0;

while((len=sis.read(buf))!=-1){

fos.write(buf,0,len);

}

fos.close();

sis.close();

}

//帶有配置信息的數據切割。

public static void splitFile(File file)throws IOException{

//用一個讀取流和文件關聯。

FileInputStream fis = new FileInputStream(file);

//創建目的地。因爲有多個。所以先創建引用。

FileOutputStream fos = null;

//指定碎片的位置。

File dir = new File("c:\\partfiles");

if(!dir.exists())

dir.mkdir();

//碎片文件大小引用。

File f = null;

byte[] buf = new byte[1024*1024];

//因爲切割完的文件通常都有規律的。爲了簡單標記規律使用計數器。

int count = 0;

int len = 0;

while((len=fis.read(buf))!=-1){

f = new File(dir,(count++)+".part");

fos = new FileOutputStream(f);

fos.write(buf,0,len);

fos.close();

}

//碎片文件生成後,還需要定義配置文件記錄生成的碎片文件個數。以及被切割文件的名稱。

//定義簡單的鍵值信息,可是用Properties。

String filename = file.getName();

Properties prop = new Properties();

prop.setProperty("filename",filename);

prop.setProperty("partcount",count+"");

File config = new File(dir,count+".properties");

fos = new FileOutputStream(config);

prop.store(fos,"");

fos.close();

fis.close();

}

}

class SuffixFilter implements FileFilter{

private String suffix;

SuffixFilter(String suffix){

this.suffix  = suffix;

}

public boolean accept(File file){

return  file.getName().endsWith(suffix);

}

}

-----------------------------------------------------------------------------------------------

RandomAccessFile:

特點:

1:該對象即可讀取,又可寫入。

2:該對象中的定義了一個大型的byte數組,通過定義指針來操作這個數組。

3:可以通過該對象的getFilePointer()獲取指針的位置,通過seek()方法設置指針的位置。

4:該對象操作的源和目的必須是文件。 

5:其實該對象內部封裝了字節讀取流和字節寫入流。

注意:實現隨機訪問,最好是數據有規律。

class RandomAccessFileDemo{

public static void main(String[] args) throws IOException{

write();

read();

randomWrite();

}

//隨機寫入數據,可以實現已有數據的修改。

public static void randomWrite()throws IOException{

RandomAccessFile raf = new RandomAccessFile("random.txt","rw");

raf.seek(8*4);

System.out.println("pos :"+raf.getFilePointer());

raf.write("王武".getBytes());

raf.writeInt(102);

raf.close();

}

public static void read()throws IOException{

RandomAccessFile raf = new RandomAccessFile("random.txt","r");//只讀模式。

//指定指針的位置。

raf.seek(8*1);//實現隨機讀取文件中的數據。注意:數據最好有規律。

System.out.println("pos1 :"+raf.getFilePointer());

byte[] buf = new byte[4];

raf.read(buf);

String name = new String(buf);

int age = raf.readInt();

System.out.println(name+"::"+age);

System.out.println("pos2 :"+raf.getFilePointer());

raf.close();

}

public static void write()throws IOException{

//rw:當這個文件不存在,會創建該文件。當文件已存在,不會創建。所以不會像輸出流一樣覆蓋。

RandomAccessFile raf = new RandomAccessFile("random.txt","rw");//rw讀寫模式

//往文件中寫入人的基本信息,姓名,年齡。

raf.write("張三".getBytes());

raf.writeInt(97);

raf.close();

}

}

------------------------------------------------------------------------------------------------

管道流:管道讀取流和管道寫入流可以像管道一樣對接上,管道讀取流就可以讀取管道寫入流寫入的數據。

注意:需要加入多線程技術,因爲單線程,先執行read,會發生死鎖,因爲read方法是阻塞式的,沒有數據的read方法會讓線程等待。

public static void main(String[] args) throws IOException{

PipedInputStream pipin = new PipedInputStream();

PipedOutputStream pipout = new PipedOutputStream();

pipin.connect(pipout);

new Thread(new Input(pipin)).start();

new Thread(new Output(pipout)).start();

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章