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 ...
随机推荐
- Mongodb最基础入门教程
Mongodb最基础入门教程 如果想了解一下redis的入门教程,可以去看一下我的上一篇博客 Mongodb的安装大家可以参考一下其他博主的博客,这里我就不做介绍了.不过值得注意的是,在Linux ...
- .Net Core 三大Redis客户端对比和使用心得
前言 稍微复杂一点的互联网项目,技术选型都可能会涉及Redis,.NetCore的生态越发完善,支持.NetCore的Redis客户端越来越多, 下面三款常见的Redis客户端,相信大家平时或多或少用 ...
- Selenium3 + Python3自动化测试系列九——cookie操作
cookie操作 一.Cookie操作 WebDriver提供了操作Cookie的相关方法,可以读取.添加和删除cookie信息. 使用方法: 1:get_cookies() ,获取cookie信息 ...
- Python——常用模块(time/datetime, random, os, shutil, json/pickcle, collections, hashlib/hmac, contextlib)
1.time/datetime 这两个模块是与时间相关的模块,Python中通常用三种方式表示时间: #时间戳(timestamp):表示的是从1970年1月1日00:00:00开始按秒计算的偏移量. ...
- Spring中老生常谈的FactoryBean
本文完整代码地址:https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/factorybean Factory ...
- Super和This总结
this: this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针. Java关键字this只能用于方法体内.当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自 ...
- 微信小程序那些令人眼泪汪汪的坑儿
前言 最近做了一个麻雀虽小,五脏俱全的微信小程序项目.一看就会,一用就废的小程序.有些坑真的坑的你两眼泪汪汪.我就爱干前人栽树后人乘凉的事儿,看到文章的你,也许是同道中人,相视一笑:亦或是小程序外围人 ...
- P3469 [POI2008]BLO-Blockade 割点 tarjan
题意 给定一个无向图,问删掉点i,图中相连的有序对数.(pair<x, y> , x != y);求每个点对应的答案 思路 首先我们可以发现,如果这个点不是割点,那么答案就是n-1,如果是 ...
- HDU 6394 Tree 分块 || lct
Tree 题意: 给你一颗树, 每一个节点都有一个权值, 如果一个石头落在某个节点上, 他就会往上跳这个的点的权值步. 现在有2种操作, 1 把一个石头放在 x 的位置 询问有跳几次才跳出这棵树, 2 ...
- lightoj 1037 - Agent 47(状压dp)
题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1037 #include <iostream> #include & ...