IO流分類講解


1 IO流基礎

javaIO就是輸入輸出體系,簡單的理解就是java對於文件內存網絡資源的操作,在java傳統的IO體系中,雖然可以完成基本所有需求的操作,但是為了實現java一次編寫到處運行的目標,java虛擬機在各個操作系統上做了很多讓步,放棄了很多操作系統的特性,這樣的結果只能使javaIO效率不高。為了提高javaIO的效率,在java4版本中增加了java new IO簡稱為NIO非阻塞的IO

1.1 IO基礎

在介紹IO之前我們需要先掌握一些基本知識。就是File類。File類IO最基礎的類,File是我們磁盤上文件目錄的抽象,我們可以通過一個路徑來創建這個類,File類中有許多方法可以讓我得到這個文件或目錄的屬性,如果是一個不存在的目錄或文件可以用這個類創建。具體代碼大家可以自己試一下,在這貼出一些簡單的例子。
例子:

import java.io.File;  
import java.io.IOException;  
public classTest1 {  
    public static void main(String[] args) {  
        File file=new File("e:\\out.txt");  
        if(!file.exists()){  
            try {  
                file.createNewFile();  
            }catch(IOException e) {  
                e.printStackTrace();  
            }  
        }  
//      file.mkdir();  
//      file.mkdirs();  
//      File.listRoots();列出文件系統的根  
        System.out.println(file.canExecute());  
        System.out.println(file.canRead());  
        System.out.println(file.canWrite());  
        System.out.println(file.isAbsolute());  
	
	System.out.println("------默認相對路徑,取得路徑不同-----");
     File f = new File("..\\src\\file");
     System.out.println(f.getPath());
     System.out.println(f.getAbsolutePath());
     System.out.println(f.getCanonicalPath());
     System.out.println("------默認相對路徑,取得路徑不同-----");
     File f2 = new File(".\\src\\file");
     System.out.println(f2.getPath());
     System.out.println(f2.getAbsolutePath());
     System.out.println(f2.getCanonicalPath());
     System.out.println("------默認絕對路徑,取得路徑相同-----");
     File f3 = new File("C:\\src\\file");
     System.out.println(f3.getPath());
     System.out.println(f3.getAbsolutePath());
     System.out.println(f3.getCanonicalPath());

	 執行結果為:
   ------默認相對路徑,取得路徑不同-----
      ..\src\file
   C:\workspace\Tip\..\src\file
   C:\workspace\src\file
   ------默認相對路徑,取得路徑不同-----
   .\src\file
   C:\workspace\Tip\.\src\file
   C:\workspace\Tip\src\file
   ------默認絕對路徑,取得路徑相同-----
   C:\src\file
   C:\src\file
   C:\src\file
  
  比較可以得到
   getPath()返回的是構造方法里的路徑,不做任何處理
   getAbsolutePath()返回的是 user.dir+getPath(),也就是執行路徑加上構造方法中的路徑
   getCanonicalPath()返回的是將符號完全解析的路徑,也就是全路徑
    }  
}  

1.2 流分類

按流向分類:

  • 輸入流: 程序可以從中讀取數據的流。
  • 輸出流: 程序能向其中寫入數據的流。

按數據傳輸單位分類:

  • 字節流:以字節(8位二進制)為單位進行處理。主要用於讀寫諸如圖像或聲音的二進制數據。
  • 字符流:以字符(16位二進制)為單位進行處理。
    都是通過字節流的方式實現的。字符流是對字節流進行了封裝,方便操作。在最底層,所有的輸入輸出都是字節形式的。

按功能分類:

  • 節點流:從特定的地方讀寫的流類,如磁盤或者一塊內存區域。
  • 過濾流:使用節點流作為輸入或輸出。過濾流是使用一個已經存在的輸入流或者輸出流連接創建的。

剛開始學習的javaio基本都是BIO也就是阻塞的IO。阻塞就是我們才操作該方法時需要一定時間的等待該方法完成后才能繼續進行。阻塞的IO在我們讀或者寫的時候如果設備或讀寫資源比較繁忙時我們的程序會被堵在一個地方。

流相當是一種管道,我們的資源數據會在流中傳輸。javaIO主要分為以下幾種流。

1.2.1 Java字節流Stream

InputStreamOutputStream是所有字節流的父類他是一個抽象類
所有的讀操作都繼承自一個公共超類java.io.InputStream類。
所有的寫操作都繼承自一個公共超類java.io.OutputStream
其主要實現子類有:ByteArrayInputStream字節數組輸入流, FileInputStream文件流,ObjectInputStream對象流, ZipFileInputStream壓縮流,PipedInputStream從線程管道中讀取數據字節,StringBufferInputStream從字符串中讀取數據字節,SequenceInputStream從兩個或多個低級流中讀取數據字節,當到達流的末尾時從一個流轉到另一個流,System.in從用戶控制台讀取數據字節
FilterInputStream過濾器流,過濾器流即能把基本流包裹起來,提供更多方便的用法。FilterInputStream類的構造方法為FilterInputStream(InputStream),在指定的輸入流之上,創建一個輸入流過濾器

java流體系:緩存流、轉換流、內存流、退出輸入流、管道流、對象流。
字節流的read方法讀取的是一個字節也就是8位2進制,而字符流讀取的是一個字符是16位2進制
其實在我們讀取操作的時候,最終是利用的操作系統的讀取,而默認操作系統是通過塊來返回我們的IO讀取的數據,但是javaIO卻是以1個字節字節的傳輸,勢必會使性能降低,但我們可以通過緩沖區來改善這種情況。
但是緩沖區的大小第一成多大才能讓速度更快呢?在這里並沒有確定的數值,只能是根據操作系統來定,但是這個值必須是8的倍數,這跟內存頁有關 系,內存的翻頁其實消耗的性能是比較多的
其實內存頁的概念就跟磁盤的扇區是差不多的,磁盤是分扇區的,而內存也是分頁的,都是為了方便數據的管理,也是數據存儲的小單元。

抽象類InputStream的類層次
在這里插入圖片描述
抽象類OutputStream的類層次結構:
在這里插入圖片描述

1.2.1.1 使用緩沖區讀入

例子:普通的方式沒有使用緩沖區

public static void f1() throwsException{         
 FileInputStream fis=new FileInputStream(new File("e:\\out.txt"));  
        int length=0;  
        while((length=fis.read())!=-1){  
            System.out.print((char)length);  
        }  
        fis.close();  
    }  

例子:我們使用緩沖區來讀寫,雖然速度快了但是出現第二個問題,就是讀出的數據會有很多空格,這是因為最后一次緩沖區並沒有填滿,后面的空間被自動補充,導致出現很多空格。

/** 
     * 會多讀很多 
     * @throws Exception 
     */  
    public static void f2() throws Exception{  
            FileInputStream fis=new FileInputStream(new File("e:\\out.txt"));  
        byte[] buffer=new byte[1024];  
        int length=0;  
        while((length=fis.read(buffer))!=-1){  
            System.out.print(new String(buffer));  
        }  
        fis.close();  
    }  

例子:我們接來改進一下這個例子。我們在構建數據的時候通過一個參數截取緩沖區中讀到的數據長度,這樣就保證了速度又不會出現過多的空格。

public static void f3() throws Exception{  
        FileInputStream fis=new FileInputStream(new File("e:\\out.txt"));  
        byte[] buffer=new byte[1024];  
        int length=0;  
        while((length=fis.read(buffer))!=-1){  
            System.out.print(new String(buffer,0,length));  
        }  
        fis.close();  
    }  

1.2.1.2 使用緩沖區寫出

寫入的例子:

public staticvoidf4()throws Exception{  
        FileOutputStream fos=new FileOutputStream("e:\\in.txt");  
        String s="helloworld";  
        fos.write(s.getBytes());  
        fos.flush();  
        fos.close();  
    }  

大家可能比較奇怪flush是做什么用的:其實flush刷新就是刷新此輸出流並強制緩存輸出字節。在這里我們再擴充一下知識:什么是讀緩存跟寫緩存

  • 讀緩存:讀緩存也是經常見到的緩存,例如我們去數據庫查詢數據,當我們第一次查找ID1的數據時需要去數據庫中查找,但是當我們第二次再去查 找ID1的數據時還需要去數據庫查找嗎?答案是否定的,數據庫查找數據是相對比較慢的一個操作,而我們在第一次去讀取數據時已經將他放到一個緩存中,這 里大家可以把它想象成一個hashmap,第二次查找時我們先去hashmap中查找,如果沒有再去數據庫查找。
  • 寫緩存:學過數據庫的讀者應該都知道,數據庫的插入操作其實耗費的時間是比較可觀的,尤其是讀寫比較頻繁的數據庫,無疑壓力是非常的大的,如何才能緩解這種情況,這里就需要用到寫緩存,我們在寫入文件時,其實並不是正在的寫入數據庫,我們可以先將他寫入內存,如果有人讀取,我們直接從內存中給他,而當壓力小的時候我們可以再寫入數據庫,但是我們不得不考慮一個情形就是萬一機器斷電,那么我們未寫入的豈不是丟失了,數據丟失對於企業來說無疑是災難,我們可以寫入日志,直接把元數據寫入文件,就跟我們計算機休眠一樣,將內存的數據寫入內存,當恢復休眠時再從硬盤恢復到內存。
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.FileOutputStream;  
public classTest3 {  
    public static void copy(String src,String des)throws Exception{  
        FileInputStream fis=new FileInputStream(src);  
        FileOutputStream fos=new FileOutputStream(des);  
        byte[] buffer=new byte[1024];  
        int hasRead=0;  
        while((hasRead=fis.read(buffer))!=-1){  
            fos.write(buffer,0, hasRead);  
            fos.flush();  
        }  
        fis.close();  
        fos.close();  
    }  
}  

1.2.2 java字符流Reader Writer

ReaderWriter是所有字節流的父類,都繼承了readerwriter
所有的讀操作都繼承自一個公共超類java.io.Reader類。
所有的寫操作都繼承自一個公共超類java.io.Writer類。

字符流是一個字符一個字符的讀取寫入。對於文字操作非常的方便。
CharArrayReader從字符數組中讀取數據,
FileReaderInputStreamReader的子類)從本地文件系統中讀取字符序列
StringReader從字符串中讀取字符序列
PipedReader從線程管道中讀取字符序列

public classTest4 {  
    public void testRead() throws Exception{  
        FileReader read=new FileReader("");  
        char[] buffer=new char[1024];  
        int hasRead=0;  
        while((hasRead=read.read(buffer))!=-1){  
            String s=new String(buffer,0,hasRead);  
            System.out.println(s);  
        }  
        read.close();  
    }  
public void testWriter() throws Exception{  
        FileWriter writer=new FileWriter("");  
        String s="aaaaaaaa";  
        writer.write(s);  
        writer.flush();  
        writer.close();  
    }  
}  

1.2.3 Java緩沖流

BufferedReader緩沖流是將字符流轉換成緩沖流,緩沖流主要實現了readline方法,可以一次讀取一行數據。BufferWriter也是緩沖流,但是我們經常使用的是PrintWriter可以實現我們向控制台打印一樣方便。

  1. java.io.BufferedReaderjava.io.BufferedWriter類各擁有8192字符的緩沖區。當BufferedReader在讀取文本文件時,會先盡量從文件中讀入字符數據並置入緩沖區,而后若使用read()方法,會先從緩沖區中進行讀取。如果緩沖區數據不足,才會再從文件中讀取,使用BufferedWriter時,寫入的數據並不會先輸出到目的地,而是先存儲至緩沖區中。如果緩沖區中的數據滿了,才會一次對目的地進行寫出。
  2. 從標准輸入流System.in中直接讀取使用者輸入時,使用者每輸入一個字符,System.in就讀取一個字符。為了能一次讀取一行使用者的輸入,使用了BufferedReader來對使用者輸入的字符進行緩沖。readLine()方法會在讀取到使用者的換行字符時,再一次將整行字符串傳入。

例子:

public class Test5 {  
    public void test() throws Exception{  
        File file=new File("");  
        FileReader reader=new FileReader(file);  
        BufferedReader br=new BufferedReader(reader);  
        Strings="";  
        while((s=br.readLine())!=null){  
            System.out.println(s);  
        }  
        reader.close();  
        br.close();  
    }  
}

1.2.4 Java轉換流

轉換流:將inputstream字節流轉換為readeroutputstream字節流轉換成writer可以使用inputstreamreaderoutputstreamwriter

public voidtest2() throwsException{  
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));  
        while(br.read()!=-1){  
             
        }  
    }  

System.in是一個位流,為了轉換為字符流,可使用InputStreamReader為其進行字符轉換,然后再使用BufferedReader為其增加緩沖功能。

public class BufferedReaderWriterDemo   
{  
    public static void main(String[] args)   
    {  
        try  
        {  
            //緩沖System.in輸入流  
            //System.in是位流,可以通過InputStreamReader將其轉換為字符流  
            BufferedReader bufReader = new BufferedReader(new InputStreamReader(System.in));  
            //緩沖FileWriter  
            BufferedWriter bufWriter = new BufferedWriter(new FileWriter(args[0]));  
  
            String input = null;  
  
            //每讀一行進行一次寫入動作  
            while(!(input = bufReader.readLine()).equals("quit"))  
            {  
                bufWriter.write(input);  
                //newLine()方法寫入與操作系統相依的換行字符,依執行環境當時的OS來決定該輸出那種換行字符  
                bufWriter.newLine();  
            }  
            bufReader.close();  
            bufWriter.close();  
        }  
        catch(ArrayIndexOutOfBoundsException e)  
        {  
            System.out.println("沒有指定文件");  
        }  
        catch(IOException e)  
        {  
            e.printStackTrace();  
        }  
    }  
}  

運行時 java ysu.hxy.BufferedReaderWriterDemo test2.txt

1.2.5 數據流

dataoutputstreamdatainputstream

public void test1() throws Exception{  
        DataOutputStream dos=new DataOutputStream(new FileOutputStream(""));  
//      dos.writeInt();寫入一個四位的  
//      dos.writeByte()  
        DataInputStream dis=new DataInputStream(new FileInputStream(""));  
        dis.readBoolean();  
        dis.readInt();  
        dis.readChar();  
    } 

1.2.6 隨機訪問流RandomAccessFile

RandomAccessFile流有兩個重要的方法,longgetFilePointer()放回文件指針當前位置,void seek(long pos)將文件的指針定位到pos位置
read方法默認是從指針位置開始操作。文件的訪問模式有四種:r只讀方式,如果寫入操作則拋出異常,rw讀寫方式如果沒有文件則創建文件。rws除了rw還對文件內容或元數據進行更新同步寫入,而rwd對文件內容同步更新。

1.2.7 對象流

序列化:將對象寫入到文件中,ObjectOutputStream 接受一個字節流,必須實現序列化接口
Java序列化深入講解

1.3 IO底層工作原理

1.3.1 緩存處理和內核vs用戶空間

緩沖與緩沖的處理方式,是所有I/O操作的基礎。術語輸入、輸出只對數據移入和移出緩存有意義。任何時候都要把它記在心中。通常,進程執行操作系統的I/O請求包括數據從緩沖區排出(寫操作)和數據填充緩沖區(讀操作)。這就是I/O的整體概念。在操作系統內部執行這些傳輸操作的機制可以非常復雜,但從概念上講非常簡單。
在這里插入圖片描述
上圖顯示了一個簡化的邏輯圖,它表示塊數據如何從外部源,例如一個磁盤,移動到進程的存儲區域(例如RAM)中。首先,進程要求其緩沖通過read()系統調用填滿。這個系統調用導致內核向磁盤控制硬件發出一條命令要從磁盤獲取數據。磁盤控制器通過DMA直接將數據寫入內核的內存緩沖區,不需要主CPU進一步幫助。當請求read()操作時,一旦磁盤控制器完成了緩存的填寫,內核從內核空間的臨時緩存拷貝數據到進程指定的緩存中。
有一點需要注意,在內核試圖緩存及預取數據時,內核空間中進程請求的數據可能已經就緒了。如果這樣,進程請求的數據會被拷貝出來。如果數據不可用,則進程被掛起。內核將把數據讀入內存。

1.3.2 虛擬內存

所有現代操作系統都使用虛擬內存。虛擬內存意味着人工或者虛擬地址代替物理(硬件RAM)內存地址。虛擬地址有兩個重要優勢:

  • 多個虛擬地址可以映射到相同的物理地址
  • 一個虛擬地址空間可以大於實際可用硬件內存

在上面介紹中,從內核空間拷貝到最終用戶緩存看起來增加了額外的工作。為什么不告訴磁盤控制器直接發送數據到用戶空間的緩存呢?好吧,這是由虛擬內存實現的。用到了上面的優勢1。通過將內核空間地址映射到相同的物理地址作為一個用戶空間的虛擬地址,DMA硬件(只能訪問物理內存地址)可以填充緩存。這個緩存同時對內核和用戶空間進程可見。
在這里插入圖片描述
這就消除了內核用戶空間之間的拷貝,但是需要內核和用戶緩沖區使用相同的頁面對齊方式。
緩沖區必須使用的塊大小的倍數磁盤控制器(通常是512字節的磁盤扇區)。操作系統將其內存地址空間划分為頁面,這是固定大小的字節組。這些內存頁總是磁盤塊大小的倍數和通常為2倍(簡化尋址)。典型的內存頁面大小是102420484096字節。虛擬和物理內存頁面大小總是相同的。

1.3.3 內存分頁

為了支持虛擬內存的第2個優勢(擁有大於物理內存的可尋址空間)需要進行虛擬內存分頁(通常稱為頁交換)。
這種機制憑借虛擬內存空間的頁可以持久保存在外部磁盤存儲,從而為其他虛擬頁放入物理內存提供了空間。本質上講,物理內存擔當了分頁區域的緩存。分頁區是磁盤上的空間,內存頁的內容被強迫交換出物理內存時會保存到這里。
調整內存頁面大小為磁盤塊大小的倍數,讓內核可以直接發送指令到磁盤控制器硬件,將內存頁寫到磁盤或者在需要時重新加載。事實證明,所有的磁盤I/O操作都是在頁面級別上完成的。這是數據在現代分頁操作系統上在磁盤與物理內存之間移動的唯一方式。
現代CPU包含一個名為內存管理單元(MMU)的子系統。這個設備邏輯上位於CPU與物理內存之間。它包含從虛擬地址向物理內存地址轉化的映射信息。當CPU引用一個內存位置時,MMU決定哪些頁需要駐留(通常通過移位或屏蔽地址的某些位)以及轉化虛擬頁號到物理頁號(由硬件實現,速度奇快)。

1.3.4 面向文件、塊I/O

文件I/O總是發生在文件系統的上下文切換中。文件系統跟磁盤是完全不同的事物。磁盤按段存儲數據,每段512字節 。 它是硬件設備,對保存的文件語義一無所知。它們只是提供了一定數量的可以保存數據的插槽。從這方面來說,一個磁盤的段與內存分頁類似。它們都有統一的大小並且是個可尋址的大數組

另一方面,文件系統是更高層抽象。文件系統是 安排和翻譯保存磁盤(或其它可隨機訪問,面向塊的設備)數據的一種特殊方法。你寫的代碼幾乎總是與文件系統交互,而不與磁盤直接交互。文件系統定義了文件名、路徑、文件、文件屬性等抽象
一個文件系統(在硬盤中)組織了一系列均勻大小的數據塊。有些塊保存元信息,如空閑塊的映射、目錄、索引等。其它塊包含實際的文件數據。單個文件的元信息描述哪些塊包含文件數據、數據結束位置、最后更新時間等。當用戶進程發送請求來讀取文件數據時,文件系統實現准確定位數據在磁盤上的位置。然后采取行動將這些磁盤扇區放入內存中。
文件系統也有頁的概念,它的大小可能與一個基本內存頁面大小相同或者是它的倍數。典型的文件系統頁面大小范圍從2048到8192字節,並且總是一個基本內存頁面大小的倍數。

分頁文件系統執行I/O可以歸結為以下邏輯步驟:

  • 確定請求跨越了哪些文件系統分頁(磁盤段的集合)。磁盤上的文件內容及元數據可能分布在多個文件系統頁面上,這些頁面可能是不連續的。
  • 分配足夠多的內核空間內存頁面來保存相同的文件系統頁面。
  • 建立這些內存分頁與磁盤上文件系統分頁的映射。
  • 對每一個內存分頁產生分頁錯誤。
  • 虛擬內存系統陷入分頁錯誤並且調度pagins(頁面調入),通過從磁盤讀取內容來驗證這些頁面。
  • 一旦pageins完成,文件系統分解原始數據來提取請求的文件內容或屬性信息。

需要注意的是,這個文件系統數據將像其它內存頁一樣被緩存起來。在隨后的I/O請求中,一些數據或所有文件數據仍然保存在物理內存中,可以直接重用不需要從磁盤重讀。

1.3.5 文件鎖定

文件加鎖是一種機制,一個進程可以阻止其它進程訪問一個文件或限制其它進程訪問該文件。雖然名為文件鎖定,意味着鎖定整個文件(經常做的)。鎖定通常可以在一個更細粒度的水平
隨着粒度下降到字節級,文件的區域通常會被鎖定。鎖與特定文件相關聯,起始於文件的指定字節位置並運行到指定的字節范圍。這一點很重要,因為它允許多個進程協作訪問文件的特定區域而不妨礙別的進程在文件其它位置操作。
文件鎖有兩種形式:共享和獨占

  • 多個共享鎖可以同時在相同的文件區域有效。
  • 獨占鎖要求沒有其它鎖對請求的區域有效。

1.3.6 流I/O

並非所有的I/O是面向塊的。還有流I/O,它是管道的原型,必須順序訪問I/O數據流的字節。常見的數據流有TTY(控制台)設備、打印端口和網絡連接。
數據流通常但不一定比塊設備慢,提供間歇性輸入。大多數操作系統允許在非阻塞模式下工作。允許一個進程檢查數據流的輸入是否可用,不必在不可用時發生阻塞。這種管理允許進程在輸入到達時進行處理,在輸入流空閑時可以執行其他功能。
比非阻塞模式更進一步的是有條件的選擇(readiness selection)。它類似於非阻塞模式(並且通常建立在非阻塞模式基礎上),但是減輕了操作系統檢查流是否就緒准備的負擔。操作系統可以被告知觀察流集合,並向進程返回哪個流准備好的指令。這種能力允許進程通過利用操作系統返回的准備信息,使用通用代碼和單個線程復用多個活動流。這種方式被廣泛用於網絡服務器,以便處理大量的網絡連接。准備選擇對於大容量擴展是至關重要的。

1.4 IO流中flush原理

大家在使用Java IO流中OutputStream、PrintWriter ……時,會經常用到它的flush()方法。那么為什么要flush
與在網絡硬件中緩存一樣,流還可以在軟件中得到緩存,即直接在Java代碼中緩存。這可以通過BufferedOutputStreamBufferedWriter鏈接到底層流上來實現。因此,在寫完數據時,flush就顯得尤為重要
在這里插入圖片描述
上圖中WEB服務器通過輸出流向客戶端響應了一個300字節的信息,但是,這時的輸出流有一個1024字節的緩沖區。所以,輸出流就一直等着WEB服務器繼續向客戶端響應信 息,當WEB服務器的響應信息把輸出流中的緩沖區填滿時,這時,輸出流才向WEB客戶端響應消息。
為了解決這種尷尬的局面,flush()方法出現了。flush()方法可以強迫輸出流(或緩沖的流)發送數據,即使此時緩沖區還沒有填滿,以此來打破這種死鎖狀態
當我們使用輸出流發送數據時,當數據不能填滿輸出流的緩沖區時,這時,數據就會被存儲在輸出流的緩沖區中。如果,我們這個時候調用關閉(close)輸出流,存儲在輸出流的緩沖區中的數據就會丟失。所以說,關閉(close)輸出流時,應先刷新(flush)換沖的輸出流,話句話說就是:迫使所有緩沖的輸出數據被寫出到底層輸出流中

解讀flush()源碼:
下面以BufferedOutputStream類為例:

public class BufferedOutputStream extends FilterOutputStream   {
          public synchronizedvoid flush()  throws IOException{ 
          flushBuffer();           
            out.flush();        
           }       
 private void flushBuffer()  throws IOException       {
         if(count > 0) {            
         out.write(buf, 0, count);            
         count = 0;        
         }     
         }
         }

看到這里大家明白了吧,其實flush()也是通過out.write()將數據寫入底層輸出流的

1.6 System.out.println(hello world)原理

我們初學java的第一個程序是hello world

 public class HelloWorld {
     public static void main(String[] args) {
         System.out.println("hello world");
     }
}

上面程序到底是怎么在屏幕上輸出hello world的呢?這就是本來要講解的內容,即System.out.println("hello world")的原理。
我們先看看System.out.println的流程。先看看System.javaout的定義,源碼如下

public final class System {
     ... 
     public final static PrintStream out = null; 
     ...
}

從中,我們發現:

  • outSystem.java的靜態變量。而且outPrintStream對象,PrintStream.java中有許多重載的println()方法。
    我們知道了outPrintStream對象。接下來,看它是如何被初始化的,它是怎么和屏幕輸出關聯的?
    我們還是一步步來分析,首先看看System.javainitializeSystemClass()方法。
    initializeSystemClass()的源碼如下
 private static void initializeSystemClass() {
 
     props = new Properties();
     initProperties(props);  // initialized by the VM 5 
      sun.misc.VM.saveAndRemoveProperties(props);
  
      lineSeparator = props.getProperty("line.separator");
      sun.misc.Version.init();
 
     FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
     FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
     FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
     setIn0(new BufferedInputStream(fdIn));
     setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
     setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true));
 
     loadLibrary("zip");
 
     Terminator.setup();
 
     sun.misc.VM.initializeOSEnvironment();
 
     Thread current = Thread.currentThread();
     current.getThreadGroup().add(current);
 
    setJavaLangAccess();
 
     sun.misc.VM.booted();
}

我們只需要關注部分代碼:即

FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));

將這兩句話細分,可以划分為以下幾步:

  1. FileDescriptor fd = FileDescriptor.out;
  2. FileOutputStream fdOut = new FileOutputStream(fd);
  3. BufferedOutputStream bufOut = new BufferedOutputStream(fdOut, 128);
  4. PrintStream ps = new PrintStream(bufout, true);
  5. setOut0(ps);

說明:
第1步,獲取FileDescriptor.java中的靜態成員outout是一個FileDescriptor對象,它實際上是標准輸出(屏幕)的標識符。
FileDescriptor.java中與FileDescriptor.out相關代碼如下

public final class FileDescriptor {
      private int fd;  
      public static final FileDescriptor out = new FileDescriptor(1);
  
      private FileDescriptor(int fd) {
          this.fd = fd;
         useCount = new AtomicInteger();
     }
 
     ...
}

創建標准輸出(屏幕)對應的文件輸出流
創建文件輸出流對應的緩沖輸出流。目的是為“文件輸出流”添加緩沖功能。
創建“緩沖輸出流”對應的“打印輸出流”。目的是為“緩沖輸出流”提供方便的打印接口,如print(), println(), printf();使其能方便快捷的進行打印輸出。
執行setOut0(ps);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM