哈喽,大家好,我是强哥。
今天我们来讲讲Java的标准输入输出流,哈哈,虽然是基础,但是往深了挖挖,还是有许多干货的。
我们先来看个面试题。
说说获取用键盘输入常用的两种方法方法 1:通过 Scanner
Scanner input = new Scanner(System.in); String s = input.nextLine(); input.close();
方法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine();
问题很简答,初学Java的在控制台写简单main方法的时候,都是用的System.in这个方式来获取键盘输入的内容。
可是,强哥想问问,大家既然会用System.in,但是真的熟悉System这个类吗?
System类中的IO知识Java遵循标准I/O的模型,提供了Syetem.in,System.out,以及System.err。System.out是一个已经预先处理过的,被包装成PrintStream的对象。和System.out一样,System.err也是一个PrintStream,但是System.in不是,它是一个未经处理的InputStream。也就是说,可以直接使用System.out和System.err打印输出到控制台,但是使用System.in直接读取数据不可以,必须先做处理。
我们看看System的三个成员变量定义:
//标准输入流,一般接收键盘输入 public final static InputStream in = null; //标准输出流,向控制台输出正确信息 public final static PrintStream out = null //标准输出流,向控制台输出错误信息 public final static PrintStream err = null;
从上面的代码我们可以看出,这些对象被final修饰且赋值为null,那么为什么上面获取输入输出的时候又可以用而不会报空指针呢?
我们看下面的代码就知道了:
static { registerNatives(); } private static native void registerNatives();
没错是在native方法中做的处理,具体在VM中会执行这个方法
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"))); loadLibrary("zip"); Terminator.setup(); sun.misc.VM.initializeOSEnvironment(); Thread current = Thread.currentThread(); current.getThreadGroup().add(current); setJavaLangAccess(); sun.misc.VM.booted(); }
setIn0(new BufferedInputStream(fdIn));设置标准输入流,也就是创建in对象,追溯到源头也就是创建了FileDescriptor.in对象,这个对象用于操作标准输入流,再往深了就到了虚拟机内部实现细节了。
那么到底什么是标准的输入输出流呢?
什么是标准输入、标准输出(stdin、stdout)?要弄清什么是标准输入输出。首先需要弄懂什么是IO。 I:从外部设备输入到内存; O:从内存输出到外部设备;
而标准输入和标准输出是干什么的?它们是用于 IO 的。 属于IO的外部设备部分(逻辑上的外部设备,为什么?接着看)。
在linux操作系统中,外部设备用什么表示?linux中一切设备皆是文件! 因此标准输入和输出更具体的含义是文件。
它们是哪两个文件? 它们是/dev/stdin这个文件和/dev/stdout这个文件。 也就是说所谓的标准输入和标准输出其实就是两个linux下的文件。
那么所谓的从标准输入到底是什么意思? 逻辑上来看:就是打开/dev/stdin这个文件,然后把这个文件里的内容读进来。
输出到标准输出是什么意思? 逻辑上来看:就是打开/dev/stdout这个文件,然后把内容输出到这个文件里去。
为什么是从逻辑上来看?因为它们不是设备文件!!! 所以它们不代表一个设备。linux里一切皆是文件,设备是文件,但是文件不一定是设备!
那它们是什么文件?他们是链接文件。(可以用 ls -l /dev 来查看 l 开头的就是链接文件。也可以用 file 命令查看其文件类型,命令:file /dev/stdin)
什么是链接文件?文件内容是另一个文件的地址的文件称为链接文件。 因此,打开、读或者写/dev/stdin和/dev/stdout实际上是打开、读或者写这两个文件存放的地址对应的设备文件。
那么,既然是这样,Java是不是可以对标准输入输出设备进行修改呢?答案是肯定的。继续看。
修改Java标准输入输出设备System类中有如下方法可以进行对标准输入输出设备进行修改
setIn:
public static void setIn(InputStream in) { checkIO(); setIn0(in); } //对setIo进行安全检查 private static void checkIO() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setIO")); } } //给System.in重新赋值,内部实现。 private static native void setIn0(InputStream in);
给System.in对象重新赋值。即当调用了些方法后,我们再使用System.in的时候接收的信息不再是从键盘录入了,而是我们指定的文件或设备。点开函数可以看到,第一个函数是进行安全检查的,第二个函数是调用的本地方法。有人疑惑System.in变量不是被final修饰了吗,为什么还可以被修改。额...这个...它调用的是本地方法,由虚拟机内部实现,他是老大能不能改变他说了算。
setOut:
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }
给System.out重新赋值,原理同setIn。比如我们想让System.out.println()把内容输出到文件,就可以调用这个方法,指定输出文件。
setErr:
public static void setErr(PrintStream err) { checkIO(); setErr0(err); }
给System.err重新赋值,原理同setOut。
写在最后综上,就是强哥通过一个面试问题,收集的关于Java标准输入输出相关的知识。
强哥觉得,很多问题,从表面上回答,很简单,很多初学者都会。可是,如果往深了聊,你是否能够回答个所以然来,这个是你与其他人的不同。多学习多思考,多关注强哥的公众号推送。
强哥只讲干货啦~