Java 日看一类(9)之IO包中的CharConversionException类和Console类

CharConversionException类是异常类,继承自IO包中的IOException



其作用注释如下:

/**
 * Base class for character conversion exceptions.
 *
 * @author      Asmus Freytag
 * @since       JDK1.1
 */

大意为:

该类为字符转换异常的基础类


仅含有一个成员变量:

该异常类的穿行序列号:

private static final long serialVersionUID = -8680016352018427031L;

仅含有两个构造函数:

不提供字符的构造函数:

public CharConversionException() {}

提供字符的构造函数:

public CharConversionException(String s) {
    super(s);
}

该异常类作用非常明显,就是提示字符转换异常



Console类完成了Flushable的接口,同时引入了如下文件:

import java.util.*;
import java.nio.charset.Charset;
import sun.nio.cs.StreamDecoder;
import sun.nio.cs.StreamEncoder;




其作用注释如下:

/**
 * Methods to access the character-based console device, if any, associated
 * with the current Java virtual machine.
 *
 * <p> Whether a virtual machine has a console is dependent upon the
 * underlying platform and also upon the manner in which the virtual
 * machine is invoked.  If the virtual machine is started from an
 * interactive command line without redirecting the standard input and
 * output streams then its console will exist and will typically be
 * connected to the keyboard and display from which the virtual machine
 * was launched.  If the virtual machine is started automatically, for
 * example by a background job scheduler, then it will typically not
 * have a console.
 * <p>
 * If this virtual machine has a console then it is represented by a
 * unique instance of this class which can be obtained by invoking the
 * {@link java.lang.System#console()} method.  If no console device is
 * available then an invocation of that method will return <tt>null</tt>.
 * <p>
 * Read and write operations are synchronized to guarantee the atomic
 * completion of critical operations; therefore invoking methods
 * {@link #readLine()}, {@link #readPassword()}, {@link #format format()},
 * {@link #printf printf()} as well as the read, format and write operations
 * on the objects returned by {@link #reader()} and {@link #writer()} may
 * block in multithreaded scenarios.
 * <p>
 * Invoking <tt>close()</tt> on the objects returned by the {@link #reader()}
 * and the {@link #writer()} will not close the underlying stream of those
 * objects.
 * <p>
 * The console-read methods return <tt>null</tt> when the end of the
 * console input stream is reached, for example by typing control-D on
 * Unix or control-Z on Windows.  Subsequent read operations will succeed
 * if additional characters are later entered on the console's input
 * device.
 * <p>
 * Unless otherwise specified, passing a <tt>null</tt> argument to any method
 * in this class will cause a {@link NullPointerException} to be thrown.
 * <p>
 * <b>Security note:</b>
 * If an application needs to read a password or other secure data, it should
 * use {@link #readPassword()} or {@link #readPassword(String, Object...)} and
 * manually zero the returned character array after processing to minimize the
 * lifetime of sensitive data in memory.
 *
 * <blockquote><pre>{@code
 * Console cons;
 * char[] passwd;
 * if ((cons = System.console()) != null &&
 *     (passwd = cons.readPassword("[%s]", "Password:")) != null) {
 *     ...
 *     java.util.Arrays.fill(passwd, ' ');
 * }
 * }</pre></blockquote>
 *
 * @author  Xueming Shen
 * @since   1.6
 */

大意如下:

方法访问基于字符的控制台程序,如果获得批准的话就将其与该程序所属的java虚拟机链接。

虚拟机是否含有控制台取决于底层系统平台和该对虚拟机的调用方法。

如果虚拟机从命令行启动且没有重定向标准输入输出流,则将存在控制台,并且通常和键盘以及虚拟机的显示相连接

如果虚拟机自动启动,例如后台调度程序,则一般不会存在控制台

如果虚拟机含有控制台,则该控制台为该类调用java.lang.System中的console方法获得的唯一实例

如果没有控制台可用时,则上述方法返回值为null

读和写操作为属于同步类型的操作(线程锁)来保证关键步骤的原子层面操作。

因此,除调用方法readLine()、readPassword()、format()、printf()以外,读、写和格式化的操作通过writer或reader返回一个对象的情况可以嵌入多线程方案

对reader和writer返回的对象调用close方法将不会关闭该对象包含的底层流

当控制台的输入流读取结束后,控制台再调用read方法将会返回空值

例如,键入control+d在Unix下或者键入control+z在Windows下,随后如果输入字符,读取操作将会成功。

除非另有说明,否则向该类中的任意方法传递空值会抛出空指针异常。

安全手册:

如果一个应用需要去读取密码或者其他安全数据,它应该使用readPassword()方法并且该敏感数据在内存中的存在周期被最小化处理后应手动清零

引例:

 Console cons;
 char[] passwd;
 if ((cons = System.console()) != null &&
      (passwd = cons.readPassword("[%s]", "Password:")) != null) {
      ...
      java.util.Arrays.fill(passwd, ' ');
 }



该类含有十个成员变量:

线程读取锁:

private Object readLock;

线程写入锁:

private Object writeLock;

控制台唯一保有的Reader:

private Reader reader;

控制台唯一保有的Writer:

private Writer out;

控制台唯一保有的PrintWriter:

private PrintWriter pw;

控制台的格式控制器:

private Formatter formatter;

当前使用字符集名称:

private Charset cs;

存储字符的缓冲区:

private char[] rcb;

是否显示:

private static boolean echoOff;

当前控制台对象:

private static Console cons;




该类含有如下方法:

从控制台取回唯一关联PrinterWriter:

public PrintWriter writer() {
    return pw;
}

从控制台取回唯一关联Reader:

public Reader reader() {
    return reader;
}

 使用指定格式字符串和参数将格式化字符串写入此控制台输出流的方法(会忽略比格式字符多的参数):

public Console format(String fmt, Object ...args) {//动态参数
    formatter.format(fmt, args).flush();//将对象按格式写入字符串并将字符串写入输出流
    return this;//返回本控制台
}
使用指定格式字符串和参数将格式化字符串写入此控制台输出流的便捷方法:(完全没看出来怎么个便捷法,注释和api也没有说明,使用该方法还要比直接使用format多个入栈出栈的操作)(笑
public Console printf(String format, Object ... args) {
    return format(format, args);
}
提供一个格式化提示,并从控制台读取单行文本:
public String readLine(String fmt, Object ... args) {
    String line = null;
    synchronized (writeLock) {//写锁
        synchronized(readLock) {//读锁
            if (fmt.length() != 0)//格式化提示长度不为零
                pw.format(fmt, args);//按照提示符格式化
            try {
                char[] ca = readline(false);
                if (ca != null)//readline读取成功
                    line = new String(ca);
            } catch (IOException x) {
                throw new IOError(x);
            }
        }
    }
    return line;
}

直接读取单行文本(无格式化提示):

public String readLine() {
    return readLine("");
}

提供一个格式化信息,从控制台读取安全数据(如密码),禁用返回值显示:

public char[] readPassword(String fmt, Object ... args) {
    char[] passwd = null;
    synchronized (writeLock) {
        synchronized(readLock) {
            try {
                echoOff = echo(false);//禁用返回值显示
            } catch (IOException x) {
                throw new IOError(x);
            }
            IOError ioe = null;
            try {
                if (fmt.length() != 0)
                    pw.format(fmt, args);
                passwd = readline(true);
            } catch (IOException x) {
                ioe = new IOError(x);
            } finally {
                try {
                    echoOff = echo(true);//启用返回值显示
                } catch (IOException x) {
                    if (ioe == null)
                        ioe = new IOError(x);
                    else
                        ioe.addSuppressed(x);
                }
                if (ioe != null)
                    throw ioe;
            }
            pw.println();//打印格式化密码
        }
    }
    return passwd;//返回密码值
}

返回无格式化密码:

public char[] readPassword() {
    return readPassword("");
}

将PrintWriter数据全部写出:

public void flush() {
    pw.flush();
}

解码:

private static native String encoding();

修改返回值显示状态:

private static native boolean echo(boolean on) throws IOException;

读取一行文本并返回:

private char[] readline(boolean zeroOut) throws IOException {
    int len = reader.read(rcb, 0, rcb.length);//将数据读入缓冲区并返回实际读取长度
    if (len < 0)
        return null;  //EOL
    if (rcb[len-1] == '\r')//换行符
        len--;        //remove CR at end;
    else if (rcb[len-1] == '\n') {//新行符
        len--;        //remove LF at end;
        if (len > 0 && rcb[len-1] == '\r')
            len--;    //remove the CR, if there is one
    }
    char[] b = new char[len];
    if (len > 0) {
        System.arraycopy(rcb, 0, b, 0, len);
        if (zeroOut) {//是否清空缓冲区
            Arrays.fill(rcb, 0, len, ' ');
        }
    }
    return b;//返回被读取的文本
}

增加读取缓冲区的容量(为当前的二倍):

private char[] grow() {
    assert Thread.holdsLock(readLock);//断言当前程序拥有读取锁
    char[] t = new char[rcb.length * 2];
    System.arraycopy(rcb, 0, t, 0, rcb.length);
    rcb = t;
    return rcb;
}

判定当前是否为终端与控制台链接:

private native static boolean istty();

私有构造函数:(无法在类外访问,也就是无法直接new出来,有许多特殊的“套路”,在这里我就不详细说明了,由兴趣的可以自己研究,在这里主要是与后述的静态代码块配合生成控制台(封装了控制台的生成条件),也算是“套路”的一种)

private Console() {
    readLock = new Object();//初始化成员变量
    writeLock = new Object();
    String csname = encoding();//获得当前使用的字符集
    if (csname != null) {
        try {
            cs = Charset.forName(csname);
        } catch (Exception x) {}
    }
    if (cs == null)
        cs = Charset.defaultCharset();//设置为默认字符集
    out = StreamEncoder.forOutputStreamWriter(//对输出流编码
              new FileOutputStream(FileDescriptor.out),
              writeLock,
              cs);
    pw = new PrintWriter(out, true) { public void close() {} };
    formatter = new Formatter(out);
    reader = new LineReader(StreamDecoder.forInputStreamReader(//输入流解码
                 new FileInputStream(FileDescriptor.in),
                 readLock,
                 cs));
    rcb = new char[1024];//初始大小
}





内部嵌套类:

class LineReader extends Reader {
    private Reader in;//读入类
    private char[] cb;//缓冲区
    private int nChars, nextChar;//缓冲区标识符
    boolean leftoverLF;//跳过新行符标记
    LineReader(Reader in) {
        this.in = in;
        cb = new char[1024];
        nextChar = nChars = 0;
        leftoverLF = false;
    }
    public void close () {}
    public boolean ready() throws IOException {//确认流开启
        //in.ready synchronizes on readLock already
        return in.ready();
    }

    public int read(char cbuf[], int offset, int length)//从本类的缓冲区中读出特定长度数组数据
        throws IOException
    {
        int off = offset;
        int end = offset + length;
        if (offset < 0 || offset > cbuf.length || length < 0 ||
            end < 0 || end > cbuf.length) {//数组下标有效性验证
            throw new IndexOutOfBoundsException();
        }
        synchronized(readLock) {
            boolean eof = false;//结束标记
            char c = 0;
            for (;;) {
                if (nextChar >= nChars) {   //fill//读取指针超出有效长度
                    int n = 0;
                    do {
                        n = in.read(cb, 0, cb.length);
                    } while (n == 0);//读取失败后继续读取(感觉这样有可能要进死循环)
                    if (n > 0) {//成功读取
                        nChars = n;
                        nextChar = 0;
                        if (n < cb.length &&
                            cb[n-1] != '\n' && cb[n-1] != '\r') {//读取量小于缓冲空间且结尾不为换行符或新行符
                            /*
                             * we're in canonical mode so each "fill" should
                             * come back with an eol. if there no lf or nl at
                             * the end of returned bytes we reached an eof.
                             */
                            eof = true;//标记结束
                        }
                    } else { /*EOF*/
                        if (off - offset == 0)//读取指针没有修改,报错
                            return -1;
                        return off - offset;//返回实际读取长度
                    }
                }
                if (leftoverLF && cbuf == rcb && cb[nextChar] == '\n') {//当需要跳过下个新行符且传入缓冲区为Console的缓冲区,且下个字符为新行符,直接跳过
                    /*
                     * if invoked by our readline, skip the leftover, otherwise
                     * return the LF.
                     */
                    nextChar++;
                }
                leftoverLF = false;//重置标记
                while (nextChar < nChars) {
                    c = cbuf[off++] = cb[nextChar];
                    cb[nextChar++] = 0;//读取后清空缓存
                    if (c == '\n') {//读取到新行符终止
                        return off - offset;
                    } else if (c == '\r') {//当前标记为换行符
                        if (off == end) {//已完成读取目标
                            /* no space left even the next is LF, so return
                             * whatever we have if the invoker is not our
                             * readLine()
                             */
                            if (cbuf == rcb) {//传入数组为Console的缓冲数组
                                cbuf = grow();//增加缓冲区
                                end = cbuf.length;//修改终止位置
                            } else {
                                leftoverLF = true;//标记跳过下个新行符
                                return off - offset;
                            }
                        }
                        if (nextChar == nChars && in.ready()) {//缓冲区读取完毕且流仍然开启
                            /*
                             * we have a CR and we reached the end of
                             * the read in buffer, fill to make sure we
                             * don't miss a LF, if there is one, it's possible
                             * that it got cut off during last round reading
                             * simply because the read in buffer was full.
                             */
                            nChars = in.read(cb, 0, cb.length);//再次读取
                            nextChar = 0;
                        }
                        if (nextChar < nChars && cb[nextChar] == '\n') {//缓冲区未读取完毕且读取到新行符,读入新行符
                            cbuf[off++] = '\n';
                            nextChar++;
                        }
                        return off - offset;
                    } else if (off == end) {//完成读取目标
                       if (cbuf == rcb) {
                            cbuf = grow();
                            end = cbuf.length;
                       } else {
                           return off - offset;
                       }
                    }
                }
                if (eof)//流传输结束
                    return off - offset;
            }
        }
    }
}





静态代码块:(在构造函数前执行,且不论实例化多少对象都仅会运行一次,执行优先级最高)

static {
    try {
        // Add a shutdown hook to restore console's echo state should
        // it be necessary.
        sun.misc.SharedSecrets.getJavaLangAccess()
            .registerShutdownHook(0 /* shutdown hook invocation order */,
                false /* only register if shutdown is not in progress */,
                new Runnable() {
                    public void run() {
                        try {
                            if (echoOff) {
                                echo(true);
                            }
                        } catch (IOException x) { }
                    }
                });
    } catch (IllegalStateException e) {
        // shutdown is already in progress and console is first used
        // by a shutdown hook
    }

    sun.misc.SharedSecrets.setJavaIOAccess(new sun.misc.JavaIOAccess() {
        public Console console() {
            if (istty()) {
                if (cons == null)
                    cons = new Console();
                return cons;
            }
            return null;
        }

        public Charset charset() {
            // This method is called in sun.security.util.Password,
            // cons already exists when this method is called
            return cons.cs;
        }
    });
}

这部分英文注释很详细,我就不写了,作用就是设置该控制台的IO连接




该类十分特别,是目前所详细了解源码中最复杂的(没看过多少源码,才疏学浅,见谅),作为控制模块需要与各类程序、终端以及数据流有密切接触。在实际使用中虽然经常使用到该类的功能,却从未在写代码中使用过该类。有一定必要读懂该类的工作原理。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值