Java I/O系统学习四:标准IO
几乎所有学习Java的同学写的第一个程序都是hello world,使用的也都是System.out.println()这条语句来输出"hello world",我也不例外,当初学的时候只是简单拿来用,平时学习的时候需要打印日志也会使用这条语句,并没有去探究这条语句背后的原理,本文就来研究一下其原理。
System.out.println()提供的能力属于标准I/O的范畴,标准I/O这个术语参考的是Unix中“程序所使用的单一信息流”这个概念,即程序的所有输入都可以来自标准输入,它的所有输出也可以发送到标准输出,以及所有的错误信息都可以发送到标准错误。标准I/O的意义在于:我们可以很容易地把程序串联起来,一个程序的标准输出可以成为另一程序的标准输入。
其实对于标准I/O,直观一点的理解可以是来自命令行的I/O,因为程序通常是在命令行下运行的,并且是在命令行环境下和用户交互的。所以Java平台提供了两种方式用于和程序进行交互:通过标准流的方式和通过控制台的方式。
标准流是很多操作系统都有的一个特性。默认情况下,标准流是从键盘读取输入并且输出到显示设备(显示器)。同时标准流也支持输入输出到文件或者程序之间的I/O,但是这两种特性是由命令行解释器来决定的,而不是程序。
Java平台支持三种标准流:
- 标准输入,通过System.in获取
- 标准输出,通过System.out获取
- 标准错误,通过System.err获取
这些对象是预定义好的并且不需要手动打开。标准输出(Standard Output)和标准错误(Standard Error)都是用于输出,提供错误输出的好处是使用者可以将正常输出指向一个文件,同时还能够读取错误信息。
下面就来详细介绍一下这些标准流。
1. 标准输出
1.1 类结构
说到标准输出就不得不说常见的System.out.println()了,这是一条Java语句,没错,它可以将程序传给System.out(标准输出)的参数打印出来。我们可以将其分成三部分来看:
System
这是java.lang包中的一个final类,主要的作用如下:
- 提供了如标准输入,标准输出和错误输出流等基础设施;
- 可以访问外部定义的属性和环境变量;
- 提供了一种加载文件和库的方法;
- 提供了可以快速复制数组的部分内容的方法;
out
这是System类的一个静态成员字段,类型是PrintStream。其访问标识符是public final,这意味着它在启动时就会被实例化,并与主机的标准输出控制台进行关联,并且该流在实例化之后会自动打开,并准备接受数据。
println
这是PrintStream的一个方法,可以输出内容到控制台。
这里直接盗用一张网上的类图,结合起来看会更清晰其结构:

说完类结构,我们再来看看System.out的一些其他操作。
1.2 将System.out转换成PrintWriter
System.out是一个PrintStream,而PrintStream是一个OutputStream。PrintWriter有一个可以接受OutputStream作为参数的构造器。所以,可以使用那个构造器将System.out转换成PrintWriter:
public class ChangeSystemOut{
public static void main(String[] args){
PrintWriter out = new PrintWriter(System.out, true);
out.println("Hello, world");
}
}
这样包装了之后就可使用PrintWriter的功能了,这里使用了有两个参数的PrintWriter构造器,并将第二个参数设为true,以便开启自动清空功能,不然的话可能看不到输出。
1.3 输出重定向
System.out中的out对象是可以手动指定的。默认会在Java运行环境启动时进行初始化,并且可以在运行时改变其实际对象。我们可以通过setOut方法来将输出重定向,比如下面的例子,将输出重定向到一个文件中:
public class ChangeOut {
public static void main(String args[]) {
try {
System.setOut(new PrintStream(new FileOutputStream("log.txt")));
System.out.println("Now the output is redirected!");
} catch(Exception e) {}
}
}
在有大量输出显示在屏幕并且这些输出滚动得太快以至于无法阅读时,重定向输出就变得极为有用。
1.4 System.out.println的性能分析
众所周知,System.out.println的性能并不好,为什么呢?我们可以看一下其调用顺序:println - > print - > write()+ newLine(),这个是在Sun / Oracle JDK中的实现。其中write()和newLine()方法都包含了一个synchronized块,同步的方式会有一点开销,不过呢更影响性能的则是添加字符到缓冲区和打印。
有文献表明,运行多个System.out.println并记录时间,执行时间会按比例增加。当打印超过50个字符并打印超过50,000行时,性能下降明显。
当然虽然System.out.println()性能不好,但是还是取决我们的使用场景,如果是写写demo学习则直接使用好了,因为是Java原生支持的特性,所以不需要引入任何依赖,这是其最大的好处吧。当然,在我们工作中开发商用软件,那就最好不要用System.out.println了,这就不仅仅是因为性能问题了。
1.5 System.out.println和通用日志组件的对比
为了方便,我们可能常常会直接使用System.out.println()输出日志,但是既然用System.out输出日志这么方便,那又为什么还需要那些通用日志组件(如log4j)呢?System.out.println()又存在什么问题?如下是一些常见的总结:
- 灵活性:像log4j这一类的通用组件提供了多种日志级别,这样就可以通过不同级别相应地分隔日志信息。例如,X消息只能在PRODUCTION级别打印,Y消息应打印在ERROR级别打印等,详细的级别定义这里就不再总结了。
- 可重构性:log4j只需一个参数更改即可关闭所有日志记录。
- 可维护性:想象一下,如果我们有数百个System.out.println散落在应用程序的各个角落,那将会使程序变得难以维护。
- 粒度:在应用程序中,每个类都可以有不同的记录器并相应地进行控制。
- 实用性:在System.out中重定向消息的选项比较少(指向文件、指向程序),但是像log4j之类的组件,其提供了更多的重定向选择,我们甚至可以重定向到自定义的输出选项。
所以呢如果我们只是正在编写一个小demo,只是为了实验/学习目的那么使用System.out.println是很方便的。但是当我们要开发软件时,我们就应该使用通用的日志组件比如log4j等。
2. 标准输入、标准错误
前面我们着重学习了一下标准输出,这里再总结一下它的兄弟:标准输入和标准错误。
标准输入和标准输入刚好相反,是用来从标准输入(一般是键盘)设备获取输入的。而标准错误则是通过PrintStream将错误信息打印到标准错误输出流中,在我们使用比如eclipse这种IDE时就可以看出它和标准输出的区别。看一个简单例子:
public class InOutErr {
public static void main(String args[]) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String filename = reader.readLine();
InputStream input = new FileInputStream(filename);
System.out.println("File opened...");
} catch (IOException e){
System.err.println("Where is that file?");
}
}
}
启动程序之后会阻塞,等待输入文件名称,随意输入,如果找不到对应文件,就会输出错误日志,可以看一下结果,err的打印是红色的。

同样,标准输入和标准错误也可以进行重定向,可以通过System提供的一些静态方法完成重定向:
- setIn(InputStream)
- setOut(PrintStream)
- setErr(PrintStream)
这里是一个简单例子演示这些方法的使用:
public class Redirecting {
public static void main(String[] args) throws IOException{
PrintStream console = System.out;
BufferedInputStream in = new BufferedInputStream(new FileInputStream("pom.xml"));
PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));
System.setIn(in);
System.setOut(out);
System.setErr(out);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s;
while((s = br.readLine()) != null){
System.out.println(s);
}
out.close(); // Remember this!
System.setOut(console);
}
}
这个例子将标准输入重定向到文件上,并将标准输出和标准错误重定向到另一个文件上。注意,它在程序开头处存储了对最初的System.out对象的引用,并且在结尾处将系统输出恢复到了该对象上。
I/O重定向操纵的是字节流,而不是字符流,所以这里使用的是InputStream和OutputStream,而不是Reader和Writer。
3. 标准输入和输出的区别
标准输入和输出除了一个是输入,一个是输出,还有使用上的一些区别。
由于某些历史原因,标准流属于字节流,System.out和System.err其实是PrintStream类型。虽然是字节流,但是PrintStream利用一个内部字符流对象来字符流的许多特性。
相对标准输出而言System.in就只是一个单纯的字节流了,没有包含内部的字符流对象。如果要像字符流一样使用标准输入则需要通过InputStreamReader将其包装一下了:
InputStreamReader cin = new InputStreamReader(System.in);
标准输出和标准输入在这一点上的区别也可从两者的使用上看出来,以我们最常用的在控制台打印一条语句和从控制台接收键盘输入为例:
// 打印日志
System.out.println(""); // 接收标准输入
Scanner scan = new Scanner(System.in);
前者直接使用的是字符流的特性,后者则通过了一个Scanner进行包装。
4. 总结
- Java平台提供了三种标准流,分别是System.in(标准输入)、System.out(标准输出)、System.err(标准错误),我们常用的System.out.println()就是属于标准输出。
- System是java.lang包中的一个final类,out则是System类的一个静态成员,其类型为PrintStream,println()则是PrintStream的一个方法,可以输出内容到控制台。
- 标准流属于字节流,System.out和System.err其实是PrintStream类型,但是具有许多字符流的特性,而System.in就只是一个单纯的字节流。
- 标准流都可以进行重定向。
- System.out.println()性能并不好,但是平时学习使用是不影响的。
- 在项目中尽量不要使用System.out.println()输出日志,而应该使用更通用的日志组件来完成日志打印的任务。
Java I/O系统学习四:标准IO的更多相关文章
- Java I/O系统学习系列二:输入和输出
编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象.“流”屏蔽了实际的I/O设备中处理数据的细节. 在这个系列的第一篇文章:<< ...
- Java I/O系统学习系列一:File和RandomAccessFile
I/O系统即输入/输出系统,对于一门程序语言来说,创建一个好的输入/输出系统并非易事.因为不仅存在各种I/O源端和想要与之通信的接收端(文件.控制台.网络链接等),而且还需要支持多种不同方式的通信(顺 ...
- Java I/O系统学习系列三:I/O流的典型使用方式
尽管可以通过不同的方式组合IO流类,但我们可能也就只用到其中的几种组合.下面的例子可以作为典型的IO用法的基本参考.在这些示例中,异常处理都被简化为将异常传递给控制台,但是这只有在小型示例和工具中才适 ...
- C++系统学习之八:IO库
新的C++标准中有三分之二的内容都是描述标准库.接下来重点学习其中几种核心库设施,这些是应该熟练掌握的. 标准库的核心是很多容器类(顺序容器和关联容器等)和一簇泛型算法(该类算法通常在顺序容器一定范围 ...
- Java I/O系统学习系列五:Java序列化机制
在Java的世界里,创建好对象之后,只要需要,对象是可以长驻内存,但是在程序终止时,所有对象还是会被销毁.这其实很合理,但是即使合理也不一定能满足所有场景,仍然存在着一些情况,需要能够在程序不运行的情 ...
- Scala系统学习(四):Scala变量
变量是保存存储值的内存位置的名称.这意味着当创建变量时,可以在内存中保留一些空间. 根据变量的数据类型,编译器分配内存并决定可以存储在预留内存中的内容.因此,通过为变量分配不同的数据类型,可以在这些变 ...
- Scala系统学习(四):Scala数据类型
Scala与Java具有相同的数据类型,具有相同的内存占用和精度.以下是提供Scala中可用的所有数据类型的详细信息的表格: 序号 数据类型 说明 1 Byte 8位有符号值,范围从-128至127 ...
- Java注解处理器--annotation学习四
Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...
- Redis系统学习 四、超越数据结构
5种数据结构组成了Redis的基础,其他没有关联特定数据结构的命令也有很多.我们已经看过一些这样的命令:info,select,flushdb,multi,exec,discard,watch,和ke ...
随机推荐
- cmd命令行界面运行python脚本显示的中文不正确
在notepad++中编写了一个脚本(如图一),在cmd命令行界面中运行却发现显示的中文不正确(如图2).图3显示的是cmd界面的默认编码. 解决方案:将脚本的注释语言改为GBK,编码格式改为ANSI ...
- python + selenium webdriver 复合型css样式的元素定位方法
<div class="header layout clearfix"></div> 当元素没有id,没有name,没有任何,只有一个class的时候,应该 ...
- 【数据结构】9.java源码关于HashTable
1.hashtable的内部结构 基础存储数据的hash桶由Entry结构的数组存放而entry数据结构,有hash,key和value,还有一个指向下一个节点的引用next对象 这里就和hashma ...
- C# 读取Word内容控件
在Word中,借助内容控件,可设计出具有特定功能的文档或模板.以下表格中简单介绍了几种常用的内容控件. 名称 简介 下拉列表内容控件 下拉列表包含了一个预先定义好的列表.和组合框不同的是下拉列表不允许 ...
- java日志框架笔记-log4j-springboot整合
# 日志框架slf4j log4j logback之间的关系 简答的讲就是slf4j是一系列的日志接口,而log4j logback是具体实现了的日志框架. ```java SLF4J获得logger ...
- Unity之Update与FixedUpdate区别
下面这段代码演示游戏暂停 using UnityEngine; using System.Collections; public class GamePauseTest : MonoBehaviour ...
- 带你了解什么是Push消息推送
前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 如果有看我最近文章的同学可能就知道我在公司负责的是一 ...
- 百度之星初赛A轮 A 度度熊拼三角 贪心
度度熊拼三角 Accepts: 2536 Submissions: 4433 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 6553 ...
- hdu 2050 折线分割平面 dp递推 *
折线分割平面 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Subm ...
- Python学习之旅:使用virtualenv创建Python环境及PyQT5环境配置
一.写在前面 从学 Python 的第一天起,我就知道了使用 pip 命令来安装包,从学习爬虫到学习 Web 开发,安装的库越来越多,从 requests 到 lxml,从 Django 到 Flas ...