天天看點

java中的System.in、System.out、System.err

java中的标準輸入輸出以及錯誤有時候會讓人迷惑,這裡通過檢視源碼來對他們深入了解一下。

定義

  • 标準輸入 System.in
  • 标準輸出 System.out
  • 标準錯誤 System.err

初始化分析

檢視 java API或者源碼可以看到定義(java.lang.System類):

public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
           

繼續檢視源碼我們看in、out、err是如何初始化的呢?

public final class System {

    /* register the natives via the static initializer.
     *
     * VM will invoke the initializeSystemClass method to complete
     * the initialization for this class separated from clinit.
     * Note that to use properties set by the VM, see the constraints
     * described in the initializeSystemClass method.
     */
    private static native void registerNatives();
    static {
        registerNatives();
    }
    ...
           

可以看到首先System這個類初始化的時候會注冊native方法并執行initializeSystemClass 這個方法進行初始化,那麼我們看initializeSystemClass這個方法内容:

/**
     * Initialize the system class.  Called after thread initialization.
     */
    private static void initializeSystemClass() {
        props = new Properties();
        initProperties(props);  // initialized by the VM
        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(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
        setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
        ...
           

這裡我删掉了部分注釋與後續代碼,友善閱讀,看最後三個方法setInt0、setOut0、setErr0。找到這三個方法:

private static native void setIn0(InputStream in);
    private static native void setOut0(PrintStream out);
    private static native void setErr0(PrintStream err);
           

發現又是native方法,這時候就不能繼續檢視native方法了,因為這種方法都是系統層面的C語言實作的,其實可以下載下傳OpenJDK看一下源碼,我這裡下載下傳了OpenJDK8源碼

下載下傳下來解壓,然後打開System.c檢視

vim OpenJDK8-master/jdk/src/share/native/java/lang/System.c
           
JNIEXPORT void JNICALL
 Java_java_lang_System_setIn0(JNIEnv *env, jclass cla, jobject stream)
 {
     jfieldID fid =
         (*env)->GetStaticFieldID(env,cla,"in","Ljava/io/InputStream;");
     if (fid == 0)
         return;
     (*env)->SetStaticObjectField(env,cla,fid,stream);
 }
 JNIEXPORT void JNICALL
 Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream)
 {
     jfieldID fid =
         (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;");
     if (fid == 0)
         return;
     (*env)->SetStaticObjectField(env,cla,fid,stream);
 }

 JNIEXPORT void JNICALL
 Java_java_lang_System_setErr0(JNIEnv *env, jclass cla, jobject stream)
 {
     jfieldID fid =
         (*env)->GetStaticFieldID(env,cla,"err","Ljava/io/PrintStream;");
     if (fid == 0)
         return;
     (*env)->SetStaticObjectField(env,cla,fid,stream);
 }
           

可以看到C代碼對in、out和err初始化的過程。

使用

System.in

對于System.in是很少使用的,使用最多的估計就是剛開始入門學習java的時候的經典echo程式:

public static void main(String[] args) {  
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()) {
            System.out.println("echo:"+sc.nextLine());
        }
    }
           

System.out與System.err

public static void main(String[] args) {  
        System.out.println("out");
        System.err.println("err");
    }
           

輸出結果:

java中的System.in、System.out、System.err

這裡out是黑色,二err為紅色。表面上看就是這點差別。

不過繼續看下面的程式:

public static void main(String[] args) {  
        for(int i = 0; i<100 ;i++) {
            System.out.println(i);
            System.err.println(i);
        }
    } 
           

檢視輸出結果:

java中的System.in、System.out、System.err

按理來說,輸出的結果都應該是成對出現,可是為何最終輸出是亂的呢?

這裡實際上就不是java層面的問題了。因為幾乎對于所有作業系統都有标準輸入、标準輸出、标準錯誤三個概念以及實作,也幾乎都是C語言實作的,C語言的标準輸入輸出是分緩沖和無緩沖的,一般地,标準輸入stdin和标準輸出stdout為行緩沖,标準錯誤輸出stderr無緩沖。

java中的标準輸出以及标準錯誤,都是直接用native方法調用系統的标準輸出以及标準錯誤,是以java中的System.out是行緩沖,System.err是無緩沖。正因為有緩沖區的存在,在同時使用System.out和System.err的時候,err會在out還未達到緩沖界限提前輸出的情況。導緻列印結果是亂的。

類似的還有異常捕獲的時候,檢視Throwable源碼

public void printStackTrace() {
        printStackTrace(System.err);
    }
           

可以看到預設的printStackTrace()方法傳入的是System.err。是以如果同時使用System.in與printStackTrace()也可能出現亂序問題,當然實際開發中隻有寫程式測試的時候才會出現這種情況,這種情況對開發也沒用影響,隻是看到現象不用奇怪即可。