Java中的IO流
1、IO流分类
在 IO 包中,字节流的输人输出流分别用 java.io.InputStream
和 java.io.OutputStream
表示,字符流的输
人输出流分别用 java.io.Reader
和 java.io.Writer
表示。
2、字节流
IO 流中针对字节的输人输出提供了一系列的流,统称为字节流。根据数据的传输方向可将其分为字节输入流和字
节输出流。在 JDK 中,提供了两个抽象类 InputStream
和 OutputStream
,它们是字节流的顶级父类。
所有的字节输人流都继承自 InputStream
,所有的字节输出流都继 OutputStream
。
InputStream
被看成一个输人管道,OutputStream
被看成一个输出管道,数据通过 InputStream
从源设备输
人到程序,通过 OutputStream
从程序输出到目标设备,从而实现数据的传输。
在 JDK 中,InputStream 和 OutputStream 提供了一系列与读写数据相关的方法。
InputStream
提供的读数据的相关方法:
-
int read()
:从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数。 -
int read(byte[] b)
:从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节数。
-
int read(byte[] b,int off,int len)
:从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始下标,len表示读取的字节数目。
-
void close()
:关闭此输入流并释放与该流关联的所有系统资源。
OutputStream
提供的写数据的相关方法:
-
void write(int b)
:向输出流写入一个字节 -
void write(byte[] b)
:把参数b指定的字节数组的所有字节写到输出流。 -
void write(byte[] b,int off,int len)
:将指定byte数组中从偏移量off开始的len个字节写入输出流。
-
void flush()
:刷新此输出流并强制写出所有缓冲的输出字节。 -
void close()
:关闭此输出流并释放与此流相关的所有系统资源。
InputStream
和 OutputStream
提供了不同的子类,这些子类形成了一个体系结构:
2.1 字节流读写文件
在操作文件时,最常见的就是从文件中读取数据并将数据写人文件,即文件的读写。针对文件的读写,JDK专门提
供了两个类,分别是 FileInputStream
和 FileOutputStream
。FileInputStream
是 InputStream
的子类,
它是操作文件的字节输人流,专门用于读取文件中的数据。由于从文件读取数据是重复的操作,因此需要通过循环
语句来实现数据的持续读取。
package com.example;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @author tom
*/
public class Example01 {
public static void main(String[] args) throws IOException {
// test.txt内容为hello
FileInputStream in = new FileInputStream("test.txt");
// 定义一个int类型的变量b,记住每次读取的一个字节
int b;
while (true) {
// 变量b记住读取的一个字节
b = in.read();
// 如果读取的字节为-1,跳出while循环
if (b == -1) {
break;
}
System.out.println(b);
}
in.close();
}
}
# 输出
104
101
108
108
111
与 FileInputStream 对应的是 FileOutputStream。FileOutputStream 是 OutputStream 的子类,它是操作文件
的字节输出流,专门用于把数据写人文件。
package com.example;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author tom
*/
public class Example02 {
public static void main(String[] args) throws IOException {
FileOutputStream out = new FileOutputStream("example.txt");
String str = "你好";
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);
}
out.close();
}
}
# example.txt文件写入你好
package com.example;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author tom
*/
public class Example03 {
public static void main(String[] args) throws IOException {
// true代表是否追加
FileOutputStream out = new FileOutputStream("example.txt", true);
String str = "你好";
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);
}
out.close();
}
}
# example.txt文件写入你好你好
从前面的例程中可以看出,IO 流在进行数据读写操作时会出现异常,为了代码的简洁,在程序中使用 throws 关
键字将异常抛出。然而一旦遇到 IO 异常,IO 流的 close() 方法将无法得到执行,流对象所占用的系统资源将得不
到释放,因此,为了保证 IO 流的 close() 方法必须执行,通常将关闭流的操作写在 finally 代码块。
finally {
try {
//如果in不为空,关闭输入流
if (in != null) in.close();
} catch (Exception e){
e.printstackTrace();
}
try {
//如果out不为空,关闭输出流
if (out != null) out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
2.2 文件的拷贝
在应用程序中,IO 流通常都是成对出现的,即输入流和输出流一起使用。例如文件的拷贝就需要通过输入流来读
取文件中的数据,通过输出流将数据写入文件。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example04 {
public static void main(String[] args) throws IOException {
// 创建一个字节输入流,用于读取当前目录下的mp3文件
InputStream in = new FileInputStream("Bad Romance.mp3");
// 创建一个文件字节输出流,用于将读取的数据写人文件中
OutputStream out = new FileOutputStream("style.mp3");
int len;
// 定义一个int类型的变量len,记住每次读取的一个字节
// 获取拷贝文件前的系统时间
long beginTime = System.currentTimeMillis();
while ((len = in.read()) != -1) {
// 读取一个字节并判断是否读到文件末尾
out.write(len);
// 将读到的字节写人文件
}
long endTime = System.currentTimeMillis();
// 获取文件拷贝结束时的系统时间
System.out.println("拷贝文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
in.close();
out.close();
}
}
# 输出
拷贝文件所消耗的时间是:13765毫秒
2.3 字节流的缓冲区
当通过流的方式拷贝文件时,为了提高效率也可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取
多个字节的数据,并保存在字节数组中然后将字节数组中的数据一次性写入文件。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example05 {
public static void main(String[] args) throws IOException {
// 创建一个字节输入流,用于读取当前目录下的mp3文件
InputStream in = new FileInputStream("Bad Romance.mp3");
// 创建一个文件字节输出流,用于将读取的数据写人文件中
OutputStream out = new FileOutputStream("style.mp3");
byte[] buff = new byte[1024];
int len;
// 定义一个int类型的变量len,记住每次读取的一个字节
// 获取拷贝文件前的系统时间
long beginTime = System.currentTimeMillis();
while ((len = in.read(buff)) != -1) {
// 读取一个字节并判断是否读到文件末尾
out.write(buff,0,len);
// 将读到的字节写人文件
}
long endTime = System.currentTimeMillis();
// 获取文件拷贝结束时的系统时间
System.out.println("拷贝文件所消耗的时间是:" + (endTime - beginTime) + "毫秒");
in.close();
out.close();
}
}
# 输出
拷贝文件所消耗的时间是:23毫秒
2.4 字节缓冲流(装饰器模式)
在 IO 包中提供两个带缓冲的字节流,分别是 BufferedlnputStream
和 BufferdOutputStream
,这两个流都使
用了装饰设计模式。它们的构造方法中分别接收 InputStream
和 OutputStream
类型的参数作为被包装对象,
在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图所示。
从图中可以看出应用程序是通过缓冲流来完成数据读写的,而缓冲流又是通过底层被包装的字节流与设备进行关联
的。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example06 {
public static void main(String[] args) throws IOException {
// 创建一个带缓冲区的输入流
// src.txt文件的内容为hello world!
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.txt"));
// 创建一个带缓冲区的输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("des.txt"));
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bis.close();
bos.close();
}
}
# des.txt文件写入hello world!
创建了 BufferedInputStream
和 BufferedOutputStream
两个缓冲流对象,这两个流内部都定义了一个大小为
8192
的字节数组,当调用 read()
或者 write()
方法读写数据时,首先将读写的数据存入定义好的字节数组,
然后将字节数组的数据一次性读写到文件中。
3、字符流
InputStream
类和 OutputStream
类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个
类就不太方便,为此 JDK 提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是 Reader
和
Writer
。其中 Reader
是字符输入流,用于从某个源设备读取字符,Writer
是字符输出流,用于向某个目标设
备写入字符。Reader
和 Writer
作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出 Reader
和 Writer
的一些常用子类:
字符流的继承关系与字节流的继承关系有些类似,很多子类都是成对(输入流和输出流)出现,其中 FileReader
和
FileWriter
用于读写文件,BufferedReader
和 BufferedWriter
是具有缓冲功能的流,它们可以提高读写效
率。
3.1 字符流操作文件
在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符便可以使用字符输人流
FileReader
,通过此流可以从关联的文件中读取一个或一组字符。
package com.example;
import java.io.FileReader;
/**
* @author tom
*/
public class Example07 {
public static void main(String[] args) throws Exception {
// 创建一个FileReader对象用来读取文件中的字符
// reader.txt文件的内容为hello world!
FileReader reader = new FileReader("reader.txt");
int ch;
// 定义一个变量用于记录读取的字符
while ((ch = reader.read()) != -1) {
// 循环判断是否读取到文件的末尾
System.out.println((char) ch);
// 不是字符流末尾就转为字符打印
}
reader.close();
// 关闭文件读流,释放资源
}
}
# 程序输出
h
e
l
l
o
w
o
r
l
d
!
字符输人流的 read()
方法返回的是int类型的值,如果想获得字符就需要进行强制类型转换。
如果要向文件中写人字符就需要使用 FileWriter
,类 FileWriter
是 Writer
的一个子类。
package com.example;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author tom
*/
public class Example08 {
public static void main(String[] args) throws IOException {
//创建一个FileWriter对象用于向文件中写人数据
FileWriter writer = new FileWriter("writer.txt");
// 追加
// FileWriter writer=new FileWriter("writer.txt",true);
String str = "hello world!";
writer.write(str);
//将字符数据写人到文本文件中
writer.write("\r\n");
//将输出语句换行
writer.close();
//关闭写人流,释放资源
}
}
# writer.txt文件写入hello world!
3.2 字符流缓冲流
了解到包装流可以对一个已存在的流进行包装来实现数据读写功能,利用包装流可以有效地提高读写数据的效率。
字符流同样提供了带缓冲区的包装流,分别是 BufferedReader
和 BufferedWriter
,其中 BufferedReader
用于对字符输人流进行包装,BufferedWriter
用于对字符输出流进行包装,需要注意的是,在
BufferedReader
中有一个重要的方法 readLine()
,该方法用于一次读取一行文本。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example09 {
public static void main(String[] args) throws IOException {
// src.txt文件内容为hello world!
FileReader reader = new FileReader("src.txt");
// 创建一个BufferedReader缓冲对象
BufferedReader br = new BufferedReader(reader);
FileWriter writer = new FileWriter("des.txt");
// 创建一个 BufferedWriter缓冲对象
BufferedWriter bw = new BufferedWriter(writer);
String str;
// 每次读取一行文本,判断是否到文件末尾
while ((str = br.readLine()) != null) {
bw.write(str);
// 写入一个换行符,该方法会根据同的操作系统生成相应的换行符
bw.newLine();
}
br.close();
bw.close();
}
}
# des.txt文件写入hello world!
其中 readLine()
方法会逐个读取字符,当读到回车 \r
或换行 \n
时会将读到的字符作为一行的内容返回。需
要注意的是,由于包装流内部使用了缓冲区,在循环中调用 BufferedWriter
的 write()
方法写字符时,这些
字符首先会被写人缓冲区,当缓冲区写满时或调用 close()
方法时,缓冲区中的字符才会被写人目标文件。因此
在循环结束时一定要调用 close()
方法,否则极有可能会导致部分存在缓冲区中的数据没有被写人目标文件。
3.3 LineNumberReader
Java 程序在编译或运行期间经常会出现一些错误,在错误中通常会报告出错的行号,为了方便查找错误,需要在
代码中加人行号。JDK提供了一个可以跟踪行号的输入流— LineNumberReader
,它是 BufferedReader
的直接
子类。
package com.example;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
/**
* @author tom
*/
public class Example10 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("example.go");
// 创建字符输入流
FileWriter fw = new FileWriter("example-line.go");
// 创建字符输出流
LineNumberReader lr = new LineNumberReader(fr);
// 包装
lr.setLineNumber(0);
// 设置读取文件的起始行号
String line;
while ((line = lr.readLine()) != null) {
// 将行号写入到文件中
fw.write(lr.getLineNumber() + ":" + line);
fw.write("\r\n");
// 写人换行
}
lr.close();
fw.close();
}
}
# example-line.go文件写入
1:package main
2:
3:import (
4: "fmt"
5:)
6:
7:func main() {
8: fmt.Println("Hello,World!")
9:}
3.4 转换流
前面提到IO流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在JDK中提供了两个类可以将字
节流转换为字符流,它们分别是 InputStreamReader
和 OutputStreamWriter
。转换流也是一种包装流,其中
OutputStreamWriter
是 Writer
的子类,它可以将一个字节输出流包装成字符输出流,方便直接写入字符,而
InputStreamReader
是 Reader
的子类,它可以将一个字节输人流包装成字符输人流,方便直接读取字符。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example11 {
public static void main(String[] args) throws IOException {
//创建字节输入流
// src.txt文件内容hello world!
FileInputStream in = new FileInputStream("src.txt");
InputStreamReader isr = new InputStreamReader(in);
//将字节流输入转换成字符输人流
BufferedReader br = new BufferedReader(isr);
//对字符流对象进行包装
FileOutputStream out = new FileOutputStream("des.txt");
//将字节输出流转换成字符输出流
OutputStreamWriter osw = new OutputStreamWriter(out);
// 对字符输出流对象进行包装
BufferedWriter bw = new BufferedWriter(osw);
String line;
while ((line = br.readLine()) != null) {
// 判断是否读到文件末尾
bw.write(line);
// 输出读取到的文件
}
br.close();
bw.close();
}
}
# des.txt文件写入hello world!
字节流和字符流之间的转换,将字节流转换为字符流,从而实现直接对字符的读写。需要注意的是,在使用转换流
时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是一张图片,此时转换为字符流就会造成数据丢
失。
4、其它IO
4.1 ObjectlnputStream和ObjectOutputStream
程序运行时,会在内存中创建多个对象,然而程序结束后,这些对象便被当作垃圾回收了。如果希望永久保存这些
对象,则可以将对象转为字节数据写人到硬盘上,这个过程称为对象序列化。为此JDK中提供了
ObjectOutputStream
(对象输出流)来实现对象的序列化。当对象进行序列化时,必须保证该对象实现
Serializable
接口,否则程序会出现 NotSerializableException
异常。
package com.example;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @author tom
*/
public class Example12 {
public static void main(String[] args) throws IOException {
// 创建一个Person对象
Person p = new Person("p1", "zhangsan", 20);
// 创建文件输出流对象,将数据写入objectStream.txt文件中
FileOutputStream fos = new FileOutputStream("objectStream.txt");
// 创建对象输出流对象,用于处理输出流对象写人的数据
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 将Person对象输出到输出流中
oos.writeObject(p);
oos.close();
fos.close();
}
}
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
首先将 Person 对象进行实例化,然后通过调用 ObjectOutputStream
的 writeObject(Object obj)
方法将
Person 对象写人 objectStream.txt
文件中,从而将 Person 对象的数据永久地保存在文件中,这个过程就是对
象的序列化。当程序运行结束后,会发现在当前目录下自动生成了一个 objectStream.txt
文件,该文件中便记
录了Person对象的数据。
Person 对象被序列化后会生成二进制数据保存在 objectStream.txt
文件中,通过这些二进制数据可以恢复序
列化之前的Java对象,此过程称为反序列化。JDK提供了 ObjectInputStream
类(对象输人流),它可以实现对象
的反序列化。
package com.example;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @author tom
*/
public class Example13 {
public static void main(String[] args) throws IOException,ClassNotFoundException {
// 创建文件输入流对象,用于读取指定文件的数据
FileInputStream fis=new FileInputStream("objectStream.txt");
// 创建对象输入流,并且从指定的输入流中读取数据
ObjectInputStream ois=new ObjectInputStream(fis);
// 从objectStream.txt中读取Person对象
Person p=(Person)ois.readObject();
System.out.println(p.getId() + "-" + p.getAge() + "-" + p.getName());
}
}
# 输出
p1-20-zhangsan
通过调用 ObjectInputStream
的 readObject()
方法将文件 objectStream.txt
的 Person对象读取出来,这
个过程就是反序列化。
4.2 DatalnputStream和DataOutputStream
通过上一个小节的学习,了解到如何通过IO流存储对象,但有的时候,并不需要存储整个对象的信息,而只需要
存储对象的成员数据,这些成员数据的类型又都是基本数据类型,这时,不必使用对象Object相关的流,,可以
使用IO包中提供的另外两个数据操作流,即 DataInputStream
和 DataOutputStream
。DataInputStream
和
DataOutputStream
是两个与平台无关的数据操作流,它们不仅提供了读写各种基本类型数据的方法,而且还提
供了 readUTF()
和 writeUTF()
方法。
DataInputStream
的 readUTF()
方法能够从输人流中读取采用UTF-8字符编码的字符串,DataOutputStream
的 writeUTF()
方法则可向输出流中写人采用UTF-8字符编码的字符串。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example14 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("dataStream.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(12);
// 写一个字节
dos.writeChar('1');
// 写一个字符
dos.writeBoolean(true);
// 写一个布尔值
dos.writeUTF("同学,你好");
// 写一个转换成UTF-8的字符串
dos.close();
// 关闭流
FileInputStream fis = new FileInputStream("dataStream.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
System.out.println(dis.readByte());
// 读一个字节
System.out.println(dis.readChar());
// 读一个字符
System.out.println(dis.readBoolean());
// 读一个转换成 UTF-8编码的字符串
System.out.println(dis.readUTF());
dis.close();
// 关闭流
}
}
# 输出
12
1
true
同学,你好
首先通过 DataOutputStream
的 writeByte()
、writeChar()
、writeBoolean()
和 writeUTF()
方法依次
写人Byte、Char、Boolean 和UTF格式的数据,然后通过 DataInputStream
的 readByte()
、readChar()
、
readB0olean()
和 readUTF()
方法将对应类型的数据依次读取,需要注意的是,只有读取数据的顺序与写数据
的顺序保持一致,才能保证最终数据的正确性。
4.3 PrintStream
通过前面的学习了解到输出流在通过 write()
方法写数据时,只能输出字节或字符类型的数据。如果希望输出其
他类型,例如一个基本数据类型的int 值19、一个Student类型的对象等,此时需要将数据先转为字符串再输出。
这种操作方式显然比较麻烦,为此,在IO包中提供了一个 PrintStream
类,它提供了一系列用于打印数据的
print()
和 println()
方法,被称作打印流。PrintStream
可以实现将基本数据类型的数据或引用数据类型的
对象格式化成字符串后再输出。
package com.example;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* @author tom
*/
public class Example15 {
public static void main(String[] args) throws IOException {
// 创建一个PrintStream对象,将FileOutputStream读取到的数据输出
PrintStream ps = new PrintStream(new FileOutputStream("printStream.txt"), true);
Student stu = new Student();
// 创建一个Student对象
ps.print("这是一个数字:");
ps.println(19);
// 打印数字
ps.println(stu);
// 打印 Student 对象
}
}
class Student {
/**
* 重写object的 toString()方法
* @return
*/
@Override
public String toString() {
return "我是一个学生";
}
}
# printStream.txt文件内容
这是一个数字:19
我是一个学生
PrintStream
的实例对象通过 print()
和 println()
方法向文件 printStream. txt
写入了数据。从运行结
果可以看出,在调用 println()
方法和 print()
方法输出对象数据时,对象的 toString()
方法被自动调用
了。这两个方法的区别在于 println()
方法在输出数据的同时还输出了换行符。
4.4 标准输入输出流
在System类中定义了三个常量:in
、out
和 err
,它们被习惯性地称为标准输人输出流。其中,in为
InputStream
类型,它是标准输人流,默认情况下用于读取键盘输人的数据;out 为 PrintStream
类型,它是
标准输出流,默认将数据输出到命令行窗口;err也是 PrintStream
类型,它是标准错误流,它和out一样也是将
数据输出到控制台。不同的是,err通常输出的是应用程序运行时的错误信息。
package com.example;
import java.io.IOException;
/**
* @author tom
*/
public class Example16 {
public static void main(String[] args) throws IOException {
StringBuffer sb = new StringBuffer();
int ch;
// while循环用于读取键盘输入的数据
// 判断是否读取到数据的末尾
while ((ch = System.in.read()) != -1) {
// 对输入的字符进行判断,如果是回车“\r”或者换行“\n”,则跳出循环
if (ch == '\r' || ch == '\n') {
break;
}
// 将读取到数据
sb.append((char) ch);
}
System.out.println(sb);
// 打印键盘输入的数据
}
}
有的时候,程序会向命令行窗口输出大量的数据,由于输出数据滚动得太快,会导致无法阅读,这时可以将标准输
出流重新定向到其他的输出设备,例如输出到一个文件中。在System类中提供了一些静态方法,这些静态方法允
许对标准输人流和输出流进行重定向,重定向流的常用静态方法如下:
-
void setIn(InputStream in)
:对标准输入流重定向 -
void setOut(PrintStream out)
:对标准输出流重定向 -
void setErr(PrintStream out)
:对标准错误输出流重定向
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example17 {
public static void main(String[] args) throws IOException {
// 对输入流进程重定向
System.setIn(new FileInputStream("source.txt"));
// 对输出流进程重定向
System.setOut(new PrintStream("target.txt"));
// 读取键盘输人的字符
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
// 判断读取到的一行是否有数据
System.out.println(line);
// 打印读取到的一行数据
}
}
}
# source.txt文件内容
床前明月光,疑是地上霜。
举头望明月,低头思故乡。
# target.txt文件写入
床前明月光,疑是地上霜。
举头望明月,低头思故乡。
4.5 PipedlnputStream和PipedOutputStream
在前面学习过多线程,多个线程之间也可以通过IO流实现数据的传输,为此JDK中提供了一种管道流。管道流分为
管道输人流(PipedInputStream
)和管道输出流(PipedOutputStream
),它是一种比较特殊的流,必须先建立连接
才能进行彼此间通信。PipedOutputStream
用于向管道中写人数据,PipedInputStream
用于从管道中读取
写入的数据。
package com.example;
import java.io.*;
/**
* @author tom
*/
public class Example18 {
public static void main(String[] args) throws IOException {
final PipedInputStream pis = new PipedInputStream();
final PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintStream ps = new PrintStream(pos);
while (true) {
try {
System.out.println(Thread.currentThread().getName() + "要求输入内容:");
ps.println(br.readLine());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "发送数据的线程").start();
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(pis));
while (true) {
try {
System.out.println(Thread.currentThread().getName() + "收到的内容:" + br.readLine());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "接收数据的线程").start();
}
}
# 输出
发送数据的线程要求输入内容:
ooo
接收数据的线程收到的内容:ooo
发送数据的线程要求输入内容:
yyyy
接收数据的线程收到的内容:yyyy
发送数据的线程要求输入内容:
iiiiiiii
接收数据的线程收到的内容:iiiiiiii
发送数据的线程要求输入内容:
aaaa
接收数据的线程收到的内容:aaaa
发送数据的线程要求输入内容:
在字符流中也有一对 PipedReader
和 PipedWriter
用于管道的通信,它们的用法和 PipedInputStream
、
PipedOutputStream
相似。
4.6 ByteArraylnputStream和ByteArrayOutputStream
在前面的学习中,都是将文件直接存储到硬盘,但有时候我们希望将文件临时存储到缓冲区,方便以后读取。为此
JDK中提供了一个ByteArrayOutputStream
类。
ByteArrayOutputStream
类会在创建对象时就创建一个byte型数组的缓冲区,当向数组中写数据时,该对象会
把所有的数据先写人缓冲区,最后一次性写入文件。
package com.example;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author tom
*/
public class Example19 {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("source.txt");
// 创建一个字节数组缓冲
ByteArrayOutputStream bos = new ByteArrayOutputStream();
FileOutputStream out = new FileOutputStream("target2.txt");
// 下面的代码是循环读取缓冲区中的数据,并将数据一次性写入文件
int b;
while ((b = in.read()) != -1) {
bos.write(b);
}
in.close();
bos.close();
// 将缓冲区中的数据一次性写入文件
out.write(bos.toByteArray());
out.close();
}
}
之前小节在读取数据时,通常都会定义一个1024个字节的数组,但如果文件太大,这个数组就不能一次性把文件
读取完,此时,需要多次向文件中写人数据,这样的操作明显效率很低。这时,如果使用
ByteArrayOutputStream
创建一个缓冲区,该缓冲区会根据存人数据的多少而自动变化,因此就可以减少写数
据的次数,使程序变得更灵活,从而提高应用程序的效率。需要注意的是,如果读取的文件非常大,就不能使用这
个类,否则会造成内存溢出。
与 ByteArrayOutputStream
类似,ByteArrayInputStream
是从缓冲区中读取数据:
package com.example;
import java.io.ByteArrayInputStream;
/**
* @author tom
*/
public class Example20 {
public static void main(String[] args) {
//创建一个字节数组
byte[] bufs = new byte[]{97, 98, 99, 100};
//读取字节数组中的数据
ByteArrayInputStream bis = new ByteArrayInputStream(bufs);
//下面的代码是循环读取缓冲区中的数据
int b;
while ((b = bis.read()) != -1) {
System.out.println((char) b);
}
}
}
# 输出
a
b
c
d
4.7 CharArrayReader和CharArrayWriter
要想将字符型数据临时存人缓冲区中,还可以使用JDK提供的 CharArrayReader
和 CharArrayWriter
,
CharArrayReader
是从字符数组中读取数据,CharArrayWriter
是在内存中创建一个字符数组缓冲区,它们的
功能与 ByteArraylnputStream
、ByteArrayOutputStream
类似,只不过操作的数据是字符:
package com.example;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.FileReader;
import java.io.IOException;
/**
* @author tom
*/
public class Example21 {
public static void main(String[] args) throws IOException {
// a.txt文件内容
// abcd
// efgh
FileReader reader = new FileReader("a.txt");
CharArrayWriter caw = new CharArrayWriter();
int b;
while ((b = reader.read()) != -1) {
caw.write(b);
}
reader.close();
caw.close();
char[] c = caw.toCharArray();
CharArrayReader cr = new CharArrayReader(c);
int i = 0;
while ((i = cr.read()) != -1) {
System.out.println((char) i);
}
}
}
# 输出
a
b
c
d
e
f
g
h
4.8 SequencelnputStream
前面对文件进行操作时,都是通过一个流对数据进行处理。如果希望多个流处理数据,这时就需要将这些流进行合
并。例如通过下载工具下载某个文件时,通常都是采用多线程的方式分段下载数据,最后会将所有的分段数据进行
合并,这时就可以使用 SequenceInputStream
。SequenceInputStream
类可以将几个输人流串联在一起,合并
为一个输入流。当通过这个流来读取数据时,它会依次从所有被串联的输人流中读取数据,对程序来说,就好像是
对同一个流进行操作。
package com.example;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
/**
* @author tom
*/
public class Example22 {
public static void main(String[] args) throws IOException {
// stream1.txt文件内容hello world!
FileInputStream in1 = new FileInputStream("stream1.txt");
// stream2.txt文件内容你好世界!
FileInputStream in2 = new FileInputStream("stream2.txt");
SequenceInputStream sis = new SequenceInputStream(in1, in2);
FileOutputStream out = new FileOutputStream("stream.txt");
int len;
byte[] buf = new byte[1024];
while ((len = sis.read(buf)) != -1) {
out.write(buf, 0, len);
out.write("\r\n".getBytes());
}
sis.close();
out.close();
}
}
# stream.txt文件内容
hello world!
你好世界!
在创建 SequencelnputStream
对象时使用的构造方法只有两个参数,也就是说只能合并两个流。如果想将多个
流进行合并,这时需要使用 SequenceInputStream
类的另一个构造方法,具体如下:
SequenceInputStream (Enumeration<? extends Inputstream>e)
该构造方法会接收一个 Enumeration
类型的对象作为参数,Enumeraion
对象会返回一系列 InputStream
类型
的对象,以提供给 SequenceInputStream
读取。
package com.example;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
/**
* @author tom
*/
public class Example23 {
public static void main(String[] args) throws IOException {
Vector vector = new Vector();
FileInputStream fis1 = new FileInputStream("1.txt");
FileInputStream fis2 = new FileInputStream("2.txt");
FileInputStream fis3 = new FileInputStream("3.txt");
vector.addElement(fis1);
vector.addElement(fis2);
vector.addElement(fis3);
Enumeration e = vector.elements();
SequenceInputStream sis = new SequenceInputStream(e);
FileOutputStream out = new FileOutputStream("stream.txt");
int len;
byte[] buf = new byte[1024];
while ((len = sis.read(buf)) != -1) {
out.write(buf, 0, len);
}
sis.close();
out.close();
}
}
程序将三个文件合并为一个文件。例程中用到了Vector集合,首先使用该集合存储3个输人流,然后调用
elements() 方法返回一个 Enumeration 对象。在创建 SequenceInputStream 对象时,将 Enumeration 对象作
为参数传递给构造方法,从而实现多个流的合并功能。
5、RandomAccessFile
前面介绍的 IO 流有一个共同特点,就是只能按照数据的先后顺序读取源设备中的数据,或者按照数据的先后顺序
向目标设备写人数据。但如果希望从文件的任意位置开始执行读写操作,则字节流和字符流都无法实现。为此,在
IO 包中,提供了一个类 RandomAccessFile
,它不属于流类,但具有读写文件数据的功能,可以随机地从文件的
任何位置开始执行读写数据的操作。
RandomAccessFile
可以将文件以只读或者读写的方式打开,具体使用哪种方式取决于创建它所采用的构造方
法。
-
RandomAccessFile(File file,String mode)
:参数file指定被访问的文件。 -
RandomAccessFile(String name,String mode)
:参数name指定被访问文件的路径。
通过这两种方法创建 RandomAccessFile
对象时,都需要接受两个参数,第一个参数指定关联的文件,第二个参
数 mode
指定访问文件的模式。参数 mode
有四个值,最常用的有两个,分别是 r
和 rw
,其中 r 表示以只读的
方式打开文件,如果试图对 RandomAccessFile
对象执行写入操作,会抛出 IOException
异常;rw 表示以读
写的方式打开文件,如果该文件不存在,则会自动创建该文件。
RandomAccessFile
类针对文件的随机访问操作,提供了一些用于定位文件位置的方法:
-
long getFilePointer()
:返回当前读写指针所处的位置。 -
void seek(long pos)
:设定读写指针的位置,与文件开头相隔 pos个字节数。 -
int skipBytes(int n)
:使读写指针从当前位置开始,跳过n个字节。 -
void setLength(long newLength)
:设置此文件的长度。
在 RandomAccessFile
对象中包含了一个记录指针,用于表示文件当前读写处的位置。当新创建一个
RandomAccessFile
对象时,该对象的文件记录指针位于文件头(也就是0处),当读写了n个字节后,文件的记录
指针就会向后移动n个字节。RandomAccessFile
的 seek(long pos)
方法,可以使记录指针向前、向后自由移
动,通过 RandomAccessFile
的 getFilePointer()
方法,便可获取文件当前记录指针的位置。
package com.example;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @author tom
*/
public class Example24 {
public static void main(String[] args) throws NumberFormatException, IOException {
RandomAccessFile raf = new RandomAccessFile("time.txt", "rw");
// int类型的变量表示试用次数
int times;
// 第一次读取文件时times为5
times = Integer.parseInt(raf.readLine());
if (times > 0) {
//试用一次,次数减少一次
System.out.println("您还可以试用" + times-- + "次!");
raf.seek(0);
//将剩余的次数再次写人文件
raf.writeBytes(times + "");
} else {
//当time<=0,告诉用户试用期已到
System.out.println("软件试用次数已到");
}
//关闭RandomAccessFile对象
raf.close();
}
}
# 输出
您还可以试用5次!
您还可以试用4次!
您还可以试用3次!
您还可以试用2次!
您还可以试用1次!
软件试用次数已到