1. 程式人生 > 實用技巧 >Java I/O

Java I/O

I/O

流,用來實現程式或程序間的通訊,或讀寫外圍裝置、外部檔案等。Java把一切輸入抽象成輸入流,把一切輸出抽象成輸出流(也可以根據流方向來定義,流向內部的是輸入流,流向外部的是輸出流)。

Java的I/O架構由輸入流/輸出流/File/Serializable四部分組成。

一、File

File是檔案和目錄路徑名的抽象表示。File中定義了對檔案和目錄的基本操作:

  1. 路徑相關 如getAbsolutePath()
  2. 檔案屬性 如getParent()、getName()等
  3. 檔案讀寫許可權 如canRead()、setWritable、setReadable
  4. 檔案的建立、刪除 如 createNewFile、delete(當刪除某一目錄時,必須保證該目錄下沒有其他檔案才能正確刪除,否則將刪除失敗)
  5. 建立資料夾 mkdir(建立單級目錄)、mkdirs(建立多級目錄)
  6. 獲取路徑下的檔案、資料夾 list(只會列出路徑下的檔案、資料夾名稱)、listFiles(列出路徑下的檔案)

二、Serializable

Serializable是Java用來進行序列化的。序列化就是把Java物件轉換為位元組序列的過程。序列化之後位元組序列可以用於儲存和傳輸(儲存和傳輸就涉及到了I/O操作)。

在Java中只要讓需要序列化的類實現Serializable即可,同時Android提供了另一種序列化方式Parcelable介面,最後就是網路通訊常用的json,以上就是常見的序列化方式。

序列化-Serializable

序列化:

        Bean bean = new Bean();
        bean.setIndex(100);
        bean.setName("number");
        FileOutputStream outputStream = new FileOutputStream(file);
         objectOutputStream = new ObjectOutputStream(outputStream);
         objectOutputStream.writeObject(bean);
         objectOutputStream.close()

通過上述程式碼我們可以將bean序列化並寫入file中。通過分析原始碼我們瞭解序列化過程如下:

  • 寫入流的頭資訊

頭資訊包含兩個欄位他們是在ObjectStreamConstants類中定義的

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;

頭資訊存放在序列化後的位元組序列的頭部,在反序列化時會校驗其值。

  • 之後呼叫writeObject開始真正的序列化流程,在writeObject會先獲取傳入的要序列化物件的本地描述並封裝到一個名叫ObjectStreamClass的類中,該類例項在構建的時候會儲存諸如類名、是否實現Serializable介面、傳入類的serialVersionUID、傳入類的欄位屬性資訊、writeObjectMethod 、readObjectMethod等等。其中很多資訊都是通過反射獲取的。

  • 獲取本地描述例項之後會把這些資訊寫入流中,此時並未寫物件的資訊,即欄位的值。

  • 最後寫入物件資訊,寫的時候會先寫基本型別的欄位值再寫非基本型別欄位值

    至此序列化過程就完成了。

    反序列化-Serializable

    • 首先會先檢查位元組序列頭部資訊是否匹配
    • 之後呼叫readObject() 讀取流中的位元組,首先會讀取到類的描述資訊ObjectStreamClass 物件,然後利用該物件記憶體儲處的資訊去例項化物件(反射建立物件未使用構造器)然後給物件欄位進行賦值,完成後返回反序列化後的物件。

    注意:

    靜態變數無法序列化

    transient關鍵字修飾的變數無法序列化

    多次序列化同一物件,內部只會序列化一次之後再次序列化會返回已序列化的編號。

    Parcelable

    使用:

    
    public class ABean implements Parcelable {
        private  int index;
        private String name;
    
        protected ABean(Parcel in) {
            index = in.readInt();
            name = in.readString();
        }
       //反序列化功能由CREATOR完成,在CREATOR的內部標明的如何建立序列物件和陣列
        public static final Creator<ABean> CREATOR = new Creator<ABean>() {
            @Override
          //從Parcel中反序列化物件
            public ABean createFromParcel(Parcel in) {
                //其內部呼叫Parcel的一系列readXXX方法實現反序列化過程
                return new ABean(in);
            }
    
            @Override
            public ABean[] newArray(int size) {
                return new ABean[size];
            }
        };
    
        public int getIndex() {
            return index;
        }
    
        public void setIndex(int index) {
            this.index = index;
        }
    
        public String getName() {
            return name == null ? "" : name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
        //序列化過程:
        //重寫writeToParcel方法,我們要在這裡逐一對需要序列化的屬性用Parcel的一系列writeXXX方法寫入
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(index);
            dest.writeString(name);
        }
    }
    
    
    

上面是基本使用,首先要實現Parcelable介面以及其要求實現的兩個方法describeContents、writeToParcel,然後還需要定義一個名字為CREATOR的 Creator物件。

序列化是通過writeToParcel方法實現的,在該方法內部會先寫入傳入類的類名然後呼叫我們實現的writeToParcel方法按順序寫入。

反序列化是通過readParcelable方法實現的,在該方法內部會先通過反射獲取我們在類內部定義的CREATOR,然後呼叫CREATOR的createFromParcel來建立例項物件,這樣就完成了反序列化。

參考連結:

https://blog.csdn.net/willway_wang/article/details/106745481

https://www.cnblogs.com/wangle12138/p/8257016.html

三、輸入輸出流

分類:

  • 按資料流的方向不同分類:輸入流、輸出流
  • 按最小的資料單元分類:位元組流、字元流
  • 按照功能不同分成節點流和處理流

輸入流

Java中所有輸入流都是InputStream或Reader的子類。

輸出流

所有輸出流都是OutputStream或Writer的子類。

位元組流

位元組流是指以 8 位(即 1 byte,8 bit)作為一個數據單元,資料流中最小的資料單元是位元組的流。InputStream/OutputStream 是位元組流的基類。

InputStream

輸入位元組流基類,其子類有FileInputStream、DataInputStream、BufferedInputStream、ByteArrayInputStream、ObjectInputStream等

  • public abstract int read( ):讀取一個byte的資料,返回值是高位補0的int型別值。若返回值=-1說明沒有讀取到任何位元組讀取工作結束。
  • public int read(byte b[ ]):讀取b.length個位元組的資料放到b陣列中。返回值是讀取的位元組數。該方法實際上是呼叫下一個方法實現的
  • public int read(byte b[ ], int off, int len):從輸入流中最多讀取len個位元組的資料,存放到偏移量為off的b陣列中。
  • public int available( ):返回輸入流中可以讀取的位元組數。注意:若輸入阻塞,當前執行緒將被掛起,如果InputStream物件呼叫這個方法的話,它只會返回0,這個方法必須由繼承InputStream類的子類物件呼叫才有用,
  • public long skip(long n):忽略輸入流中的n個位元組,返回值是實際忽略的位元組數, 跳過一些位元組來讀取
  • public int close( ) :我們在使用完後,必須對我們開啟的流進行關閉.
OutputStream

輸出位元組流基類,其子類有FileOutputStream、DataOutputStream、BufferedOutputStream、ByteArrayOutputStream、ObjectOutputStream等

  • public void write(byte b[ ]):將引數b中的位元組寫到輸出流。
  • public void write(byte b[ ], int off, int len) :將引數b的從偏移量off開始的len個位元組寫到輸出流。
  • public abstract void write(int b) :先將int轉換為byte型別,把低位元組寫入到輸出流中。
  • public void flush( ) : 將資料緩衝區中資料全部輸出,並清空緩衝區。
  • public void close( ) : 關閉輸出流並釋放與流相關的系統資源。

字元流

Reader

字元輸入流基類,其子類有BufferedReader、InputStreamReader、StringReader等

  • public int read() throws IOException; //讀取一個字元,返回值為讀取的字元
  • public int read(char cbuf[]) throws IOException;/讀取一系列字元到陣列cbuf[]中,返回值為實際讀取的字元的數量/
  • public abstract int read(char cbuf[],int off,int len) throws IOException;
Writer

字元輸出流基類,其子類有BufferedWriter、OutputStreamWriter、StringWriter等

  • public void write(int c) throws IOException;//將整型值c的低16位寫入輸出流
  • public void write(char cbuf[]) throws IOException;//將字元陣列cbuf[]寫入輸出流
  • public abstract void write(char cbuf[],int off,int len) throws IOException;//將字元陣列cbuf[]中的從索引為off的位置處開始的len個字元寫入輸出流
  • public void write(String str) throws IOException;//將字串str中的字元寫入輸出流
  • public void write(String str,int off,int len) throws IOException;//將字串str 中從索引off開始處的len個字元寫入輸出流

節點流

Java根據流是否直接處理資料分為節點流和處理流,節點流是真正直接處理資料的流,在使用完畢後需要關閉。FileInputStream,FileOutputStrean,FileReader,FileWriter,StringReader,StringWriter等都是節點流。

處理流

處理流是裝飾加工節點流的,它是對一個已存在的流的連線和封裝,通過所封裝的流的功能呼叫實現資料讀寫。BufferedImputStrean,BufferedOutputStream,BufferedReader ,BufferedWriter,InputStreamReader,OutputStreamWriter等都是處理流。

比如Bufferedxxx流都是帶緩衝的流

最後補充一個圖(來源於網路):

四、RandomAccessFile

它是Java提供的隨機存取檔案內容的類,在建構函式中傳入所需許可權(讀、寫等)。其提供了seek函式,該函式可以把檔案游標定位到指定位置從而從此開始讀寫檔案內容。它還提供readInt、readDobule、writeInt等讀寫基本資料型別的方法。底層原理是記憶體對映檔案方式(nio包),即把檔案對映到記憶體後再操作,省去了頻繁磁碟io( 待驗證 )。

RandomAccessFile簡介與使用_LJHSkyWalker的部落格-CSDN部落格_randomaccessfile