今天学习的内容是IO流概述、字符流和字符流缓冲区
一、IO流概述
首先介绍一下流: 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象 。IO流用来处理设备间的数据传输 ,其中I就是Input( 读取 ),O就是Output( 写入 )。IO流根据数据流向的不同分为输入流和输出流;根据处理数据类型的不同分为字节流和字符流。程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。字节流处理的是以字节为单位的数据,可以操作字节、字节数组和二进制对象。而Java中的字符是Unicode编码,是双字节的,字节流并不能很好地处理字符数据,这就出现了字符流。实际上字符流就是字节流获取字节数据后,不直接操作,而是先查指定的编码表,获取对应的文字,再对这个文字进行操作,简单来说就是字节流+编码表。字符流处理的是以字符为单位的数据,可以操作字符、字符数组和字符串。字节流和字符流的区别:
- 字节流处理字节、字节数组和二进制对象;字符流处理字符、字符数组或字符串
- 字节流在输出数据时不需要调用flush()或close()方法,因为它本身并没用到缓冲区;字符流在输出数据时必须使用flush()或close()方法,这是因为字符流在输出时本身就用到了缓冲区
- 如果是音频文件、图片、歌曲等等,就用字节流处理;如果是关系到文字(文本)的,用字符流处理
其中所有从InputStream类和Reader类继承而来的类都含有read()方法,用于读取数据;所有从OutputStream类和Writer类继承而来的类也都含有write() 方法,用于写入数据。但是,我们通常不会直接使用继承来的方法,它们之所以存在是因为别的IO类会用到它们,以便提供更有用的方法。因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(装饰器和适配器设计模式)。
二、字符流
首先从我们最熟悉的文字开始,介绍字符流。字符流主要用来操作文本数据,下面以FileReader类与FileWriter类为例实现对文本文件的读取与写入(这里有个小坑):
public class Test73 {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
public static void main(String[] args) {
// 使用字符输出流将文字写到文本文件中
FileWriter fw = null;
try {
fw = new FileWriter("test.txt");// 如果文件不存在,会被自动创建;如果文件存在,内容会被覆盖
// !fw = new FileWriter("x:\\test.txt");如果指定不存在的路径,会抛出FileNotFoundException
// fw = new FileWriter("test.txt",true); 如果在构造函数中加入true,可以实现文件内容续写
fw.write("下一句q换行" + LINE_SEPARATOR);// 将数据写入到缓冲区
fw.write("hahaa");// write()方法输出的内容不会换行,要使用换行符
fw.flush();// 刷新缓冲区的数据到目的地。虽然close()方法也会先执行一次flush()操作,但是最好显式地调用flush()方法
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
// 使用字符输入流将文本文件中的内容读取并打印到控制台,方法1
FileReader fr_1 = null;
try {
fr_1 = new FileReader("test.txt");// 一定要确保该文件存在,若不存在会抛出FileNotFoundException
int ch = 0;
while ((ch = fr_1.read()) != -1) {// 单个字符读取
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr_1 != null) {
try {
fr_1.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
// 使用字符输入流将文本文件中的内容读取并打印到控制台,方法2
FileReader fr_2 = null;
try {
fr_2 = new FileReader("test.txt");
char[] buf = new char[1024];// 创建一个临时数组来缓存读到的字符
int len = 0;// 往数组里装的字符个数
while ((len = fr_2.read(buf)) != -1) {// 将字符直接读取到字符数组中
System.out.print(new String(buf, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr_2 != null) {
try {
fr_2.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
}
}
我们还可以使用字符流的读写方法完成文本文件的复制,如下:
public class Test74 {
public static void main(String[] args) {
// 单个字符读取读取
FileReader fr_1 = null;
FileWriter fw_1 = null;
try {
fr_1 = new FileReader("IOTest.txt");
fw_1 = new FileWriter("IOTest_copy_1.txt");
int ch = 0;
while ((ch = fr_1.read()) != -1) {
fw_1.write(ch);
fw_1.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw_1 != null) {
try {
fw_1.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
if (fr_1 != null) {
try {
fr_1.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
// 将字符直接读取到字符数组中
FileReader fr_2 = null;
FileWriter fw_2 = null;
try {
fr_2 = new FileReader("IOTest.txt");
fw_2 = new FileWriter("IOTest_copy_2.txt");
char[] buf = new char[1024];
int len = 0;
while ((len = fr_2.read(buf)) != -1) {
fw_2.write(buf, 0, len);
fw_2.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw_2 != null) {
try {
fw_2.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
if (fr_2 != null) {
try {
fr_2.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
}
}
三、字符流缓冲区
一般来说,为了性能优化,最好使用缓冲区。其实上例中的char数组就是一种读取的缓冲区,而字符流的输出本身就使用了缓冲区。缓冲区就好比超市的购物车,在购买商品(从磁盘读取数据)时不用每拿一件就结一次账(将数据写入磁盘),而是可以放到购物车(内存缓冲区)中,结账的时候再从购物车中一件一件拿出商品,以提高效率(由于内存处理速度高于磁盘)。如果想要强制写入,可以调用flush()方法。为了使用方便,Java将字符流缓冲区封装为对象,并使用BufferedReader类和BufferedWriter类描述。缓冲区类使用装饰器设计模式,封装缓冲区的好处就是提供了更多的方法供我们使用,比如readline()和newLine()方法,下面使用字符流和字符流缓冲区完成对文本文件的读写(依旧是那个坑):
public class Test75 {
public static void main(String[] args){
// 使用字符输出流和字符流的缓冲区将文字写到文本文件中
BufferedWriter out = null;
try{
out = new BufferedWriter(new FileWriter("test.txt"));
out.write("下一句q换行");// 将数据写入到缓冲区
out.newLine();// 这就相当于换行符
out.write("hahaa");// write()方法输出的内容不会换行,要使用换行符
out.flush();//刷新缓冲区的数据到目的地(这里就是文件)
}catch (IOException e){
e.printStackTrace();
}finally{
if (out != null) {
try {
out.close();// 其实关闭的就是流
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
// 使用字符输入流和字符流缓存区将文本文件中的内容读取并打印到控制台
BufferedReader in = null;
try{
in = new BufferedReader(new FileReader("test.txt"));
String line = null;
while((line = in.readLine())!=null){
System.out.println(line);
}
}catch (IOException e){
e.printStackTrace();
}finally{
if (in != null) {
try {
in.close();// 其实关闭的就是流
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
}
}
使用缓冲区技术复制文本文件:
public class Test76 {
public static void main(String[] args) {
BufferedReader in = null;
BufferedWriter out = null;
try {
in = new BufferedReader(new FileReader("IOTest.txt"));
out = new BufferedWriter(new FileWriter("IOTest_copy.txt"));
String line = null;
while ((line = in.readLine()) != null) {
out.write(line);
out.newLine();
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException("关闭资源失败");
}
}
}
}
}