java基礎之I/O

一、概述

I/O的本質是通信。

有多種源端和接收端:文件(硬盤)、鍵盤/控制檯、網絡鏈接等
有多種不同的通信方式:順序、隨機存取、緩衝、二進制、按字符、按行、按字等。
java設計了大量的類來解決 這個通信問題。

在電腦上的數據有三種存儲方式,一種是外存,一種是內存,一種是緩存。緩存用於提升計算機工作效率。
將數據衝外存中讀取到內存中的稱爲輸入流,將數據從內存寫入外存中的稱爲輸出流。

I/O類圖:
這裏寫圖片描述

二:File類

1、概述:
既能代表一個文件的名稱,也能代表一個目錄。
File f1 = new File(“c:\java\demo.txt”); File f2 = new File(“c:\java”);

2、各種方法:
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) :可以實現移動的效果: 剪切 +重命名。

6.目錄的操作:

    當file對象代表目錄時,實質上是代表該目錄下一組文件的名稱,是一個文件集合,可以用listFiles方法獲得一個File類型的數組:
    File[ ] f2list = f2.listFiles( );

    當要獲取所有文件目錄包括子目錄時,可以採用遞歸的方式:

     public static void showDir(File dir,int level)
     {        
          System.out.println(getLevel(level)+dir.getName());
          level++;
          File[] files = dir.listFiles();
          for(int x=0; x<files.length; x++)
          {
               if(files[x].isDirectory())
                    showDir(files[x],level);
               else
                    System.out.println(getLevel(level)+files[x]);
          }
     }

     public static String getLevel(int level)
     {
          StringBuilder sb = new StringBuilder();
          sb.append("|--");
          for(int x=0; x<level; x++)
          {
               //sb.append("|--");
               sb.insert(0,"|  ");
          }
          return sb.toString();
     }     

三、RandomAcessFile類:

非流類,主要提供隨機訪問文件的功能,內部封裝了格式化輸入輸出流(DataInputStream和DataOutputStream),必須文件的結構是已知的,才能使用本類來操作。

使用方法:通過getFilePointer獲取指針位置,利用seek()方法在文件中移動指針
可以指定操作模式:只讀或讀寫。讀寫時,如果文件存在,不會像File,PrintWriter那樣覆蓋。

提供瞭如下方法:
(1)RandomAccessFile raf = new RandomAccessFile(“file”,”r或者rw”);
(2)raf.seek(long pos); 把文件指針設定在pos處
(3)long raf.getFilePointer():返回文件指針
(4)long raf.length();返回長度
(5)raf.skipBytes(long);

四、字節流

InputStream接口提供的方法:

    (1)int read(); 讀取1個字節數據,然後返回該字節轉換成的0-255範圍的int值,讀取完爲-1
    注意:這裏read方法中的byte轉換成int和一般類型轉換的byte轉int是不同的,這裏的int範圍一定要在0-255之間。
    (2)int read(byte[]b); 讀取流中數據寫入字節數組,返回本次讀入的長度int值
    (3)int read(byte[]b,int off,int len) 讀取len長度的字節數,寫入b的off開始的字節數組中
    注意:如果考慮效率方面,則可以使用(2)(3)的方法,因爲能夠批量讀取。
    (4)void close(); 關閉流

OutputStream提供瞭如下方法:

    (1)write(int b); 將b先轉型成byte即截取低八位字節,並寫入輸出流,例如如果b=257,則實際寫入的只是1
    (2)write(byte[]b); 將byte數組寫入到輸出流中。
    (3)write(byte[]b,int off,int len);
    (4)void flush();
    (5)void close();關閉前會自行刷新一次

五、字符流

與字節流的對比:

    字符流處理的單元爲2個字節的Unicode字符,分別操作字符、字符數組或字符串;而字節流處理單元爲1個字節, 操作字節和字節數組。
    所有文件的儲存是都是字節(byte)的儲存,在磁盤上保留的並不是文件的字符而是先把字符編碼成字節,再儲存這些字節到磁盤。在讀取文件時,也是一個字節一個字節地讀取以形成字節序列.
    字節流可用於任何類型的對象,包括二進制對象,而字符流只能處理字符或者字符串; 2. 字節流提供了處理任何類型的IO操作的功能,但它不能直接處理Unicode字符,而字符流就可以。

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

    read()返回的值爲int型,是讀入該char[]數組的字符數,不同於字節流的單個字節的int值。

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


編碼問題:

在Java中,字符串用統一的Unicode編碼,每個字符佔用兩個字節,與編碼有關的兩個主要函數爲:

    1)將字符串用指定的編碼集合解析成字節數組,完成Unicode-〉charsetName轉換
    public byte[] getBytes(String charsetName)
    2)將字節數組以指定的編碼集合構造成字符串,完成charsetName-〉Unicode轉換
    public new String(byte[] bytes, String charsetName)

流編碼過程:

    從文件讀取:源bytes–>根據指定的或默認的編碼表編碼後的字符–>Unicode字符(java中最終的char)
    寫入到文件:與String.getBytes([encode])同理:Unicode字符–>encode字符–>bytes。

Unicode統一採用2個字節編碼,UTF-8是Unicode的改進,原本ASCII碼的字符還是一個字節。

Unicode與各編碼之間的直接轉換

    Unicode和GBK
    每個漢字轉換爲兩個字節,且是可逆的,即通過字節可以轉換回字符串
    Unicode和UTF-8
    測試結果如下,每個漢字轉換爲三個字節,且是可逆的,即通過字節可以轉換回字符串
    Unicode和ISO-8859-1
    測試結果如下,當存在漢字時轉換失敗,非可逆,即通過字節不能再轉換回字符串

Unicode與各編碼之間的交叉轉換
在上面直接轉換中,由字符串(Unicode)生成的字節數組,在構造回字符串時,使用的是正確的編碼集合,如果使用的不是正確的編碼集合會怎樣呢?會正確構造嗎?如果不能正確構造能有辦法恢復嗎?會信息丟失嗎?
能夠正確顯示的中間不正確轉換

    String-GBK〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-GBK〉String
    String-UTF-8〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-UTF-8〉String
    String-UTF-8〉ByteArray-GBK〉String-GBK〉ByteArray-UTF-8〉String

六、裝飾器之轉換流

InputStreamReader和InputStreamWriterInputStreamReader:
以字節流爲數據源,經過編碼後變成字符。編碼方式如未指定,使用的是系統默認的編碼表。

InputStreamReader in2 = new InputStreamReader(new FileInputStream("Reader.txt"),"UTF-8");

七、裝飾器之緩衝功能:

BufferedInputStream和BufferedOutputStream:

緩衝流作爲過濾流的中間層,構建緩衝區,提高效率

BufferedInputStream in = new BufferedInputStream(new FileInputStream("1.txt"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("1.txt"));

BufferedReader 和BufferedWriter :

特點:提供了readLine()方法,可以一行一行地讀取文本。

    FileReader fr = new FileReader("bufdemo.txt");
    BufferedReader bufr  = new BufferedReader(fr);
    String line = null;
    while((line=bufr.readLine())!=null)
    { 
        System.out.println(line); //readLine方法返回的時候是不帶換行符的。
    }
    bufr.close();



    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();//關閉緩衝區,其實就是在關閉具體的流。

八、文件的讀寫:文件流:

根據文件的類型選擇相應的流對象:

    文本文件選用字符流,其他選擇字節流。且爲提高效率,多用緩衝功能。輸出流多采用打印流簡化書寫。

字節流:FileInputStream和FileOutputStream:

BufferedInputStream in = new BufferedInputStream(new FileInputStream("c:\\1.bmp"));
PrintStream out = new PrintStream("d:\\1.bmp",true);

字符流:FileReader和FileWriter
寫文本文件時,若需指定編碼表,則使用FileWriter+轉換流,若無需指定,使用PrintWriter簡化書寫。

BufferedReader bufr = new BufferedReader(new FileReader("c:\\1.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("d:\\1.txt"));//使用默認編碼表編碼,若文件存在,則覆蓋。

九、標準輸入輸出:

System.in:鍵盤錄入封裝成一個InputStream,常用轉換流轉換成字符,一行行讀取
System.out: 控制檯輸出,封裝成一個OutputStream,提供了print和println方法。

十、數據格式化輸入輸出流:

DataInputStream和DataOutputStream

實現了DataInput和DataOutput接口的類。
DataInput提供瞭如下方法:
(1)Xxx readXxx();讀取基本數據類型
(2)int read(byte[]b);讀取至字節數組中,返回實際讀取的長度
(3)readChar()讀取一個字符即兩個字節
(4)String readUTF();

DataOutput提供瞭如下方法:
(1)void wrtieXxx(Xxx )寫入基本數據類型
(2)void writeBytes(String)能夠以字節方式寫入String,隨後可以用read讀取。
(3)void writeChars(String)以字符方式寫入,一個字符是兩個字節
(4)void writeUTF(String );

十一、打印流

PrintStream和PrintWriter: 簡化了書寫,且指定輸出流時,可定義自動刷新

PrintStream out = new PrintStream(OutputStream o,boolean autoflush);
提供了方法:
out.print(Xxx);
out.println(Xxx);

十二、序列流

序列流,作用就是將多個讀取流合併成一個讀取流。實現數據合併。
表示其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到到達文件末尾,接着從第二個輸入流讀取,依次類推,直到到達包含的最後一個輸入流的文件末尾爲止。
這樣做,可以更方便的操作多個讀取流,其實這個序列流內部會有一個有序的集合容器,用於存儲多個讀取流對象。

SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration<? extends InputStream> e) 

對象的構造函數參數是枚舉,想要獲取枚舉,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中沒有枚舉,只有自己去創建枚舉對象。
但是方法怎麼實現呢?因爲枚舉操作的是具體集合中的元素,所以無法具體實現,但是枚舉和迭代器是功能一樣的,所以,可以用迭代替代枚舉。

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

十二、操作對象的流與對象序列化

目的:將一個具體的對象進行持久化,寫入到硬盤上。
注意:靜態數據不能被序列化,因爲靜態數據不在堆內存中,是存儲在靜態方法區中。
如何將非靜態的數據不進行序列化?用transient 關鍵字修飾此變量即可。

Serializable:用於啓動對象的序列化功能,可以強制讓指定類具備序列化功能,該接口中沒有成員,這是一個標記接口。這個標記接口用於給序列化類提供UID。這個uid是依據類中的成員的數字簽名進行運行獲取的。如果不需要自動獲取一個uid,可以在類中,手動指定一個名稱爲serialVersionUID id號。依據編譯器的不同,或者對信息的高度敏感性。最好每一個序列化的類都進行手動顯示的UID的指定。

相關流:ObjectInputStream和ObjectOutputStream:
對象流實現了DataInput和DataOutput接口
對於對象:
(1)writeObject(Object);
(2)Object readObject(); 讀取後需要強制類型轉換
對於基本類型:使用readXxx和writeXxx方法

十三、管道流

管道讀取流和管道寫入流可以像管道一樣對接上,管道讀取流就可以讀取管道寫入流寫入的數據。
注意:需要加入多線程技術,因爲單線程,先執行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();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章