Java Io原理
IO流用来处理设备之间的数据传输,Java程序中,对于数据的输入/输出操作 都是以“流”的方式进行的。java.io包下提供了各种“流”类的接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
对于计算机来说,数据都是以二进制形式读出或写入的。我们可以把文件想象为一个桶,我们可以通过管道将桶里的水抽出来。这里的管道也就相当于Java中的流。流的本质是一种有序的数据集合,有数据源和目的地。
1、流的概念和作用
流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象
流的本质: 数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
流的作用:为数据源和目的地建立一个输送通道。
Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。
3、IO流的分类
· 根据处理数据类型的不同分为:字符流和字节流
· 根据数据流向不同分为:输入流和输出流
· 按数据来源(去向)分类:
1、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
2、byte[]:ByteArrayInputStream, ByteArrayOutputStream
3、Char[]: CharArrayReader,CharArrayWriter
4、String:StringBufferInputStream, StringReader, StringWriter
5、网络数据流:InputStream,OutputStream, Reader, Writer
java中io常用的类:
流的分类
Java I/O主要包括如下几个层次,包含三个部分:
1.流式部分 ――IO的主体部分;
2.非流式部分 ――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;
3.其他类 --文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
主要的类如下:
-
File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
-
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
-
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特
-
Reader(文件格式操作):抽象类,基于字符的输入操作。
-
Writer(文件格式操作):抽象类,基于字符的输出操作。
-
RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
-
Java中IO流的体系结构如图:
Java流类的类结构图:
字节流的继承关系
字符流之间的继承关系
2.字符流和字节流
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
- 字节流:数据流中最小的数据单元是字节
- 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
字节流的继承关系
字符流之间的继承关系
字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
输入流和输出流
根据数据的输入、输出方向的不同对而将流分为输入流和输出流。
1) 输入流
程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
2) 输出流
程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。
采用数据流的目的就是使得输出输入独立于设备。
输入流( Input Stream )不关心数据源来自何种设备(键盘,文件,网络)。
输出流( Output Stream )不关心数据的目的是何种设备(键盘,文件,网络)。
3)特性
相对于程序来说,输出流是往存储介质或数据通道写入数据,而输入流是从存储介质或数据通道中读取数据,一般来说关于流的特性有下面几点:
1、先进先出,最先写入输出流的数据最先被输入流读取到。
2、顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile可以从文件的任意位置进行存取(输入输出)操作)
3、只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
3、Java IO流对象
1.输入字节流InputStream
IO 中输入字节流的继承图可见上图,可以看出:
-
InputStream是所有的输入字节流的父类,它是一个抽象类。
-
ByteArrayInputStream、StringBufferInputStream(上图的StreamBufferInputStream)、FileInputStream是三种基本的介质流,它们分别从Byte数组、StringBuffer、和本地文件中读取数据。
-
PipedInputStream是从与其它线程共用的管道中读取数据.
-
ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。
InputStream中的三个基本的读方法
abstract int read() :读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。
intread(byte[]?b) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
intread(byte[]?b, int?off, int?len) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。
流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。
其它方法
long skip(long?n):在输入流中跳过n个字节,并返回实际跳过的字节数。
int available() :返回在不发生阻塞的情况下,可读取的字节数。
void close() :关闭输入流,释放和这个流相关的系统资源。
voidmark(int?readlimit) :在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit设置的值,则流忽略这个标记。
void reset() :返回到上一个标记。
booleanmarkSupported() :测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false。
2.输出字节流OutputStream
IO 中输出字节流的继承图可见上图,可以看出:
-
OutputStream是所有的输出字节流的父类,它是一个抽象类。
-
ByteArrayOutputStream、FileOutputStream是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据。
-
ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。
outputStream中的三个基本的方法
abstract void write(int?b):往输出流中写入一个字节。
void write(byte[]?b) :往输出流中写入数组b中的所有字节。
void write(byte[]?b, int?off, int?len) :往输出流中写入数组b中从偏移量off开始的len个字节的数据。
其它方法
void flush() :刷新输出流,强制缓冲区中的输出字节被写出。
void close() :关闭输出流,释放和这个流相关的系统资源。
3.字符输入流Reader
在上面的继承关系图中可以看出:
-
Reader是所有的输入字符流的父类,它是一个抽象类。
-
CharReader、StringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。
-
BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。
-
FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。
-
InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。Reader中各个类的用途和使用方法基本和InputStream中的类使用一致。后面会有Reader与InputStream的对应关系。
主要方法:
public int read() throws IOException; //读取一个字符,返回值为读取的字符
public int read(char cbuf[]) throws IOException; //读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
public abstract int read(char cbuf[],int off,int len) throws IOException; //读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现
4.字符输出流Writer
在上面的关系图中可以看出:
-
Writer是所有的输出字符流的父类,它是一个抽象类。
-
CharArrayWriter、StringWriter是两种基本的介质流,它们分别向Char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据,
-
BufferedWriter是一个装饰器为Writer提供缓冲功能。
-
PrintWriter和PrintStream极其类似,功能和使用也非常相似。
-
OutputStreamWriter是OutputStream到Writer转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream极其类似.
主要方法:
(1) public void write(int c) throws IOException; //将整型值c的低16位写入输出流
(2) public void write(char cbuf[]) throws IOException; //将字符数组cbuf[]写入输出流
(3) public abstract void write(char cbuf[],int off,int len) throws IOException; //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流
(4) public void write(String str) throws IOException; //将字符串str中的字符写入输出流
(5) public void write(String str,int off,int len) throws IOException; //将字符串str 中从索引off开始处的len个字符写入输出流
5.字节流的输入与输出的对应
蓝色的为主要的对应部分,红色的部分就是不对应部分。从上面的图中可以看出JavaIO中的字节流是极其对称的。我们看看这些字节流中不太对称的几个类:
-
LineNumberInputStream主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。
-
PushbackInputStream的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream几乎实现相近的功能。
-
StringBufferInputStream已经被Deprecated,本身就不应该出现在InputStream部分,主要因为String应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。
-
SequenceInputStream可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO包中去除,还完全不影响IO包的结构,却让其更“纯洁”――纯洁的Decorator模式。
-
PrintStream也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO包!System.out和System.out就是PrintStream的实例!
6.字符流与字节流转换
转换流的特点:
-
其是字符流和字节流之间的桥梁
-
可对读取到的字节数据经过指定编码转换成字符
-
可对读取到的字符数据经过指定编码转换成字节
何时使用转换流?
-
当字节和字符之间有转换动作时;
-
流操作的数据需要编码或解码时。
具体的对象体现:
转换流:在IO中还存在一类是转换流,将字节流转换为字符流,同时可以将字符流转化为字节流。
-
InputStreamReader:字节到字符的桥梁
-
OutputStreamWriter:字符到字节的桥梁
OutputStreamWriter(OutStreamout):将字节流以字符流输出。
InputStreamReader(InputStream in):将字节流以字符流输入。
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
7.字节流和字符流的区别**(重点)**
实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操作文件
如图所示:
节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法。
读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
8.非流式文件类–File类
从定义看,File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。
File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,以下是文件操作常用的函数。
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
File类共提供了三个不同的构造函数,以不同的参数形式灵活地接收文件和目录名信息。
构造函数:
1)File (String pathname)
例:File f1=new File(“FileTest1.txt”); //创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
2)File (String parent , String child)
例:File f2=new File(“D:\dir1",“FileTest2.txt”) ;// 注意:D:\dir1目录事先必须存在,否则异常
3)File (File parent , String child)
例:File f4=new File("\dir3");
File f5=new File(f4,“FileTest5.txt”); //在如果 \dir3目录不存在使用f4.mkdir()先创建
一个对应于某磁盘文件或目录的File对象一经创建, 就可以通过调用它的方法来获得文件或目录的属性。
1)public boolean exists( ) 判断文件或目录是否存在
2)public boolean isFile( ) 判断是文件还是目录
3)public boolean isDirectory( ) 判断是文件还是目录
4)public String getName( ) 返回文件名或目录名
5)public String getPath( ) 返回文件或目录的路径。
6)public long length( ) 获取文件的长度
7)public String[ ] list ( ) 将目录中所有文件名保存在字符串数组中返回。
File类中还定义了一些对文件或目录进行管理、操作的方法,常用的方法有:
1) public boolean renameTo( File newFile ); 重命名文件
2) public void delete( ); 删除文件
3) public boolean mkdir( ); 创建目录
public class FileDemo1 {
public static void main(String[] args) {
File file = new File("D:" + File.separator + "test.txt");
if (file.exists()) {
file.delete();
} else {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
9.RandomAccessFile类
该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。该对象特点:
-
该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
-
该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
10、其它常用流
序列流
也称为合并流。 SequenceInputStream ,对多个流进行合并。
序列流特点:流对象的有序的排列。
序列流解决问题:将多个输入流合并成一个输入流。将多个源合并成一个源。对于多个源的操作会变的简单。
序列流功能:特殊之处在构造函数上。一初始化就合并了多个流进来。
使用场景:对多个文件进行数据的合并。多个源对应一个目的。
/*序列流:将多个流进行逻辑串联(合并成为一个流,使操作变得更加方便,因为多个源变成了一个源)
*SequenceInputStream sis=new SequenceInputStream(InputStream s1, InputStream s2) ;
*当要合并三个或三个以上的流时,需要使用枚举作为参数
*SequenceInputStream sis=new SequenceInputStream(Enumeration<? extends InputStream> e);
*/
@Test//把三个文件中的数据写入到一个文件中
public void sequenceInputStreamDemo() throws IOException{
InputStream is1=new FileInputStream("1.txt");
InputStream is2=new FileInputStream("2.txt");
InputStream is3=new FileInputStream("3.txt");
Collection<InputStream> col=new ArrayList<InputStream>();
col.add(is1);
col.add(is2);
col.add(is3);
//import java.util.Collections;这个包中有很多操作集合的方法
Enumeration<InputStream> e = Collections.enumeration(col);
//把is1,is2,is3三个流合并成为了一个流sis
SequenceInputStream sis=new SequenceInputStream(e);
OutputStream is=new FileOutputStream("seq.txt");
int len=0;
byte b[]=new byte[4];
while((len=sis.read(b))!=-1){ //先把合并流中的数据读取到字节数组b中
is.write(b,0,len); //在把字节数组中的数据写入到seq文件夹
}
}
序列化和反序列化(重点)
序 列 化: 指把堆内存中的Java对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络的节点(在网络上传输).我们把这个过程称之为序列化.
反序列化: 把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象的过程.
为什么要做序列化:
1):在分布式系统中,需要共享的数据的JavaBean对象,都得做序列化,此时需要把对象再网络上传输,此时就得把对象数据转换为二进制形式.以后存储在HttpSession中的对象,都应该实现序列化接口(只有实现序列化接口的类,才能做序列化操作).
2):服务钝化:如果服务发现某些对象好久都没有活动了,此时服务器就会把这些内存中的对象,持久化在本地磁盘文件中(Java对象–>二进制文件).如果某些对象需要活动的时候,现在内存中去寻找,找到就使用,找不到再去磁盘文件中,反序列化我们得对象数据,恢复成Java对象.
需要做序列化的对象的类,必须实现序列化接口:java.io.Serializable接口 (标志接口[没有抽象方法]).底层会判断,如果当前对象是Serializable的实例,才允许做序列化. boolean ret = Java对象 instanceof Serializable;
在Java中大多数类都已经实现Serializable接口.
1.使用对象流来完成序列化操作
ObjectOutputStream: 通过writeObject方法做序列化操作的.
ObjectInputStream: 通过readObject方法做反序列化操作的.
做反序列化操作必须存在对象的字节码对象.
下面我们来对一个User对象做序列化和反序列化:
public class User{
private int id;
private String nameString;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, String nameString, int age) {
super();
this.id = id;
this.nameString = nameString;
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", nameString=" + nameString + ", age=" + age
+ "]";
}
}
Test:
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class IODemo7 {
public static void main(String[] args) throws Exception {
writeObject();
}
//使用对象流对User对象进行序列化操作
private static void writeObject() throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
out.writeObject(new User(1,"张三",18));
out.close();
}
}
注意!!!!!!此时会报错,为什么? 这是因为我们自己定义的User类还没有实现序列化接口
这时我们就应该修改User类,让它实现Serializable接口
代码如下:
import java.io.Serializable;
public class User implements Serializable{
private int id;
private String nameString;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, String nameString, int age) {
super();
this.id = id;
this.nameString = nameString;
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", nameString=" + nameString + ", age=" + age
+ "]";
}
}
这时运行就能成功将User对象写入到文件中,然后我们打开肯定看不懂的(是一堆字节码),需要对象流输入流来进行反序列化操作
2.下面我们进行反序列化
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class IODemo7 {
public static void main(String[] args) throws Exception {
writeObject();//序列化
readObject();//反序列化
}
//使用对象流对User对象进行序列化操作
private static void writeObject() throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
out.writeObject(new User(1,"张三",18));
out.close();
}
//使用对象流对User对象进行反序列化操作
private static void readObject() throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.txt"));
User user = (User)in.readObject();
System.out.println(user);//User [id=1, nameString=张三, age=18]
}
}
打印结果就是: User [id=1, nameString=张三, age=18]
序列化细节:序列化的版本
1):如果某些数据不需要做序列化,比如密码,此时怎么办?
理论上说,静态的字段和瞬态的字段是不能做序列化操作的.
下面我们的User类添加一个password字段,然后在字段前面加上一个修饰符transient,看代码:
import java.io.Serializable;
public class User implements Serializable{
private int id;
transient private int password;//加上了transient修饰符
private String nameString;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void setPassword(int password) {
this.password = password;
}
public int getPassword() {
return password;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, int password, String nameString, int age) {
super();
this.id = id;
this.password = password;
this.nameString = nameString;
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", password=" + password + ", nameString="
+ nameString + ", age=" + age + "]";
}
}
这时候的password就不会被序列化,或者说该属性不会进行持久化,也就是该值不会保存
2):序列化的版本问题:
反序列化Java对象时必须提供该对象的class文件,现在问题是,随着项目的升级,系统的class文件也会升级(增加一个字段/删除一个字段),如何保证两个class文件的兼容性? Java通过serialVersionUID(序列化版本号)来判断字节码是否发生改变.如果不显示定义serialVersionUID类变量,该类变量的值由JVM根据类相关信息计算,而修改后的类的计算方式和之前往往不同.从而造成了对象反序列化因为版本不兼容而失败的问题.
此时我们需要给我们的User加上一个序列化ID就可以了
那么以后我们无论添加或者修改字段之后都不会在报错,要保证序列化ID相同
总结: 其实在开发中,我们只需要对我们自己定义的类加上序列化ID就行了,至于序列化和反序列化这个对象,框架会帮我们完成
注意:瞬态关键字(transient)
该关键字是用来修饰属性,一旦某个属性被这个关键字修饰,则该属性不会进行持久化,也就是该值不会保存!!!
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰。只要被transient修饰了,序列化时这个属性就不会被序列化了。同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化。
11、System类对IO的支持
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:
System.in(标准输入),通常代表键盘输入。
System.out(标准输出):通常写往显示器。
System.err(标准错误输出):通常写往显示器。
标准I/O
Java程序可通过命令行参数与外界进行简短的信息交换,同时,也规定了与标准输入、输出设备,如键盘、显示器进行信息交换的方式。而通过文件可以与外界进行任意数据形式的信息交换。
注意:
(1)System类不能创建对象,只能直接使用它的三个静态成员。
(2)每当main方法被执行时,就自动生成上述三个对象。
-
标准输出流 System.out
System.out向标准输出设备输出数据,其数据类型为PrintStream。方法:
Void print(参数)
Void println(参数)
2)标准输入流 System.in
System.in读取标准输入设备数据(从标准输入获取数据,一般是键盘),其数 据类型为InputStream。方法:
int read() //返回ASCII码。若,返回值=-1,说明没有读取到任何字节读取工作结束。
int read(byte[] b)//读入多个字节到缓冲区b中返回值是读入的字节数
例如:
import java.io.*;
public class StandardInputOutput {
public static void main(String args[]) {
int b;
try {
System.out.println("please Input:");
while ((b = System.in.read()) != -1) {
System.out.print((char) b);
}
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
等待键盘输入,键盘输入什么,就打印出什么:
3)标准错误流
System.err输出标准错误,其数据类型为PrintStream。可查阅API获得详细说明。
标准输出通过System.out调用println方法输出参数并换行,而print方法输出参数但不换行。println或print方法都通 过重载实现了输出基本数据类型的多个方法,包括输出参数类型为boolean、char、int、long、float和double。同时,也重载实现 了输出参数类型为char[]、String和Object的方法。其中,print(Object)和println(Object)方法在运行时将调 用参数Object的toString方法。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class StandardInputOutput {
public static void main(String args[]) {
String s;
// 创建缓冲区阅读器从键盘逐行读入数据
InputStreamReader ir = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(ir);
System.out.println("Unix系统: ctrl-d 或 ctrl-c 退出"
+ "\nWindows系统: ctrl-z 退出");
try {
// 读一行数据,并标准输出至显示器
s = in.readLine();
// readLine()方法运行时若发生I/O错误,将抛出IOException异常
while (s != null) {
System.out.println("Read: " + s);
s = in.readLine();
}
// 关闭缓冲阅读器
in.close();
} catch (IOException e) { // Catch any IO exceptions.
e.printStackTrace();
}
}
}
在Java语言中使用字节流和字符流的步骤基本相同,以输入流为例,首先创建一个与数据源相关的流对象,然后利用流对象的方法从流输入数据,最后执行close()方法关闭流。