《Java編程思想》之I/O系統

1、Java中“流“類庫讓人迷惑的主要原因:創建單一的結果流,卻需要創建多個對象。

2、使用層疊的數個對象爲單個對象動態地、透明地添加職責的方式,稱作“修飾器“模式。修飾器必須與其所修飾的對象具有相同的接口,這使得修飾器的基本應用具有透明性——我們可以想修飾過或沒有修飾過的對象發送相同的消息。

3、爲什麼使用修飾器

         在直接使用擴展子類的方法時,如果導致產生了大量的、用以滿足所需的各種可能的組合的子類,這是通常就會使用修飾器——處理太多的子類已經不太實際

4、修飾器的缺點:增加代碼的複雜性。

5、JavaI/O類庫操作不便的原因在於:我們必須創建許多類——“核心”I/O類型加上所有的修飾器,才能得到我們所希望的單個I/O對象。

6、FilterInputStream和FilterOutputStream是用來提供修飾器類接口以控制特定輸入流和輸出流的倆個類。它們分別繼承自I/O類庫中的基類InputStream和OutputStream。

7、DataInputStream是FilterInputStream的直接子類,它允許我們讀取不用的基本類型數據以及String對象。

8、InputStream和OutputStream是面向字節的,而Reader和Writer是面向字符與兼容Unicode

9、在InputStream和OutputStream的繼承層次結構僅支持8位的字節流,並且不能很好地處理16位的Unicode。由於Unicode用用於字符國際化,所以添加Reader和Write繼承結構就是爲了在所有的I/O操作中都支持Unicode。另外添加了Reader和Write的I/O流類庫使得它的操作比舊類庫更快。

10、但是,Reader和Write並不是用來取代nputStream和OutputStream的,因爲InputStream和OutputStream在面向字節形式的I/O中仍然提供了極有價值的功能。

11、有時,我們需要把來自於“字節”層次結構的類和“字符”層次結構的類結合起來使用。這時,需要使用“適配器(adapter)”類:InputStreamReader可以把InputStream轉換爲Reader,而OutputStreamWriter可以把OutputStream轉換爲Writer。

12、當我們使用DataOutputStream時,寫字符串並且讓DataInputStream能夠恢復它的唯一可靠的做法就是使用UTF-8編碼。UTF-8是Unicode的變體,後者把所有字符都存儲成兩個字節的形式。而我們使用的只是ASCII或者幾乎是ASCII字符(只佔7位),這麼做就顯得極其浪費空間和寬帶,所以UTF-8將ASCII字符編碼成單一字節的形式,而非ASCII字符則編碼成兩到三個字節的形式。另外,字符串的長度存儲在兩個字節中

13、System.in是一個沒有被加工過的InputStream,所以在讀取System.in之前必須對其進行加工。

14、標準I/O重定向:如果我們突然開始在顯示器上創建大量輸出,而這些輸出滾動得太快以至於無法閱讀是,重定向輸出就顯得極爲有用。重定向有一下方法:

SetIn(InputStream)
SetOut(InputStream)
SetErr(InputStream)

注意:重定向操作的是字節流(InputStream、OutputStream),而不是字符流。

15、JDK1.4的java.nio.*包引入了新的JavaI/O類庫,其目的在於提高速度。速度的提高來自於所使用的結構更接近操作系統的I/O的方式:通道和緩衝器。

         我們可以把它想象成一個煤礦,通道是一個包含煤層(數據)的礦藏,而緩衝器則是派送到礦藏的卡車。卡車載滿煤炭而歸,我們再從卡車上獲得煤炭。也就是說,我們並沒有直接和通道交互;只是和緩衝器交互,並把緩衝器派送到通道。通道要麼從緩衝器獲得數據,要麼向緩衝器發送數據。

         唯一直接與通道交互的緩衝器是ByteBuffer——也就是說,可以存儲未加工字節的緩衝器。ByteBuffer是一個相當基礎的類:通過告知分配多少存儲空間來創建一個ByteBuffer對象,並且還有一個方法選擇集,用以原始的字節形式或基本數據類型輸出和讀取數據。

16、FileInputStream、FileOutputStreaam以及用於既讀又寫的RandomAccessFile被使用nio重新實現過,都有一個getChannel()方法返回一個FileChannel(通道)。通道是一種相當基礎的東西:可以向它傳送用於讀寫的ByteBuffer,並且可以鎖定文件的某些區域用於獨佔式訪問。Reader和Writer這種字符模式類不能產生通道;但是java.nio.channels.Channels類提供了實用方法,用以在通道中產生Reader和Wirter。

以下

17、轉換數據:緩衝器容納的是普通的字節,爲了把它們轉化成字符,我們要麼在輸入它們的時候對其進行編碼,要麼在將其從緩衝器輸出時對它們進行解碼。請參看下面例子:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
 
public class GetChannel{
   public static void main(String[] args) throws Exception{
      //寫入data.txt
      FileChannel fc = new FileOutputStream("data.txt").getChannel();
      fc.write(ByteBuffer.wrap("Some text".getBytes()));
      fc.close();
      //讀取data.txt
      fc = new FileInputStream("data.txt").getChannel();
      ByteBuffer buff = ByteBuffer.allocate(1024);
      fc.read(buff);
      buff.flip();
     
      System.out.println(buff.asCharBuffer());
      //使用系統默認的字符集進行解碼(輸出時對其進行解碼)
      buff.rewind();/返回數據開始的部分
      String encoding = System.getProperty("file.encoding");
      System.out.println("Decodedusing " + encoding + ":"
           + Charset.forName(encoding).decode(buff));
      //再次寫入(輸入時對其進行編碼)
      fc = new FileOutputStream("data.txt").getChannel();
      fc.write(ByteBuffer.wrap("Some text".getBytes("UTF-16BE")));
      fc.close();
      //再次讀取
      fc = new FileInputStream("data.txt").getChannel();
      buff = ByteBuffer.allocate(1024);
      fc.read(buff);
      buff.flip();
      System.out.println(buff.asCharBuffer());
      //再次寫入(輸入時對其進行編碼)
      fc = new FileOutputStream("data.txt").getChannel();
      buff = ByteBuffer.allocate(24);
      buff.asCharBuffer().put("Some text");
      fc.write(buff);
      fc.close();
      //再次讀取
      fc = new FileInputStream("data.txt").getChannel();
      buff.clear();
      fc.read(buff);
      buff.flip();
      System.out.println(buff.asCharBuffer());
   }
}

運行結果:


18、獲取基本類型:儘管ByteBuffer只能保存字節類型的數據,但是它具有從其所容納的字節中產生出各種不同的類型值的方法。

import java.nio.ByteBuffer;

public class GetData{
   private final static int BSIZE = 1024;
   public static void main(String[] args){
  
      ByteBuffer bb = ByteBuffer.allocate(BSIZE);
      int i = 0;
      while(i++ < bb.limit()){
        if(bb.get() != 0){
           System.out.println("nonzero");
        }
      }
      System.out.println("i = " + i);
      bb.rewind();
      //char
      bb.asCharBuffer().put("Howdy");
      char c;
      while((c = bb.getChar()) != 0){
        System.out.print(c + " ");
      }
      System.out.println();
      bb.rewind();
      //short。是一個特例,必須加上(short)進行類型轉換
      bb.asShortBuffer().put((short)471142);
      System.out.println(bb.getShort());
      bb.rewind();
      //int
      bb.asIntBuffer().put(99471142);
      System.out.println(bb.getInt());
      bb.rewind();
      //long
      bb.asLongBuffer().put(99471142);
      System.out.println(bb.getLong());
      bb.rewind();
      //float
      bb.asFloatBuffer().put(99471142);
      System.out.println(bb.getFloat());
      bb.rewind();
      //double
      bb.asDoubleBuffer().put(99471142);
      System.out.println(bb.getDouble());
      bb.rewind();
   }
}

運行結果:


19、視圖緩衝器:可以讓我們通過某個特定的基本數據類型的視窗查看其底層的ByteBuffer。

ByteBuffer bb = ByteBuffer.allocate(BSIZE);
//asIntBuffer() 創建此字節緩衝區的視圖,作爲 int 緩衝區。
IntBuffer ib = bb.asIntBuffer();

20、不同機器可能會有不同的字節排序方法來存儲數據。默認的ByteBuffer是以高位優先的順序存儲數據的。考慮下面兩個字節:


如果我們以short(ByteBuffer.asShortBuffer)高位優先讀出的是97(0000000001100001),若更改ByteBuffer更改爲地位優先,則讀出的是24832(0110000100000000)。再看下面例子:

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
 
public class Endians{
   public static void main(String[] args){
      ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
      bb.asCharBuffer().put("abcdef");
      System.out.println(Arrays.toString(bb.array()));
     
      bb.rewind();
      bb.order(ByteOrder.BIG_ENDIAN);//設置爲高位排序
      bb.asCharBuffer().put("abcdef");
      //array()只能對有數組支持的緩衝器調用
      System.out.println(Arrays.toString(bb.array()));
     
      bb.rewind();
      bb.order(ByteOrder.LITTLE_ENDIAN);//設置爲低位排序
      bb.asCharBuffer().put("abcdef");
      System.out.println(Arrays.toString(bb.array()));
   }
}

運行結果:


21、如果想把一個字節數組寫到文件中去,那麼應該使用ByteBuffer.wrap()方法把字節數組包裝起來,然後用getChannel()方法在FileOutputStream上打開一個通道,接着將來自於ByteBuffer的數據寫到FileChannel中去。

22、存儲器映射文件:允許我們創建和修改那些因爲太大而不能放入內存的文件。有了存儲器映射文件,我們就可以假定整個文件都在內存中,而且可以完全把它當作非常大的數組來訪問。

    儘管“舊”的I/O在用nio實現後性能有所提高,但是“映射文件訪問”往往可以更加顯著地加快速度

23、文件加鎖機制:允許我們同步訪問某個作爲共享資源的文件。文件鎖對其它操作系統進程是可見的,因爲Java的文件加鎖之間映射到本地操作系統的加鎖工具。另外,利用對映射文件的部分加鎖,可以對巨大的文件進行部分加鎖,以便其他進程可以對修改文件中未被加鎖的部分

24、壓縮:Java壓縮類庫是按字節方式的,它們繼承自InputStream和OutputStream。

1).GZIP:如果只想對單個數據流(而不是一系列互異數據)進行壓縮,那麼它是比較適合的選擇。下面是對單個文件進行壓縮的例子:

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
 
public class GZIPcompress{
   public static void main(String[] args) throws IOException{
      //InputStreamReader中設置字符編碼爲GBK
      BufferedReader in = new BufferedReader(new InputStreamReader(new
           FileInputStream("test.txt"), "GBK"));
      BufferedOutputStream out = new BufferedOutputStream((
           new GZIPOutputStream(new FileOutputStream("test.txt.gz"))));
      System.out.println("Writingfile");
      int c;
      while((c = in.read()) != -1){
        //使用GBK字符編碼將此 String編碼到 out中
        out.write(String.valueOf((char) c).getBytes("GBK"));
      }
      in.close();
      out.close();
      System.out.println("ReadingFile");
      BufferedReader in2 = new BufferedReader((new InputStreamReader
           (new GZIPInputStream(new FileInputStream("test.txt.gz")))));
      String s;
      while((s = in2.readLine()) != null){
        System.out.println(s);
      }
   }
}

2).用Zip進行多文件壓縮:對於每一個要加入壓縮檔案的文件,都必須調用putNextEntry(),並將其傳遞給一個ZipEntry對象。

25、對象的序列化:將那些實現Serializable接口的對象轉換成一個字節序列,並能夠在以後將這個字節序列完全恢復爲原來的對象。利用它可以實現“輕量持久性”。

1).持久化”:意味一個對象的生存週期並不取決於程序是否正在執行。通過將一個序列化對象寫入磁盤,然後再重新調用程序時恢復該對象,就能夠實現持久性的效果。

2).輕量級”:因爲它不能用某種“persistent”(持久)關鍵字來簡單定一個對象,並讓系統自動維護其他細節問題。相反,對象必須在程序中顯示地序列化和反序列化還原。

26、對象序列化的概念加入到語言中是爲了支持兩種主要特性:

1).Java的“遠程方法調用”(Remote Method Invocation,RMI)。

2).Java Beans。

27、

1).序列化:創建OutputStream對象,封裝到ObjectOutputStream,再調用writeObject()

2).反序列化:創建InputStream對象,封裝到ObjectInputStream,再調用readObject()

3).Serializable的對象在還原的過程中,沒有調用任何構造器

28、對象序列化是面向字節的,因此採用InputStream和OutputStream層次結構。

29、序列化的控制

1).實現Externalizable接口,接口繼承自Serializable,添加了writeExternal()和readExternal()兩個方法,它們會在序列化和反序列化還原的過程中被自動調用。

2).反序列過程中,對於Serializable對象,對象完全以它存儲的二進制位爲基礎來構造,而不是調用構造器。而Externalizeble對象,普通的缺省構造函數會被調用

3).transient(瞬時)關鍵字:只能和Serializable對象一起使用。

private transient String password;

password將不會被保存到磁盤中。

30、序列化/反序列化static成員:顯示調用serializeStaticState()和deserializeStaticState()

31、Preferences:用於存儲和讀取用戶的偏好(preferences)以及程序配置項的設置。只能用於小的、受限的數據集合——我們只能保存基本數據類型和字符串,並且每個字符串的存儲類型長度不能超過8K(不是很小,單我們也不想用它來創建任何重要的東西)。它是一個鍵-值集合(類似映射),存儲在一個節點層次結構中。

import java.util.Arrays;
import java.util.Iterator;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
 
public class PreferencesDemo{
   public static void main(String[] args) throws BackingStoreException{
      Preferences prefs = Preferences.userNodeForPackage(PreferencesDemo.class);
      prefs.put("日期", "2012年02月05日");
      prefs.put("天氣", "晴");
      prefs.putInt("放假多少天了?", 24);
      prefs.putBoolean("現在在家?", true);
     
      int usageCount = prefs.getInt("寒假還剩多少天?", 16);
      usageCount--;
      prefs.putInt("寒假還剩多少天?", usageCount);
     
      Iterator<String> it = Arrays.asList(prefs.keys()).iterator();
      while(it.hasNext()){
        String key = it.next().toString();
        //null作爲缺省
        System.out.println(key + ":" + prefs.get(key, null));
      }
      System.out.println("放假多少天了?" + prefs.getInt("放假多少天了?", 0));
   }
}

注意:每次運行,usageCount的值沒都減少1。即每次打印出“寒假還剩多少天?”後面的數字都減少1。然而,在程序第一次運行之後,並沒有任何本地文件出現。Preferences API利用合適的系統資源完成了這個任務,並且這些資源會隨操作系統的不同而不同。例如在windows裏,就使用註冊表。

32、正則表達式

1). 在Java ,“\\”意味“我正在插入一個正則表達式反斜槓”,那麼隨後的字符具有特殊意義。若想插入一個字面意義上的反斜槓,得這樣子表示“\\\\”。

2).量詞:

·貪婪的:竟可能的模式發現儘可能的匹配。

·勉強的:用問號來指定,匹配滿足模式所需的最少字符數。

·佔有的:只有在Java語言中才可用,並且它也更高效,常常用於防止正則表達式失控,因此可以是正則表達式執行起來更有效。

3).注意abc+與(abc)+的不同。

4).在java中,正則表達式是通過java.util.regex包裏面的Pattern和Matcher類來實現的。

·Pattern對象表示一個正則表達式的編譯版本。靜態的complie()方法將一個正則表達式字符串編譯成Pattern對象。

·Matcher對象有matcher()方法和輸入字符串編譯過的Pattern對象中產生Matcher對象。

5).split()分裂操作將輸入字符串斷開成字符串對象數組。

 

發佈了62 篇原創文章 · 獲贊 29 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章