1. 程式人生 > >[java] System類深度解析

[java] System類深度解析

概述

大部分寫的第一個java功能,應該都是如下這行程式碼:

System.out.println("hello world");

這裡即用到了System類,本篇文章就將分析System類的實現原理。在java裡面System類是一個final修飾的類,意味著它的方法和屬性都不能被修改。

System 類包含了多個有用的欄位和方法,它不能被例項化。在system類提供的功能當中,有標準輸入、標準輸出和錯誤輸出流,訪問外部定義的屬性和環境變數;載入檔案和庫的方法;以及用於快速複製陣列的一部分的實用方法。

構造方法如下:

 /** Don't let anyone instantiate this class */
    private System() {
    }

註釋裡寫的非常清楚:不讓任何人例項化這個類。

關於System.out.println()實現原理

System是一個類,而out則是該類的一個成員變數,定義如下:

 /**
     * The "standard" output stream. This stream is already
     * open and ready to accept output data. Typically this stream
     * corresponds to display output or another output destination
     * specified by the host environment or user.
     * <p>
     * For simple stand-alone Java applications, a typical way to write
     * a line of output data is:
     * <blockquote><pre>
     *     System.out.println(data)
     * </pre></blockquote>
     * <p>
     * See the <code>println</code> methods in class <code>PrintStream</code>.
     *
     * @see     java.io.PrintStream#println()
     * @see     java.io.PrintStream#println(boolean)
     * @see     java.io.PrintStream#println(char)
     * @see     java.io.PrintStream#println(char[])
     * @see     java.io.PrintStream#println(double)
     * @see     java.io.PrintStream#println(float)
     * @see     java.io.PrintStream#println(int)
     * @see     java.io.PrintStream#println(long)
     * @see     java.io.PrintStream#println(java.lang.Object)
     * @see     java.io.PrintStream#println(java.lang.String)
     */
    public final static PrintStream out = null;

可以看到,out實際上是類PrintSteam的一個例項, 而println()就是PrintStream類的一個方法而已。PrintStream類是“標準”輸出流。此流已經開啟並準備接受輸出資料。通常,該流對應於主機環境或使用者指定的顯示輸出或另一輸出目的地。而在我們的編輯器裡面,它就是輸出在console上的。

但是這裡的out變數是null並沒有例項化,那麼它是在哪裡例項化的呢?答案在System類的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")));

        // Load the zip library now in order to keep java.util.zip.ZipFile
        // from trying to use itself to load this library later.
        loadLibrary("zip");

        // Setup Java signal handlers for HUP, TERM, and INT (where available).
        Terminator.setup();

        ...
        sun.misc.VM.initializeOSEnvironment();

        // The main thread is not added to its thread group in the same
        // way as other threads; we must do it ourselves here.
        Thread current = Thread.currentThread();
        current.getThreadGroup().add(current);

        // register shared secrets
        setJavaLangAccess();

        ...
        sun.misc.VM.booted();
    }

在上面的程式碼當中,有一行呼叫了setOut0()方法,該方法就是對out屬性初始化的方法。不出意外,它又是一個native方法。在open jdk當中,有它的實現程式碼,如下:

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);
}

setOut0(PrintStream ps)的作用,就是將PrintStream設定為System類的out靜態變數。

而在PrintSteam類裡面,println方法有多個過載的實現,它們最終都是呼叫的一個write方法,實現如下

 private void write(String s) {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.write(s);
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush && (s.indexOf('\n') >= 0))
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }

該write方法呼叫的是PrintSteam類的成員變數BufferedWriter類的一個例項,在BufferedWriter類裡面,我們可以看到write方法的實現:

 /**
     * Writes a portion of an array of characters.
     *
     * <p> Ordinarily this method stores characters from the given array into
     * this stream's buffer, flushing the buffer to the underlying stream as
     * needed.  If the requested length is at least as large as the buffer,
     * however, then this method will flush the buffer and write the characters
     * directly to the underlying stream.  Thus redundant
     * <code>BufferedWriter</code>s will not copy data unnecessarily.
     *
     * @param  cbuf  A character array
     * @param  off   Offset from which to start reading characters
     * @param  len   Number of characters to write
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void write(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return;
            }

            if (len >= nChars) {
                /* If the request length exceeds the size of the output buffer,
                   flush the buffer and then write the data directly.  In this
                   way buffered streams will cascade harmlessly. */
                flushBuffer();
                out.write(cbuf, off, len);
                return;
            }

            int b = off, t = off + len;
            while (b < t) {
                int d = min(nChars - nextChar, t - b);
                System.arraycopy(cbuf, b, cb, nextChar, d);
                b += d;
                nextChar += d;
                if (nextChar >= nChars)
                    flushBuffer();
            }
        }
    }

因此,例項化了PrintStream物件,然後藉助它去呼叫println方法,這就是大致的思想。

System.gc方法

 /**
     * Runs the garbage collector.
     * <p>
     * Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects.
     * <p>
     * The call <code>System.gc()</code> is effectively equivalent to the
     * call:
     * <blockquote><pre>
     * Runtime.getRuntime().gc()
     * </pre></blockquote>
     *
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }

程式碼如上,又是一個native方法,結合註釋即可理解它的含義。另外值得注意的是,這個方法並不是強制讓虛擬機器回收不用的物件,這個方法只是提醒虛擬機器,程式設計師希望你在這回收一下物件,但回不回收還是虛擬機器來決定,也就是說程式設計師對回不回收沒有絕對的控制權。關於這個問題在我的另一篇部落格裡也有提到。

System.exit方法

定義如下:

 /**
     * Terminates the currently running Java Virtual Machine. The
     * argument serves as a status code; by convention, a nonzero status
     * code indicates abnormal termination.
     * <p>
     * This method calls the <code>exit</code> method in class
     * <code>Runtime</code>. This method never returns normally.
     * <p>
     * The call <code>System.exit(n)</code> is effectively equivalent to
     * the call:
     * <blockquote><pre>
     * Runtime.getRuntime().exit(n)
     * </pre></blockquote>
     *
     * @param      status   exit status.
     * @throws  SecurityException
     *        if a security manager exists and its <code>checkExit</code>
     *        method doesn't allow exit with the specified status.
     * @see        java.lang.Runtime#exit(int)
     */
    public static void exit(int status) {
        Runtime.getRuntime().exit(status);
    }

這個方法的作用是結束當前正在執行的Java虛擬機器,int型別的status表示退出的狀態碼,非零表示異常終止。注意:無論status為何值程式都會退出,和return 相比有不同的是:return是回到上一層,而System.exit(status)是回到最上層。