天天看點

java基礎之I/O

一、概述

I/O的本質是通信。

有多種源端和接收端:檔案(硬碟)、鍵盤/控制台、網絡連結等

有多種不同的通信方式:順序、随機存取、緩沖、二進制、按字元、按行、按字等。

java設計了大量的類來解決 這個通信問題。

在電腦上的資料有三種存儲方式,一種是外存,一種是記憶體,一種是緩存。緩存用于提升計算機工作效率。

将資料沖外存中讀取到記憶體中的稱為輸入流,将資料從記憶體寫入外存中的稱為輸出流。

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=; 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=; x<level; x++)
          {
               //sb.append("|--");
               sb.insert(,"|  ");
          }
          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=; x<; 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();
    }