1. 程式人生 > 資訊 >三部門:ETC 欠費行為將納入徵信體系

三部門:ETC 欠費行為將納入徵信體系

位元組流

有對流的操作只需要三個步驟:

  1. 建立流
  2. 寫資料/讀資料
  3. 關閉流

建立流:

DataOutputStream是一個輸出位元組過濾流,自己無法寫資料,只能給其它的流增加寫多種型別資料的功能,所以在建立物件時需要另外一個流物件:

DataOutputStream dos =
    new DataOutputStream(
    new FileOutputStream( "c:\\test.dat" ) );


//裝飾模式

寫資料:

DataOutputStream提供了多種寫資料的方法,如果希望寫一個long型別的資料,可以使用writeLong( )方法:

long l = 12345678L;
dos.writeLong( l );

關閉流: 只需要關閉最外層的流,所有被封裝到內部的流物件會依次被關閉:

dos.close( );

DataInputStream類的使用方法類似。

其它過濾流的使用方法也類似。

Java提供了BufferedOutputStream和BufferedInputStream類,這也是一對位元組過濾流,它們的作用是可以給其它的流增加快取。

位元組流是以位元組為單位讀寫檔案的,當頻繁的訪問外部儲存裝置時,使用快取可以極大地提高訪問效率。

PrintStream:

  • 既可以作節點流,也可以做過濾流。
  • 同時具備FileOutputStream、BufferedOutputStream和DataOutputStream的功能。
  • 沒有和它一對的輸入流。

字元流

中文亂碼是怎麼來的?

計算機使用二進位制儲存資料,客觀世界中的任何物件抽象到計算機中都是二進位制的資料。

思考:計算機如何儲存一個字元?

在文字編輯器中,每一個字元都是一個圖形。

16個位元組能表示字母A的圖形,這個圖形資料儲存在字型檔案中。但是還需要一個編碼和字母A對應起來,這個編碼是十進位制的65。

文字檔案也是以二進位制的方式儲存的,文字編輯器在開啟一個文字檔案時,如果檔案中出現了數值65,文字編輯器就到作業系統的字型檔案中尋找65所對應的圖形資料,然後顯示出來。

實際上並不能保證字元和編碼是一一對應的關係。

每一個國家都自己制訂了一套編碼,包含自己國家需要用到的所有字元,這套編碼就是字符集

在不同的字符集中同一個編碼可能會對應不同的字元,如果一個檔案編碼和解碼使用的字符集不統一,就會出現亂碼問題。

常見的字符集:

美國:

  • ASCII(美國資訊交換標準程式碼)
  • 第一套字符集,包含英語字母、數字、符號及特殊字元
  • 使用一個byte表示一個字元
  • 以後的字符集都相容ASCII字符集,所以英語永遠沒有亂碼

常見的字符集:

歐洲:

  • ISO-8859-X(共15個字符集)
  • 包含歐洲各種語言中使用的字元

常見的字符集:

中國大陸:

  • GB2312 使用2個位元組表示一個漢字
  • 只包含常用漢字,但是不包含生僻字
  • GBK 相容GB2312
  • 加入了生僻字、東亞國家文字

常見的字符集:

港澳臺地區:

  • BIG5 只包含繁體字
  • 與GB2312和GBK毫不相容

常見的字符集:

國際通用字符集:

  • UNICODE(char資料型別) 幾乎包含所有語言的常用文字,是一套統一的字符集
  • 使用2個位元組表示一個字元
  • UTF-8 UNICODE的一種變體
  • 使用變長位元組表示字元,使用3個位元組表示一箇中文漢字

思考:如果使用GBK的字符集給一個在臺灣的人傳送訊息,對方會收到什麼內容?

String s = new String( "你好".getBytes( "GBK"//編碼 ),
    "BIG5"//解碼 );

中國程式設計師無法迴避亂碼問題,只有知道了為什麼出現亂碼,才能夠在出現亂碼時解決問題。

字元流只能讀寫文字檔案,而不能讀寫二進位制檔案。

文字檔案也是以二進位制的方式儲存的,也可以使用位元組流讀寫,為什麼還要使用字元流處理文字檔案呢?

使用字元流可以很方便的處理字元的編解碼問題。

FileReader和FileWriter是一對字元節點流,可以用來讀寫檔案,每次傳輸一個字元,所有的用法和FileInputStream、FileOutputStream相同。

BufferedReader和BufferedWriter是一對字元過濾流,可以給其它的字元流增加輸入或輸出快取,提高輸入和輸出的效率。

PrintWriter同時具備FileWriter和BufferedWriter的功能,它既是節點流,也是過濾流,並且沒有和它一對的輸入流。

橋轉換流

已經拿到了一個位元組流物件,如果想把它轉換成字元流,應該怎樣做?

InputStreamReader和OutputStreamWriter是一對特殊功能的流。他們的父類分別是Reader和Writer,也就是說,它們是字元流。

建立這兩個類的物件需要其它的流物件,也就是說,它們是過濾流。

這兩個流的特殊之處在於,他們是字元流,但是需要一個位元組流建立物件。

這兩個流的作用是把一個位元組流包裝成字元流,在兩種不同型別的流之間搭了一座橋,所以也叫橋轉換流。

使用BufferedWriter包裝一個位元組流:

BufferedWriter writer =
    new BufferedWriter(   //字元流
    new OutputStreamWriter(
    new FileOutputStream( "c:\\test.dat" ),    //位元組流
    "UTF-8" ) );  //編碼字符集

使用BufferedReader包裝一個位元組流:

BufferedReader reader =
    new BufferedReader(   //字元流
    new InputStreamReader(
    new FileInputStream( "c:\\test.dat" ),   //位元組流
    "UTF-8" ) );   //解碼字符集

序列化和克隆

與其說是把一個物件儲存到檔案中,不如說是把這個物件在記憶體中的資料通過輸出位元組流儲存在一個二進位制檔案中。

ObjectOutputStream類有一個writeObject( )方法,可以把一個Object型別的物件寫入流中:

ObjectOutputStream oos =
    new ObjectOutputStream(
    new FileOutputStream( "c:\\test.dat" ) );
Student stu = new Student( "zhangsan", 20 );
oos.writeObject( stu );


//丟擲異常

把一個物件寫入流中的過程叫做序列化,從流中讀出一個物件的過程叫做反序列化。

要想把一個物件序列化,前提是這個物件能序列化,不是所有物件都能序列化和反序列化。

如果試圖序列化一個無法序列化的物件,就會丟擲異常。

如何讓一個Student物件能夠序列化?

如果想讓一個物件能夠序列化,必須讓這個物件所屬的類實現Serializable介面。Serializable介面是一個標識介面,沒有任何方法的宣告。

序列化物件是有風險的,讓一個類實現Serializable介面就是程式設計師主動承擔序列化物件的風險。

class Student implements Serializable { …… }
ObjectOutputStream oos =
    new ObjectOutputStream(
    new FileOutputStream( "c:\\test.dat" ) );
Student stu = new Student( "zhangsan", 20 );   //序列化物件
oos.writeObject( stu );
ObjectInputStream ois =
    new ObjectInputStream(
    new FileInputStream( "c:\\test.dat" ) );
Student stu = ( Student )ois.readObject( );    //反序列化物件
……

思考:把一個物件序列化到檔案中,都會儲存物件的哪些內容?

只會把物件的屬性儲存,物件的方法不會儲存。

思考:一個物件要能夠序列化,屬性必須滿足什麼要求?

屬性必須也能序列化。

如果屬性包含子屬性,必須保證子屬效能夠序列化。

如果屬性是一個集合,必須保證集合中的元素能夠序列化。

常用類的物件都能序列化,但是如果一個物件包含了一個不能序列化的屬性怎麼辦?

如果希望某個屬性不被序列化,可以使用transient修飾符修飾這個屬性。被修飾的屬性成為臨時屬性,不會參與序列化。

class Student implements Serializable {
    private transient int score;   //臨時屬性不參與序列化
}
Student stu = new Student( "zhangsan", 20 );
oos.writeObject( stu );
stu.setAge( 21 );
oos.writeObject( stu );
Student s1 = ( Student )ois.readObject( );
Student s2 = ( Student )ois.readObject( );


//s1和s2的age屬性值相同。都是修改前的值

ObjectOutputStream物件在寫同一個物件時,即使物件的屬性發生變化,寫入的結果也不會改變。

如果要避免這種情況,需要再建立一個和原物件相同的物件,只要物件的記憶體地址變了,序列化時就會重新寫入新的屬性值。

思考:如何建立一個和原物件相同的物件?

只要一個類實現了Cloneable介面,並覆蓋Object類的clone( )方法,擴大訪問許可權修飾符,就能在記憶體中複製出一個相同的物件。

Cloneable介面也是一個標識介面。

克隆物件是有風險的,讓一個類實現Cloneable介面就是程式設計師主動承擔克隆物件的風險。

class Student implements Serializable, Cloneable{
    public Object clone( ) {
        Object o = null;
        try{
            o = super.clone( ); 
        } catch( Exception e ) { }
        return o;
    }
}
Student stu = new Student( "zhangsan", 20 );
oos.writeObject( stu );
stu.setAge( 21 );
oos.writeObject( stu.clone( ) );   //新物件
Student s1 = ( Student )ois.readObject( );
Student s2 = ( Student )ois.readObject( );
 

//兩個物件屬性不一致了

物件的克隆分為深克隆和淺克隆。

淺克隆僅僅克隆物件和基本型別的屬性,原物件和新物件的引用型別的屬性為同一個物件。

深克隆會遞迴的克隆一個物件的所有屬性。

Object類的clone( )方法是淺克隆。

如何深克隆一個物件?

把一個物件序列化,然後再反序列化回來,此時的新物件就是一個深克隆的物件。