概述
计算机并不区分二进制文件与文本文件。所有的文件都是以二进制形式来存储的,因此,从本质上说,所有的文件都是二进制文件。所以字符流是建立在字节流之上的,它能够提供字符层次的编码和解码。例如: 在写入一个字符时, Java虚拟机会将字符转为文件指定的编码(默认是系统默认编码), 在读取字符时, 再将文件指定的编码转化为字符
- 常见的码表如下:
- ASCII : 美国标准信息交换码。用一个字节的7位可以表示
- ISO8859-1 : 拉丁码表(欧洲码表)用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面
- GB2312 : 英文占一个字节,中文占两个字节.中国的中文编码表
- GBK : 中国的中文编码表升级,融合了更多的中文文字符号
- Unicode : 国际标准码规范,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode
- UTF-8 : 最多用三个字节来表示一个字符
(我们以后接触最多的是iso8859-1、gbk、utf-8)
查看上述码表后,很显然中文的‘中’在iso8859-1中是没有对映的编码的。或者一个字符在2中码表中对应的编码不同,例如有一些字在不同的编码中是有交集的,例如bjg5 和gbk 中的汉字简体和繁体可能是一样的,就是有交集,但是在各自码表中的数字不一样
- 例 : 使用gbk 将中文保存在计算机中,
中国
对映100 200
, 如果使用big5 打开可能? ...
, 不同的编码对映的是不一样的。很显然,我们使用什么样的编码写数据,就需要使用什么样的编码来对数据。
ISO8859-1:一个字节
GBK: 两个字节包含了英文字符和扩展的中文(ISO8859-1+中文字符)
UTF-8 : 万国码,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间 - 将指定位置的文件通过字节流读取到控制台
public class TestIo {
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt";
writFileTest();
readFileByInputStream(path);
}
private static void readFileByInputStream(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
int len = 0;
while ((len = fis.read()) != -1) {
System.out.print((char) len);
}
}
private static void writFileTest() throws FileNotFoundException,
IOException {
// 创建文件对象
File file = new File("c:\\a.txt");
// 创建文件输出流
FileOutputStream fos = new FileOutputStream(file);
fos.write("中国".getBytes());
fos.close();
}
}
发现控制台输出的信息:???ú
是这样的东西,打开a.txt 文本发现汉字 中国
确实写入成功。那么说明使用字节流处理中文有问题。仔细分析,我们的FileInputStream输入流的read() 一次是读一个字节的,返回的是一个int显然进行了自动类型提升。那么我们来验证一下“中国”对应的字节是什么 ? 使用:"中国".getBytes() 即可得到字符串对应的字节数组。是[-42, -48, -71, -6]同样,将read方法返回值直接强转为byte ,发现结果也是-42, -48, -71, -6 。代码如下:
public class TestIo {
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt";
writFileTest();
readFileByInputStream(path);
//查看中国对应的编码
System.out.println(Arrays.toString("中国".getBytes()));
}
private static void readFileByInputStream(String path) throws IOException{
FileInputStream fis = new FileInputStream(path);
int len = 0;
while ((len = fis.read()) != -1) {
System.out.println((byte)len);
}
}
private static void writFileTest() throws FileNotFoundException,
IOException {
// 创建文件对象
File file = new File("c:\\a.txt");
// 创建文件输出流
FileOutputStream fos = new FileOutputStream(file);
fos.write("中国\r\n".getBytes());
fos.close();
}
}
那么中国对应的是-42, -48, -71, -6是4个字节。 那就是一个中文占2个字节,(这个和编码是有关系的)很显然,我们的中文就不能够再一个字节一个字节的读了。所以字节流处理字符信息时并不方便那么就出现了字符流。字节流是字符流是以字符为单位。
- 体验字符流:
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt";
readFileByReader(path);
}
private static void readFileByReader(String path) throws IOException {
FileReader fr = new FileReader(path);
int len = 0;
while ((len = fr.read()) != -1) {
System.out.print((char) len);
}
}
- 总结:
字符流 = 字节流 + 编码表
,为了更便于操作文字数据。字符流的抽象基类:Reader , Writer。由这些类派生出来的子类名称都是以其父类名作为子类名的后缀,如FileReader、FileWriter。
Reader输入字符流
- Reader中常见的方法
-
int read()
: 读取一个字符. 返回的是读到的那个字符. 如果读到流的末尾, 返回-1 -
int read(char[])
: 将读到的字符存入指定的数组中, 返回的是读到的字符个数, 也就是往数组里装的元素的个数. 如果读到流的末尾, 返回-1 -
close()
: 读取字符其实用的是window系统的功能, 就希望使用完毕后, 进行资源的释放, 由于Reader也是抽象类, 所以想要使用字符输入流需要使用Reader的实现类(FileReader)
-
- FileReader
- 用于读取文本文件的流对象
- 用于关联文本文件
- FileReader构造函数 : 在读取流对象初始化的时候, 必须要指定一个被读取的文件, 如果该文件不存在会发生FileNotFoundException
public class IOTest_Reader {
public static void main(String[] args) throws Exception {
String path = "c:/a.txt";
// readFileByInputStream(path);
readFileByReader(path);
}
/**
* 使用字节流读取文件内容
*
* @param path
*/
public static void readFileByInputStream(String path) throws Exception {
InputStream in = new FileInputStream(path);
int len = 0;
while ((len = in.read()) != -1) {
System.out.print((char) len);
}
in.close();
}
/**
* 使用字符流读取文件内容
*/
public static void readFileByReader(String path) throws Exception {
Reader reader = new FileReader(path);
int len = 0;
while ((len = reader.read()) != -1) {
System.out.print((char) len);
}
reader.close();
}
}
Writer输入字符流
- Writer中的常见的方法:
-
write(ch)
: 将一个字符写入到流中。 -
write(char[])
: 将一个字符数组写入到流中。 -
write(String)
: 将一个字符串写入到流中。 -
flush()
: 刷新流,将流中的数据刷新到目的地中,流还存在。 -
close()
: 关闭资源, 在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭
-
- 发现基本方法和OutputStream 类似,有write方法,功能更多一些。可以接收字符串。同样道理Writer是抽象类无法创建对象。查阅API文档,找到了Writer的子类FileWriter
- FileWriter
- 将文本数据存储到一个文件中
public class IoTest2_Writer {
public static void main(String[] args) throws Exception {
String path = "c:/ab.txt";
writeToFile(path);
}
/**
* 写指定数据到指定文件中
*
*/
public static void writeToFile(String path) throws Exception {
Writer writer = new FileWriter(path);
writer.write('中');
writer.write("世界".toCharArray());
writer.write("中国");
writer.close();
}
}
- 追加文件 : 默认的FileWriter方法新值会覆盖旧值,想要实现追加功能需要
使用如下构造函数创建输出流 append值为true即可。
- `` FileWriter(String fileName, boolean append) ``
- `` FileWriter(File file, boolean append) ``
- flush方法 : 如果使用字符输出流,没有调用close方法,会发生什么?
private static void writeFileByWriter(File file) throws IOException {
FileWriter fw = new FileWriter(file);
fw.write('新');
fw.flush();
fw.write("中国".toCharArray());
fw.write("世界你好!!!".toCharArray());
fw.write("明天");
// 关闭流资源
//fw.close();
}
程序执行完毕打开文件, 发现没有内容写入. 原来需要使用flush方法. 刷新该流的缓冲, 为什么只要指定close方法就不用再flush方法? 因为close也调用了flush方法.
字符流拷贝文件
一个文本文件中有中文有英文字母,有数字。想要把这个文件拷贝到别的目录中。我们可以使用字节流进行拷贝,使用字符流呢?肯定也是可以的
- 字符流拷贝文件实现
- 一次读一个字符就写一个字符,效率不高。把读到的字符放到字符数组中,再一次性的写出。
public static void main(String[] args) throws Exception {
String path1 = "c:/a.txt";
String path2 = "c:/b.txt";
copyFile(path1, path2);
}
/**
* 使用字符流拷贝文件
*/
public static void copyFile(String path1, String path2) throws Exception {
Reader reader = new FileReader(path1);
Writer writer = new FileWriter(path2);
int ch = -1;
while ((ch = reader.read()) != -1) {
writer.write(ch);
}
reader.close();
writer.close();
}
- 字节流可以拷贝视频和音频等文件,那么字符流可以拷贝这些吗?经过验证拷贝图片是不行的。发现丢失了信息,为什么呢?计算机中的所有信息都是以二进制形式进行的存储(1010)图片中的也都是二进制, 在读取文件的时候字符流自动对这些二进制按照码表进行了编码处理,但是图片本来就是二进制文件,不需要进行编码。有一些巧合在码表中有对应,就可以处理,并不是所有的二进制都可以找到对应的。信息就会丢失。所以字符流只能拷贝以字符为单位的文本文件 (以ASCII码为例是127个, 并不是所有的二进制都可以找到对应的ASCII, 有些对不上的, 就会丢失信息)
public static void main(String[] args) throws Exception {
String path1 = "c:/a.txt";
String path2 = "c:/b.txt";
copyFile(path1, path2);
}
public static void copyFile3(String path1, String path2) throws Exception {
Reader reader = new FileReader(path1);
Writer writer = new FileWriter(path2);
int ch = -1;
char [] arr=new char[1024];
while ((ch = reader.read(arr)) != -1) {
writer.write(arr,0,ch);
}
reader.close();
writer.close();
}
字符流的异常处理
public static void main(String[] args) throws Exception {
String path1 = "c:/a.txt";
String path2 = "c:/b.txt";
copyFile2(path1, path2);
}
/**
* 使用字符流拷贝文件,有完善的异常处理
*/
public static void copyFile2(String path1, String path2) {
Reader reader = null;
Writer writer = null;
try {
// 打开流
reader = new FileReader(path1);
writer = new FileWriter(path2);
// 进行拷贝
int ch = -1;
while ((ch = reader.read()) != -1) {
writer.write(ch);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 关闭流,注意一定要能执行到close()方法,所以都要放到finally代码块中
try {
if (reader != null) {
reader.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
```
####字符流的缓冲区
- 查看Reader发现Reader, 操作的是字符, 我们就不需要进行编码解码操作, 由字符流读到二进制, 自动进行解码得到字符, 写入字符自动编码成二进制. Reader有一个子类BufferedReader. 子类继承父类显然子类可以重写父类的方法, 也可以增加自己的新方法. 例如一次读一行就是常用的操作.那么BufferedReader 类就提供了这个方法, 可以查看readLine()方法具备 一次读取一个文本行的功能. 很显然, 该子类可以对功能进行增强
- 体验BufferedReader
```java
public class IoTest_BufferedReader {
public static void main(String[] args) throws IOException {
readFile("c:\\a.txt");
}
private static void readFile(String path) throws IOException {
Reader read = new FileReader(path);
BufferedReader br = new BufferedReader(read);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
```
- 注意:在使用缓冲区对象时,要明确,缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区对象时,要先有流对象存在.缓冲区的出现提高了对流的操作效率。原理:其实就是将数组进行封装
- 使用字符流缓冲区拷贝文本文件.
```java
public class Demo {
public static void main(String[] args) throws IOException {
// 关联源文件
File srcFile = new File("c:\\linux大纲.txt");
// 关联目标文件
File destFile = new File("d:\\linux大纲.txt");
// 实现拷贝
copyFile(srcFile, destFile);
}
private static void copyFile(File srcFile, File destFile)
throws IOException {
// 创建字符输入流
FileReader fr = new FileReader(srcFile);
// 创建字符输出流
FileWriter fw = new FileWriter(destFile);
// 字符输入流的缓冲流
BufferedReader br = new BufferedReader(fr);
// 字符输出流的缓冲流
BufferedWriter bw = new BufferedWriter(fw);
String line = null;
// 一次读取一行
while ((line = br.readLine()) != null) {
// 一次写出一行.
bw.write(line);
// 刷新缓冲
bw.flush();
// 进行换行,由于readLine方法默认没有换行.需要手动换行
bw.newLine();
}
// 关闭流
br.close();
bw.close();
}
}
```