1. 程式人生 > >雜談——探祕位元組流與字元流

雜談——探祕位元組流與字元流

正所謂,一切事情皆有緣由。而程式中的所有資料,自然也有它自己的流。(哈哈,強行押韻最為致命 ヽ(✿゚▽゚)ノ

今天我們要認識的就是位元組流字元流這兩個小兔崽子。

想要認識它們,我們就得先獲得“流”這本祕籍,去看看“流”到底是什麼。

1.流是什麼呢?

要知道,在程式中所有的資料都是以流的方式進行傳輸或儲存的,程式需要資料的時候要使用輸入流讀取資料,而當程式需要將一些資料儲存起來的時候,就要使用輸出流完成。

程式中的輸入輸出都是以流的形式儲存的,流中儲存的實際上全都是位元組檔案

好的,看完上面兩段的介紹,大家想必就知道流的重要性了。它作為資料輸入輸出的基礎,少了它可萬萬不行。

接下來我們就進入正題,探究一下位元組流和字元流的真面目。

2.位元組流與字元流

在java.io包中操作檔案內容的按資料型別劃分的話主要有兩大類:位元組流、字元流。兩類都分為輸入和輸出操作。

如下圖:

io

java中提供了專用於輸入輸出功能的包Java.io,其中包括:InputStream,OutputStream,Reader,Writer。(這四個都是抽象類)。

  •      InputStream 和OutputStream,兩個是為位元組流設計的,主要用來處理位元組或二進位制物件,
  •      Reader和 Writer.兩個是為字元流(一個字元佔兩個位元組)設計的,主要用來處理字元或字串.

即在位元組流中輸出資料主要是使用OutputStream完成,輸入使的是InputStream;在字元流中輸出主要是使用Writer類完成,輸入流主要使用Reader類完成。

位元組流處理單元為1個位元組,操作位元組和位元組陣列;而字元流是由Java虛擬機器將位元組轉化為2個位元組的Unicode字元為單位的字元而成的,因此字元流處理的單元為2個位元組的Unicode字元,分別操作字元、字元陣列或字串。

簡單點說呢:位元組流就是普通的二進位制流,讀出來的是bit;字元流就是在位元組流的基礎按照字元編碼處理,處理的是char。

由此我們可以粗略地知道,如果是處理音訊檔案、圖片、歌曲,就用位元組流好點。而字元流對多國語言支援性比較好,比如如果是關係到中文(文字)的,則用字元流好點。

所有檔案的儲存是都是位元組(byte)的儲存,在磁碟上保留的並不是檔案的字元而是先把字元編碼成位元組,再儲存這些位元組到磁碟。在讀取檔案(特別是文字檔案)時,也是一個位元組一個位元組地讀取以形成位元組序列

因此可以總結二者區別如下:

  • 位元組流處理單元為1個位元組,操作位元組和位元組陣列;字元流處理的單元為2個位元組的Unicode字元,分別操作字元、字元陣列或字串。
  • 位元組流可用於任何型別的物件,包括二進位制物件,而字元流只能處理字元或者字串。
  • 位元組流提供了處理任何型別的IO操作的功能,但它不能直接處理Unicode字元,而字元流就可以。

位元組流是最基本的,所有的InputStrem和OutputStream的子類都是,主要用在處理二進位制資料,它是按位元組來處理的。這個我們都知道,那既然位元組流可以直接處理二進位制資料,那為什麼不直接用位元組流,反而還多出一個字元流呢,這豈不是多此一舉?

但是,我們知道,一個位元組是8位,只能有256個值,如果用來表示文字,可以表示ASCII碼,包括控制字元,數字,符號,英文字母,西歐字母,製表符。那中文呢?中文少說有幾千漢字,所以一個位元組表示不了,得用兩個位元組,編碼方案有GB2312,GBK,Big5等。後來又出現統一字符集,把各個常用語言都容納進來,肯定1個位元組也放不下。Java使用Unicode,用char這個資料型別表示一個多位元組的字元。

因此,由於實際中很多的資料是文字,所以才提出了字元流的概念。它是按虛擬機器的encode來處理,也就是要進行字符集的轉化 。位元組流與字元流之間通過 InputStream | Reader,OutputStream | Writer來關聯,即通過byte[]和String來關聯。因此,我們可以知曉,平常在實際開發中出現的漢字問題實際上都是在字元流和位元組流之間轉化不統一而造成的 。

注:大部分人認為一切都是位元組流,其實沒有字元流這個東西。字元只是根據編碼集對位元組流翻譯之後的產物。

3.位元組流

位元組流主要是操作byte型別資料,以byte陣列為準,主要操作類就是OutputStream、InputStream。

3.1位元組輸出流:OutputStream

OutputStream是整個IO包中位元組輸出流的最大父類,此類的定義如下:

public abstract class OutputStream extends Object implements Closeable,Flushable

從以上的定義可以發現,該類是一個抽象類,如果想要使用此類的話,就首先必須通過子類例項化物件。那麼如果現在要操作的是一個檔案,則可以使用:FileOutputStream類。通過向上轉型之後,可以將OutputStream例項化。

Closeable表示可以關閉的操作,因為程式執行到最後肯定要關閉

Flushable:表示重新整理,清空記憶體中的資料

FileOutputStream類的構造方法如下:

public FileOutputStream(File file)throws FileNotFoundException

舉個例子:

對Test11.txt進行寫資料操作,程式碼如下:

package javatest;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;

public class Test11 {
	public static void main(String[] args) throws IOException {       
		File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
        OutputStream out=new FileOutputStream(f);//如果檔案不存在會自動建立
        String str="Searchin";
        byte[] b=str.getBytes();
        out.write(b);//因為是位元組流,所以要轉化成位元組陣列進行輸出
     /* 也可以一個位元組一個位元組寫入
        for(int i=0;i<b.length;i++){
        	            out.write(b[i]);
        }
    */
        out.close();
     }
 }

上文程式將Searchin這個字串寫入檔案中,這種方式將覆蓋原有的內容。我們開啟檔案看一下是否寫入了字串:

現在就寫入成功了。 

當然了,因為上面這種方法會覆蓋檔案中的內容,那麼我要是不想覆蓋內容,而是在內容後面追加的話,這就要用到FileOutputStream類的另一個構造方法了。

public FileOutputStream(File file,boolean append)throws FileNotFoundException

在構造方法中,如果將append的值設定為true,則表示在檔案的末尾追加內容。

我們來玩一下這個方法。

程式碼如下:

package javatest;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;

public class Test11 {
	public static void main(String[] args) throws IOException {       
		File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
        String str="Searchin";
        OutputStream out=new FileOutputStream(f,true);//追加內容
 
       byte[] b=str.getBytes();
        for(int i=0;i<b.length;i++){
        	out.write(b[i]);
        }
        out.close();
     }
 }

程式執行之後就出現在Searchin後面又追加了一個Searchin了。

當然,你要是覺得這樣不好看,你也可以換個行,檔案中換行為:\r\n。我們來試試看: 

 String str="\r\nSearchin";

執行結果如下: 

好了,現在就換了個行啦。 

 

3.2位元組輸入流:InputStream

既然程式可以向檔案中寫入內容,那當然可以通過InputStream從檔案中把內容讀取進來啦,首先來看InputStream類的定義:

public abstract class InputStream extends Object implements Closeable

與OutputStream類一樣,InputStream本身也是一個抽象類,必須依靠其子類,如果現在是從檔案中讀取,就用FileInputStream來實現。

觀察FileInputStream類的構造方法:

public FileInputStream(File file)throws FileNotFoundException

還是用剛才的檔案,我們看看讀檔案是怎麼玩的:

package javatest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Test22 {
   public static void main(String[] args) throws IOException {
		File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
      InputStream in=new FileInputStream(f);
      byte[] b=new byte[4096];
//(注意:這裡以前寫作1024,現在不能再這樣寫,必須是4096的倍數) 
      int len=in.read(b);
      in.close();
      System.out.println(new String(b,0,len));
     }
 }

這個程式將讀取檔案中的內容,即,然後再輸出。

我們來看看輸出結果:

好的,讀取成功。

不過,這個方法是有問題的呀。上文中的程式碼自定義了一個固定的位元組陣列,用來儲存從檔案中讀出來的內容。但是顯然我讀出來的字元用不了這麼大的陣列,剩下的那部分空間就完全給浪費了。

那麼我們可不可以根據檔案的大小來定義位元組陣列的大小,從而減少浪費呢?

當然可以,File類中的方法:public long length()就是做這個的。

我們只要上邊程式中的

byte[] b=new byte[4096];

 

改為:

 byte[] b=new byte[(int) f.length()];

這樣就可以根據檔案大小來定義位元組陣列的大小了。效果還是一樣完美~

 到這裡可能有人會問,我就是想要一個位元組一個位元組地讀出來,可以嗎?可以啊!

當你知道輸入檔案的大小的時候,就可以將讀操作的語句改為如下語句:

for(int i=0;i<b.length;i++){
           b[i]=(byte) in.read();
 }

即(這裡為了看得方便,就直接給main函式啦)

 public static void main(String[] args) throws IOException {
	 File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
     InputStream in=new FileInputStream(f);
     byte[] b=new byte[(int) f.length()];
     for(int i=0;i<b.length;i++){
         b[i]=(byte) in.read();
     }
     in.close();
     System.out.println(new String(b));
}

 

當然,要是你不知道輸入檔案是多大的話,那也沒有問題,只不過需要藉助標誌來完成咯,用如下語句就可以了:

public static void main(String[] args) throws IOException {
	  File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
      InputStream in=new FileInputStream(f);
      byte[] b=new byte[4096];
      int temp=0;
      int len=0;
      while((temp=in.read())!=-1){//-1為檔案讀完的標誌
          b[len]=(byte) temp;
          len++;
      }
      in.close();
      System.out.println(new String(b,0,len));
}

 

4.字元流

在程式中一個字元等於兩個位元組,那麼java提供了Reader、Writer兩個專門操作字元流的類。

4.1字元輸出流:Writer

Writer本身是一個字元流的輸出類,此類的定義如下:

public abstract class Writer extends Object implements Appendable,Closeable,Flushable

此類本身也是一個抽象類,如果要使用此類,則肯定要使用其子類,此時如果是向檔案中寫入內容,所以應該使用FileWriter的子類。

FileWriter類的構造方法定義如下:

public FileWriter(File file)throws IOException

字元流的操作比位元組流操作好在一點,就是可以直接輸出字串了,不用再像之前那樣進行轉換操作了。

那麼我們接下來就看看它是怎麼進行讀寫操作的。

寫檔案:

這個程式預設會將原內容覆蓋,我們來執行看看

如果想要在檔案後邊追加,也是依靠在建構函式上加上追加標記。即 

Writer out=new FileWriter(f,true);

現在就變成在後面追加啦~ 

 

4.2字元輸入流:Reader

Reader是使用字元的方式從檔案中取出資料,Reader類的定義如下:

public abstract class Reader extends Objects implements Readable,Closeable

Reader本身也是抽象類,如果現在要從檔案中讀取內容,則可以直接使用FileReader子類。

FileReader的構造方法定義如下:

public FileReader(File file)throws FileNotFoundException

以字元陣列的形式讀取出資料:

package javatest;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Test22 {
   public static void main(String[] args) throws IOException {
		File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
		Reader input=new FileReader(f);
		char[] c=new char[4096];
		int len=input.read(c);
		input.close();
		System.out.println(new String(c,0,len));
     }
 }

現在就讀出檔案裡的內容啦~~

 當然啦,也可以想位元組流的讀取一樣,用迴圈方式,判斷是否讀到底:

package javatest;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Test22 {
   public static void main(String[] args) throws IOException {
		File f = new File("C:\\Users\\lenovo\\Desktop" + File.separator+"Test11.txt");
		Reader input=new FileReader(f);
		 char[] c=new char[4096];
		int temp=0;
		int len=0;
		while((temp=input.read())!=-1){
		c[len]=(char) temp;
		len++;
		}
		input.close();
		System.out.println(new String(c,0,len));
     }
 }

 5.總結

位元組流和字元流使用是非常相似的,那麼除了操作程式碼的不同之外,還有哪些不同呢?

  • 位元組流在操作的時候本身是不會用到緩衝區(記憶體)的,是與檔案本身直接操作的;而字元流在操作的時候會使用到緩衝區的。
  • 位元組流在操作檔案時,即使不關閉資源(close方法),檔案也能輸出,但是如果字元流不使用close方法的話,則不會輸出任何內容,說明字元流用的是緩衝區,並且可以使用flush方法強制進行重新整理緩衝區,這時才能在不close的情況下輸出內容(這一點需要注意

那開發中究竟用位元組流好還是用字元流好呢?

在所有的硬碟上儲存檔案或進行傳輸的時候都是以位元組的方法進行的,包括圖片也是按位元組完成,而字元是隻有在記憶體中才會形成的,所以使用位元組的操作是最多的。

如果要java程式實現一個拷貝功能,應該選用位元組流進行操作(可能拷貝的是圖片),並且採用邊讀邊寫的方式(節省記憶體)。

 

 

好啦,以上就是位元組流和字元流的相關總結,如果大家有什麼更具體的發現或者發現文中有描述錯誤的地方,歡迎留言評論,我們一起學習呀~~

 

Biu~~~~~~~~~~~~~~~~~~~~宫å´éªé¾ç«è¡¨æå|é¾ç«gifå¾è¡¨æåä¸è½½å¾ç~~~~~~~~~~~~~~~~~~~~~~pia!