java複習筆記7--java基礎之I/O流2
字元流
前面針對位元組流和位元組快取流做了一個比較全面的探索。位元組流以位元組(8bit)為單位讀取資料,且可以處理所有的資料,包括文字,音訊等,這裡就要丟擲一個問題了,既然位元組流這麼方便,只是讀取資料比較麻煩,那我們完全可以包裝位元組流進行快速的一些處理,為什麼還要衍生出字元流?
字元流的優勢
- 字元流提供了針對字元型別的資料的很好的API操作
- 位元組流讀取中文的時候,存在亂碼的情況
- 字元流會使用緩衝區
字元流的API之後詳細去看,先看一下中文亂碼和緩衝區這兩個。首先,位元組流讀取中文的時候為什麼可能出現中文亂碼的情況。有以下兩種原因:
- 我們陣列轉String是有預設的charset(“UTF-8”),由於中文在不同的編碼中對應的位元組以及長度都是不一樣的,如若檔案格式和轉換所使用的編碼不一致,就會出現亂碼的情況,比如
@Test public void ioDemo1() { File file = new File("D:" + File.separator + "a.txt"); if (file.exists()) { log.info(file.getAbsolutePath()); InputStream inputStream = null; try { inputStream = new FileInputStream(file); inputStream = new FileInputStream("D:\\a.txt"); new BufferedInputStream(inputStream); int n = 0; byte[] bytes =new byte[1024]; StringBuilder sb = new StringBuilder(); while ((n = inputStream.read(bytes)) != -1) { sb.append(new String(bytes, 0, n)); } System.out.print(sb.toString()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
我們a.txt裡面內容為 ‘’aaa中‘’,陣列大小為1024,一次可以全部讀取完,但是,由於我們檔案使用的是GBK的編碼,但是轉換String使用的預設的utf-8的編碼,導致輸出是亂碼的aaa�й�
。
- 還有一種可能,我們這裡將檔案改為UTF-8格式的,這樣格式一致了,但是我們將陣列大小改為4:
:byte[] bytes =new byte[4];
我們執行發現還是亂碼:aaa�����
。這裡是什麼原因呢?正如上面說的,位元組流讀取的時候,是以位元組(8bit)為單位讀取資料,英文字母和數字都是一個位元組,而中文在UTF-8中是佔三個位元組的,而位元組流讀取資料的時候,,如果剛好讀取到某個中文的第一個位元組或者第二個位元組,然後陣列滿了,返回了,這個時候,就會出現亂碼
既然我們知道位元組流讀取會出現亂碼的情況,並且知道原因,那怎麼解決呢?其實很簡單,讀取的io流和轉換為String的Charset保持一致,然後,根據各個charset針對中文的定義,判斷是否讀取到中文,如果是,根據規則將後面的剩餘部分找出來一起拼湊:
{
inputStream = new FileInputStream(file);
inputStream = new FileInputStream("D:\\a.txt");
new BufferedInputStream(inputStream);
int n = 0;
byte[] bytes =new byte[4];
StringBuilder sb = new StringBuilder();
while ((n = inputStream.read(bytes)) != -1) {
//判斷最後一位是否是中文一部分
int a = 0;
if(n == bytes.length && bytes[bytes.length - 1] < 0){
//判斷缺幾個位元組
for(int i = 0; i< bytes.length; i++){
if(bytes[i] < 0){
a++;
}
}
}
a = a% 3;
if(a > 0){
byte[] bytes1 =new byte[3 - a];
for(int i = 0; i< (3 - a); i++){
bytes1[i] = (byte)inputStream.read();
}
byte[] bytes2 = Arrays.copyOfRange(bytes, n -a, n);
byte[] bytes3 = ArrayUtils.addAll(bytes2, bytes1);
//無需拼接的部分
sb.append(new String(bytes, 0, n -a));
//拼接的中文
sb.append(new String(bytes3));
}else{
sb.append(new String(bytes, 0, n));
}
}
System.out.print(sb.toString());
}
整個實現程式碼還是比較冗餘的,所以字元流應運而生,對於文字和字元資料的處理,建議使用字元流,一來不存在亂碼的問題,二來使用緩衝區,效率高,三來也有比較多簡潔便捷的API。緩衝區也是我們一直提到的一個知識點,其實緩衝區是固定長度的資料容器,也是記憶體中的一部分。有一個單獨的緩衝區型別來儲存每種型別的基本值的資料,除了布林型別值。比如建立一個8個位元組的緩衝區:ByteBuffer bb = ByteBuffer.allocate(8);
,緩衝區和陣列很類似,有容量,有position(指標),只是它內部有一些自己針對讀和寫的處理方法。
基礎的字元流
和位元組流一樣,Reader 是所有的輸入字元流的父類,它是一個抽象類,是將磁碟(檔案)中的資料讀入記憶體中。Writer:是所有的輸出字元流的父類,它是一個抽象類,是將記憶體中的資料按照字元形式寫入磁碟(檔案)中。
字元流的構造方法以及基類中的對於資料的讀取基本和位元組流一致,具體的建構函式和API可以看一篇文章的介紹,或者自行閱讀,比較簡單,程式碼的寫法也和位元組流相似:
{
File file = new File("D:" + File.separator + "a.txt");
if (file.exists()) {
FileReader fileReader = null;
try {
fileReader = new FileReader(file);
char [] ch=new char[1024];
int len=0;
while((len=fileReader.read(ch))!=-1){
System.out.print(new String(ch,0,len));
}
fileReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
字元緩衝流
-
BufferedReader:字元輸入緩衝流,從字元輸入流中讀取文字,緩衝各個字元,從而實現字元、陣列和行的高效讀取。BufferedReader 由Reader類擴充套件而來,也是裝飾者模式的典型應用,BufferedReader 的建構函式需要傳一個reader進來:
public BufferedReader(Reader in) { this(in, defaultCharBufferSize); }
,後面那個表示的是預設的緩衝區的大小(8192)。他提供了一個readLine的逐行讀取文字的方法:String readLine(boolean ignoreLF) throws IOException
,對於文字資料和字元資料的處理很方便。 -
BufferedWriter:字元輸出緩衝流,原理和BufferedReader類似。 該類提供了 newLine() 方法,與BufferedReader的readLine呼應,它使用平臺自己的行分隔符概念(由系統屬性 line.separator 定義)。其實也是對於write方法的一次封裝。
字元緩衝流對於我們工作中與文字或者字元資料打交道,對於他們的處理有一個很好的幫助,能夠大大簡化我們的程式碼量,例如,這裡用readLine和newLine方法去實現上一次的檔案的copy,程式碼會很簡潔:
@Test
public void ioDemo3() {
File file = new File("D:" + File.separator + "a.txt");
if (file.exists()) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new FileReader("D:\\a.txt"));
bufferedWriter = new BufferedWriter(new FileWriter("E:\\a.txt"));
String str=null;
while((str=bufferedReader.readLine())!=null){
bufferedWriter.write(str);
bufferedWriter.newLine();//寫入一個行分隔符。行分隔符字串由系統屬性 line.separator 定義,並且不一定是單個新行 ('\n') 符。
bufferedWriter.flush();//緩衝輸出流一定要flush
}
bufferedWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}finally {
try {
bufferedWriter.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
至此,字元流我們基本有了一個瞭解,對於其用法和常用的API也有了運用,對於長期與磁碟,檔案打交道的程式設計師來說,這些知識雖然很基礎,但是也是必不可少的。下一次溫習,我們將針對遺留的讀取資料的pos,也就是指標和reset,mark這些方法做一些學習和分享。