在Java中,可从中读出一系列数据的对象称为“输入流(InputStream)”,而能向其中写入一系列数据的对象称为“输出流(OutputStream)”。Java的输出/输入都是通过继承抽象类InputStream和OutputStream(面向字节)、Reader和writer(面向字符)来实现的。
一、IO流对象层次关系
二、IO 基本操作
在java.io包中流的操作主要有字节流、字符流两大类,两者都有输入和输出的操作。在字节流中输出数据主要使用OutputStream类完成,输入使用InputStream类。在字符流中输出主要是使用Writer类完成,输入主要使用Reader类完成。
在Java中IO操作也有相应的步骤,以文件的操作为例,主要的操作流程如下:
(1) 使用File类打开一个文件。
(2) 通过字节流或字符流的子类指定输出位置。
(3) 进行读/写操作。
(4) 关闭输入/输出。
下面分别介绍如何使用字节流或字符流保存或读取数据。
三、字节流
字节流主要存操作byte类型数据,以byte数组为准,主要操作类是OutputStream类和InputStream类。
1.字节输出流:OutputStream
OutputStream是整个IO包中字节输出流的最大父类,此类的定义如下:
public abstract class OutputStream extends Objectimplements Closeable, Flushable
从以上定义中可以发现,OutputStream是一个抽象类,如果要是使用此类,则首先必须通过子类实例化对象。如果现在操作的是一个文件,则可以使用FileOutputStream类,通过向上转型后,可以为OutputStream实例化,在OutputStream类中的主要操作方法:
// 关闭此输出流并释放与此流有关的所有系统资源
public void close() throws IOException
// 刷新此输出流并强制写出所有缓冲的输出字节。
public void flush() throws IOException
// 将 b.length 个字节从指定的 byte 数组写入此输出流
public void write(byte[] b) throws IOException
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
public void write(byte[] b, int off, int len) throws IOException
范例:向文件中写入字符串。
public class OutputStreamDemo {
/**
* 向文件中写入字符串
*/
public static void main(String[] args) throws IOException {
// 1.使用File类,找到一个将字符串写入的文件
File file = new File("E:" + File.separator + "ops.txt");
// 2.通过子类FileOutputStream实例化OutputStream
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
// 3.进行写操作
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
} finally {
try {
// 4.关闭输出流
outputStream.close();
} catch (IOException e) {
throw new RuntimeException("关闭字符输出流失败!");
}
}
}
}
①提问:通过上面的例子,我们发现将字符串写入到文件中,但是我们再次运行时会发现,新写入的字符串将过去的覆盖了,我们该如何解决了?
回答:
此时我们应该使用FileOutPutStream的另外一个构造方法:
// 如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
public FileOutputStream(File file, boolean append) throws FileNotFoundException
②提问:如何增加换行?
回答:如果要换行,则直接在字符串要换行处加一个"\r\n"。在windows操作系统中“\r\n”表示换行,但是在Linux操作系统中“\n”表示换行,解决办法,如下:
private static final String FILE_SEPARATOR = System.getProperty("line.separator");
2.字节输入流
既然程序可以向文件中写入内容,则也可以通过InputStream从文件中将内容读取出来。InputStream类的定义如下:
public abstract class InputStreamextends Objectimplements Closeable
与OutputStream类一样,InputStream本身也是一个抽象类,需要依靠子类来实现,如果从文件中读取数据,子类是FileInputStream。InputStream的主要方法:
// 返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available() throws IOException
// 关闭此输入流并释放与该流关联的所有系统资源。
public void close() throws IOException
// 将输入流中最多 len 个数据字节读入 byte 数组
public int read(byte[] b, int off, int len) throws IOException
范例:从文件中读取内容。
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
// 1.使用File类,找到一个将字符串写入的文件
File file = new File("E:" + File.separator + "ops.txt");
InputStream inputStream = null;
try {
// 2.实例化字节输入流 InputStream对象
inputStream = new FileInputStream(file);
// 3.进行读操作
byte[] bs = new byte[1024];
int len = 0;
while ((len = inputStream.read(bs)) != -1) {
System.out.println(new String(bs, 0, len));
}
} finally {
try {
// 4.关闭资源
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("关闭输入字节流失败!");
}
}
}
}
①问题:上面程序中,开辟的byte数据大小为1024,而实际的内容只有12个字节,显然开辟的空间太大,该怎么解决了?
回答:
byte[] bs = new byte[(int) file.length()];
此程序明确知道了要读取的内容为14字节,所以可以定义(int)file.length()大小的空间,如果不确定大小,不建议使用此种方法。
四、字符流
在程序中一个字符等于两个字节,那么Java提供了Reader和Writer两个专门操作字符流的类。
1.字符输出流Writer
此类的定义如下:
public abstract class Writer extends Objectimplements Appendable, Closeable, Flushable
关于对Appendable接口的说明,此接口的定义如下:
public interface Appendable {
Appendable append(char c) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException
Appendable append(CharSequence csq) throws IOException
}
此接口表示的是内容可以被追加,接受的参数是CharSequence,实际上String类也实现了此接口,所以可以直接通过此接口的方法向输出流中追加内容。
Writer的主要方法:
// 关闭此流,但要先刷新它
public abstract void close()throws IOException
// 刷新该流的缓冲
public abstract void flush() throws IOException
// 写入字符串
public void write(String str) throws IOException
// 写入字符数组
public void write(char[] cbuf) throws IOException
范例:向文件中写入数据
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
out.close();
2.字符输入流Reader
Reader是使用字符的方式从文件中读取数据,Reader类的定义如下:
public abstract class Reader extends Objectimplements Readable, Closeable
Reader类的常用方法:
// 关闭该流并释放与之关联的所有资源
public abstract void close()throws IOException
// 读取单个字符
public int read() throws IOException
// 将字符读入数组
public int read(char[] cbuf) throws IOException
范例:从文件中读取内容
File file = new File("E:" + File.separator + "w.txt");
Reader reader = new FileReader(file);
char[] c = new char[1024];
int len = 0;
while ((len = reader.read(c)) != -1) {
System.out.println(new String(c, 0, len));
}
reader.close();
五、字节流与字符流的区别
字节流与字符流使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同了?
实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身操作的,二字符流在操作时使用了缓冲区,通过缓冲区在操作文件。
下面一两个写文件的操作为主进行比较,但是在操作时字节流和字符流的操作完成之后都不关闭输出流。
范例:使用字节流不关闭执行
File file = new File("E:" + File.separator + "ops.txt");
OutputStream outputStream = new FileOutputStream(file);
String str = "Hello world!";
byte[] b = str.getBytes();
outputStream.write(b);
// 关闭输出流
// outputStream.close();
没有关闭字节流操作,但是文件中也依然存在输出的内容,证明字节流是直接操作文件本身的。下面使用字符流,观察效果
范例:使用字符流不关闭执行
File file = new File("E:" + File.separator + "w.txt");
Writer out = new FileWriter(file);
String str = "hello world!";
out.write(str);
// 关闭输出流
// out.close();
程序运行之后发现文件中没有任何内容,这是因为字符流操作使用了缓冲区,而在关闭字符流时会强制性的将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的,所以得出结论:字符流使用缓冲区,而字节流没有使用缓冲区。
①问题:什么是缓冲区?
回答:可以简单地把缓冲区理解为一段特殊的内存。
某些情况下,一个程序频繁地操作一个资源(如文件),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域中,以后直接从此区域中读取数据即可,因为读内存速度比较快,这样提高性能。
强制性清空缓冲区
OutputStream的flush()方法。
②问题:使用字节流好还是字符流好?
回答:字节流更好。所有的文件在硬盘或者在传输时都是以字节的方式进行的,包括图片等,而字符是只有在内存中才使用。
范例:文件复制
1.实现要求,在dos命令中存在一个文件的复制命令(copy),copy命令的语法格式如下:
copy 源文件 目标文件
下面使用Java完成以上功能,程序运行时可以按照如下格式进行:
java Copy 源文件 目标文件
2.思路,从运行格式中发现,要输入源文件和目标文件的路径,可以使用命令行参数完成,必须对输入的参数进行验证,如果输入的参数不是两个,或者源文件不存在,则程序给出错误信息并退出。
是选择字节流还是字符流,因为要复制的文件不一定都是文本文件,也可能包含图片或者声音,所以使用字节流。
两种操作方式:
将源文件中的内容全部读取到内存,并一次性写入到目标文件中。
不要将源文件中的内容全部都取进来,而是使用边读边写的方式。
很显然,使用边读边写更好,文件内容过多,则内存是无法装的。
public class Copy {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("输入的参数不正确!");
System.out.println("例:java Copy 源文件路径 目标文件路径");
System.exit(1); // 系统退出
}
File source = new File(args[0]); // 源文件的File对象
File target = new File(args[1]); // 目标文件的File对象
if (!source.exists()) {
System.out.println("源文件不存在!");
System.exit(1);
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(source);
outputStream = new FileOutputStream(target);
if (inputStream != null && outputStream != null) {
int len = 0;
byte[] bs = new byte[1024];
while ((len = inputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}
System.out.println("复制完成!");
}
} catch (IOException e) {
System.out.println("复制失败!");
throw new RuntimeException(e.getMessage());
}
finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
throw new RuntimeException("输出关闭流失败!");
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException("输入关闭流失败!");
}
}
}
}
}