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 ...
随机推荐
- 重学计算机网络(二) - 曾记否,查IP地址
先献上几个梗 1.1.1.1 不是测试用的,原来一直没分配,现在被用来做一个DNS了,宣传是比谷歌等公司的dns服务 更保护用户隐私. IP地址255.255.255.255,代表有限广播,它的目标是 ...
- ASP.NET Core on K8S深入学习(7)Dashboard知多少
本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 在第二篇<部署过程解析与Dashboard>中介绍了如何部署Das ...
- 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- Vim高手,从来不用鼠标
Vim脱离鼠标第一步 平时不可缺少的会用到vim,但是避免不了鼠标,事实上,省略鼠标是完全可以的,没有想像中那么难,看我短短几行带大家一起省略鼠标. 对了,vim有三种模式,基本模式就是用来输入命令的 ...
- Redis学习总结(一)--Redis入门
Redis 概念 1.Redis 是什么 Redis 是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理. 2.Redis 能干什么 Redis 支持字符串.哈希 ...
- 学习js都学习什么?
前言:js(javaScript)是面向对象(OOP)的编程语言,目前不仅仅是客户端语言了,基予node可以做服务器端程序,那我们学习js都学习什么? 学习js,我们学习它的几部分组成 1.ECMAS ...
- 章节十六、5-TestNG高级功能--Part2
一.测试用例的依赖关系--->(dependsOnMethods = {"依赖方法名"}) 1.在实现自动化的过程中,有些测试用例必须在其它测试用例执行之后才能运行,两者之间 ...
- Codeforces 985E
题意略. 思路: 这个题目开始想的有点暴力,后来发现有搜索的性质,因此转而用动态规划.首先,我们要把这些数排个序. 定义状态:dp[i]为排序后i~n能否成功打包,1表示可以,0表示不能打包. 状态转 ...
- MySQL 5.7 的安装历程
mysql5.7零基础入门级的安装教程: 安装环境:Windows 10, 64 位(联想拯救者R720) 安装版本:mysql-5.7.25-winx64 一.下载 1.进入官网 首先,下载MySQ ...
- 安装Python及各种包/库——没有网络的电脑上
我们做项目时可能会遇到,一些电脑只能联内网或者无法联网,这种情况怎样在电脑上安装Python及各种第三方包/库呢? 1.首先,在有网络的电脑上在python官网下载好python安装包,地址:http ...