IO流(二)~ 字符流
一、转换流出现的原因及思想
1. 转换流出现的原因及思想
由于字节流操作中文不是特别方便,所以,java就提供了转换流。字符流=字节流+编码表。
2. 编码表概述和常见的编码表
- 概述
学IO的时候使用字节流的read()针对中文操作输出的时候可能出现中文乱码。这是为什么呢?因为在默认情况下,使用的编码表示ASCII表,中文是两个字节存储的,ASCII表示一个字节代表一个字符,强行将中文拆分成两个字节进行解析就会找不到对应字符出现或者找到的字符不可能是中文的。ASCII码讲中文拆分成两个字节。第一个字节的对应位置一定是负数,第二个常见是负数,可能是正数。
由现实世界的字符和其对应的数值组成的一张表,用来解析和转换各种字符 - 常见的编码表
①ASCII码表:7位表示一个数据,一个字节表示一个字符。最高位是符号位
'a' 97 'A' 65 0 48
②ISO-8859-1:拉丁码表 8位表示一个数据
③GB2312:中国的简体中文编码表
④GBK:中国的简体中文编码表升级
⑤GB18030:GBK的取代版本
⑥BIG-5:繁体中文编码表,俗称“大五码”
⑦Unicode:国际标准码,融合了各种文字
所有文字都用两个字节表示,java语言使用的就是Unicode编码
⑧UTF-8:国际化编码表,升级版
最多能用三个字节表示一个字符。就是能用一个字节表示的字符就使用一个字节表示(兼容ASCII),一个用不了的就用两个,实在还不行就使用三个。
3.字符串中的编码问题
编码:用预先规定的方法将文字、数字或其它对象编成数码,或将信息、数据转换成规定的电脉冲信号,是信息从一种形式或格式转换为另一种形式的过程。
解码:是编码的逆过程。
简单的来说,编码就是将人类能看懂的信息转换成计算机能看懂的信息,解码是将计算机看懂的信息转换成人类能看懂的信息
编码问题:如果编码和解码使用的编码方式不同,那么就会出现乱码现象。
案例代码:
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "积云";
//编码,默认编码方式为utf-8:[-25, -89, -81, -28, -70, -111]
byte[] bys1 = str.getBytes();
System.out.println("默认编码后的结果:"+Arrays.toString(bys1));
//解码,使用utf-8解码,数据还是原来的
String decodeUtf8 = new String(bys1, "utf-8");
//结果还是:积云
System.out.println("utf-8解码:"+decodeUtf8);
////解码,使用gbk解码,数据变成了乱码:绉簯
String decodeGbk = new String(bys1,"GBK");
System.out.println("gbk解码:"+decodeGbk);
}
}
二、字符流使用
1.OutputStreamWriter介绍
OutputStreamWriter是从字符流到字节流的桥接:使用指定的字符集将写入其中的字符编码为字节。它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。
每次调用write()方法都会导致在给定字符上调用编码转换器。生成的字节在写入底层输出流之前在缓冲区中累积。可以指定此缓冲区的大小,但默认情况下,它足够大,可用于大多数用途。请注意,传递给write()方法的字符不会被缓冲。
构造方法:
//out 输出流
public OutputStreamWriter(OutputStream out)
//out 输出流,charsetName 编码方式
public OutputStreamWriter(OutputStream out,String charsetName)
字符流的5种写数据方式:
//写一个字符
public void write(int c)
//写一个字符数组
public void write(char[] cbuf)
//写入字符串的某一部分,cbuf:字符数组,off:起始的索引,len:写入的字符个数
public void write(char[] cbuf,int off,int len)
//写一个字符串
public void write(String str)
//写一个字符串的一部分,str:字符串,off:起始的索引,len:写入的字符个数
public void write(String str,int off,int len)
1. 案例:
将“欢迎来到积云”写入一个文件中,使用默认的编码方式
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件的绝对路径
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream( "/Users/tensionxu/Desktop/test.txt"));
// 写数据
osw.write("欢迎来到积云");
// 释放资源
osw.close();
}
}
运行结果(打开文件):
使用默认格式为utf-8的软件打开test.txt文档可以看到里面的内容
2. 案例:
将“欢迎来到积云”写入一个文件中,使用指定的编码方式GBK
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件的绝对路径,"GBK":指定的编码方式
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream( "/Users/tensionxu/Desktop/test.txt"),"GBK");
// 写数据
osw.write("积云欢迎你!");
// 释放资源
osw.close();
}
}
运行结果(打开文件):
使用默认格式为utf-8的软件打开test.txt文档,可以看到直接打不开了,强行打开会看到里面是乱码,更改文件编码格式后可以看到正确的内容
3. 案例:
使用 write(int c)将‘a’写入文件
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件的绝对路径,
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream( "/Users/tensionxu/Desktop/test.txt"));
//这样写是可以的
//osw.write('a');
//注意写入文件后的内容是 a, 不是97, 因为这里是写入一个ASCII表中,97这个数字对应的是a
osw.write(97);
// 释放资源
osw.close();
}
}
4. 案例:
跳过字符数组前两个字符,将其他的写入文件
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
char[] chars = {'a','b','c','d'};
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件绝对路径
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream( "/Users/tensionxu/Desktop/test.txt"));
//跳过前两个,即跳过索引是0和1的字符,所以off为2,len为字符数组总长度-2
osw.write(chars,2,chars.length-2);
// 释放资源
osw.close();
}
}
运行结果(打开文件):
5. 案例:
将字符串的最后三个字符写入文件
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
String str = "黑夜给了我一双黑色的眼睛";
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件绝对路径
OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream( "/Users/tensionxu/Desktop/test.txt"));
//将字符串的最后三个字符写入文件
//最后三个应该是:的眼睛
//我们可以去数"的"的索引,发现是9,最后三个字符,所以len是3
//osw.write(str,9,3);
//但是有个问题,如果字符串内容几千个,一个一个数,数完估计整个人都不好了
//1.所以我们可以动态去计算,索引是从0开始,到str.length()-1结束
//2.最后一个字符的索引是str.length()-1,那么倒数第三个的索引是多少呢? str.length()-3
osw.write(str,str.length()-3,3);
// 释放资源
osw.close();
}
}
2. InputStreamReader 介绍
InputStreamReader类是从字节流到字符流的桥接器:它使用指定的字符集读取字节并将它们解码为字符。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节。 为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节。
构造方法:
//in:字节输入流
public InputStreamReader(InputStream in)
//in:字节输入流,charsetName:编码方式
public InputStreamReader(InputStream in,String charsetName)
字符流的2种读数据方式:
//读取单个字符。
public int read()
//将读到的内容存入字符数组中
public int read(char[] cbuf)
1. 案例
将文件中的内容一个一个读取出来并打印
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
//文件中的内容:黑色的眼睛
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件绝对路径,"UTF-8":编码格式
InputStreamReader isr = new InputStreamReader(
new FileInputStream("/Users/tensionxu/Desktop/test.txt"), "UTF-8");
// 读取数据 一次读取一个字符
int ch = 0;
while ((ch = isr.read()) != -1) {
System.out.println((char) ch);
}
// 释放资源
isr.close();
}
}
2. 案例
将文件中的内容一次性读取出来
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
// 创建对象,/Users/tensionxu/Desktop/test.txt:文件绝对路径,
InputStreamReader isr = new InputStreamReader(
new FileInputStream("/Users/tensionxu/Desktop/test.txt"));
// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
while ((len = isr.read(chs)) != -1) {
System.out.print(new String(chs, 0, len));
}
// 释放资源
isr.close();
}
}
3.使用字符流实现文件复制(文本)
案例分析:
实现文件复制:
将文件test.txt(里面内容:黑色的眼睛)拷贝到haha文件夹下面,haha文件夹有可能不存在
1.需要将文件里面的内容(黑色的眼睛)全部拷贝,文件名称(test.txt)也需要拷贝
2.需要在一个目录下面创建一个文件,文件名称使用拷贝的(test.txt),文件里面的内容也使用拷贝的文件内容(黑色的眼睛)
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
//1. 拷贝文件名字并且创建对应的目标文件
// 苹果笔记本文件绝对路径:/Users/tensionxu/Desktop/test.txt,要拷贝的就是这个文件
// windows文件路径:C:\Users\DUDU\Desktop\2002A\day15\test.txt
String name = "/Users/tensionxu/Desktop/test.txt";
//创建一个源文件对象
File srcFile = new File(name);
//目标文件将要存储的文件夹
String destDir = "/Users/tensionxu/Desktop/haha";
//目标文件夹
File dir = new File(destDir);
//目标文件夹不存在,创建
if (!dir.exists()){
dir.mkdirs();
}
//目标文件的路径:File.separator文件分割符 '/',file.getName()源文件的名字
String destPath = destDir+File.separator+srcFile.getName();
//目标文件
File destFile = new File(destPath);
//如果目标文件不存在,创建文件
if (!destFile.exists()){
//到这里,文件就创建出来了,但是仅仅拷贝了个相同名字的文件,里面内容还没有拷贝
destFile.createNewFile();
}
//2.将源文件的内容拷贝到目标文件中
//创建字符输入流,用来读取源文件内容
InputStreamReader reader = new InputStreamReader(new FileInputStream(srcFile));
//创建字符输出流,用来向目标文件写内容
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(destFile));
// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
//字符输入流读数据
while ((len = reader.read(chs)) != -1) {
//字符输出流写文件
writer.write(chs,0,len);
}
// 释放资源
reader.close();
writer.close();
}
}
4.字符流操作要注意的问题
flush()和close()的区别:
- close()关闭流对象,关闭之前先刷新一次缓冲区,再关闭。关闭之后,流对象不可以继续再读写了。
- flush()仅仅刷新缓冲区,刷新之后,流对象还可以继续读写。
5.转换流 FileWriter和FileReader介绍
转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化我们的书写,转换流提供了对应的子类。
FileWriter写数据
FileReader读取数据
FileWriter
FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。可以通过以下几种构造方法创建需要的对象。
//在给出文件名的情况下构造 FileWriter 对象
public FileWriter(String fileName)
//fileName:文件路径/名字,append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。
public FileWriter(String fileName, boolean append)
//在给出 File 对象的情况下构造一个 FileWriter 对象。
public FileWriter(File file)
//file:文件,append:如果 append 参数为 true,则将字节写入文件末尾处,相当于追加信息。如果 append 参数为 false, 则写入文件开始处。
public FileWriter(File file, boolean append)
//构造与某个文件描述符相关联的 FileWriter 对象。fd:文件的描述对象
public FileWriter(FileDescriptor fd)
FileReader
FileReader 类从 InputStreamReader 类继承而来。该类按字符从文件中读取数据。可以通过以下几种构造方法创建需要的对象。
//在给出文件名的情况下构造 FileReader 对象
public FileReader(String fileName)
//在给出 File 对象的情况下构造一个 FileReader 对象。
public FileReader(File file)
//构造与某个文件描述符相关联的 FileReader 对象。fd:文件的描述对象
public FileReader(FileDescriptor fd)
案例
将“欢迎来到德莱联盟” 写入文件末尾(文件原来的内容保留),然后两个两个读取出来并打印
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
//读写文件的地址,原来里面的内容:黑色的眼睛,
String fileName = "/Users/tensionxu/Desktop/test.txt";
//用来读取数据
FileReader fr = new FileReader(fileName);
//用来写数据,append:true代表在原来文件的基础上续写,也就是数据会写在文件末尾
FileWriter fw = new FileWriter(fileName,true);
//写入内容
fw.write("欢迎来到德莱联盟");
// 释放资源
fw.close();
//每次读取两个字符
char[] chs = new char[2];
int len = 0;
while ((len = fr.read(chs)) != -1) {
System.out.println(new String(chs,0,len));
}
// 释放资源
fr.close();
}
}
6.使用转换流实现文件复制(文本)
案例分析:
实现文件复制,要求使用转换流:
将文件test.txt(里面内容:黑色的眼睛,欢迎来到德莱联盟)拷贝到haha文件夹下面,haha文件夹有可能不存在
- 需要将文件里面的内容全部拷贝,文件名称(test.txt)也需要拷贝
- 需要在一个目录下面创建一个文件,文件名称使用拷贝的(test.txt),文件里面的内容也使用拷贝的文件内容
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
//1. 拷贝文件名字并且创建对应的目标文件
// /Users/tensionxu/Desktop/test.txt:文件绝对路径,要拷贝的就是这个文件
String name = "/Users/tensionxu/Desktop/test.txt";
//创建一个源文件对象
File srcFile = new File(name);
//目标文件将要存储的文件夹
String destDir = "/Users/tensionxu/Desktop/haha";
//目标文件夹
File dir = new File(destDir);
//目标文件夹不存在,创建
if (!dir.exists()){
dir.mkdirs();
}
//目标文件的路径:File.separator文件分割符 '/',file.getName()源文件的名字
String destPath = destDir+File.separator+srcFile.getName();
//目标文件
File destFile = new File(destPath);
//如果目标文件不存在,创建文件
if (!destFile.exists()){
//到这里,文件就创建出来了,但是仅仅拷贝了个相同名字的文件,里面内容还没有拷贝
destFile.createNewFile();
}
//2.将源文件的内容拷贝到目标文件中
//创建字符输入流,用来读取源文件内容
InputStreamReader reader = new InputStreamReader(new FileInputStream(srcFile));
//创建字符输出流,用来向目标文件写内容
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(destFile));
// 一次读取一个字符数组
char[] chs = new char[1024];
int len = 0;
//字符输入流读数据
while ((len = reader.read(chs)) != -1) {
//字符输出流写文件
writer.write(chs,0,len);
}
// 释放资源
reader.close();
writer.close();
}
}
7. 字符缓冲流BufferedWriter和BufferedReader介绍
为了提高字符流读写的效率,引入了缓冲机制,进行字符批量的读写,提高了单个字符读写的效率。BufferedReader用于加快读取字符的速度,BufferedWriter用于加快写入的速度。
BufferedReader和BufferedWriter类各拥有8192个字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并放满缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。
BufferedReader
BufferedReader是为了提供读的效率而设计的一个包装类,它可以包装字符流。可以从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
构造方法:
//in:Reader对象
public BufferedReader(Reader in)
//in:Reader对象,sz:缓存的大小,默认是8192个字符
public BufferedReader(Reader in, int sz)
成员方法:
//读取单个字符。
public int read()
//将字符读入数组的某一部分。cbuf:字符数组,off:开始读取的起始位置,len:读取的字符个数
public int read(char[] cbuf, int off, int len)
//读取一个文本行。
public String readLine()
//跳过字符。n:跳过的数量
public long skip(long n)
//判断此流是否已准备好被读取。
public boolean ready()
//关闭该流并释放与之关联的所有资源。
public void close()
//标记流中的当前位置。
public void mark(int readAheadLimit)
//判断此流是否支持 mark() 操作(它一定支持)。
public boolean markSupported()
//将流重置到最新的标记。
public void reset()
1. 案例:
读取文件:一个数组一个数组的读取并打印
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
String filePath = "/Users/tensionxu/Desktop/test.txt";
//创建字符缓冲流BufferedReader
BufferedReader reader=new BufferedReader(
new FileReader(filePath)
);
//判断是否可以读取
if(!reader.ready()) {
//不能读取,结束此方法
System.out.println("文件流暂时无法读取");
return;
}
int len=0;
//每次读取3个字符
char[] cbuf=new char[3];
while((len=reader.read(cbuf, 0, cbuf.length))!=-1) {
System.out.println(new String(cbuf,0,len));
}
//关闭流
reader.close();
}
}
2. 案例:
读取文件:一行一行的读取并打印
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
/**
* 文件内容为3行
* 黑色的眼睛
* ,
* 欢迎来到德莱联盟
*/
String filePath = "/Users/tensionxu/Desktop/test.txt";
//创建字符缓冲流BufferedReader
BufferedReader reader=new BufferedReader(
new FileReader(filePath)
);
//判断是否可以读取
if(!reader.ready()) {
//不能读取,结束此方法
System.out.println("文件流暂时无法读取");
return;
}
String line = "";
//读取一行,需要注意的是:reader.readLine()方法返回的一行字符中不包含换行符,所以输出的时候要自己加上换行符。
while((line=reader.readLine())!=null) {
System.out.println(line);
}
//关闭流
reader.close();
}
}
BufferedReader比FileReader高级的地方在于这个,FileReader能一次读取一个字符,或者一个字符数组。而BufferedReader也可以,同时BufferedReader还能一次读取一行字符串。同时,BufferedReader带缓冲,会比FileReader快很多。
但是FileReader使用项目的编码来读取解析字符,不能指定编码,可能会出现编码问题,如果要指定编码可以使用包装InputStreamReader的BufferedReader。这样兼顾效率和编码。
BufferedWriter
构造方法:
//创建一个缓冲字符输出流,使用默认大小的输出缓冲区
public BufferedWriter(Writer out)
//创建一个缓冲字符输出流,使用给定大小的输出缓冲区
public BufferedWriter(Writer out, int sz)
成员方法:
//写入单个字符。
public void write(int c)
//写入字符数组的某一部分。cbuf:字符数组,off:写入的起始位置,len:写入字符的长度
public void write(char[] cbuf, int off, int len)
//写入字符串的某一部分。s:字符串,off:写入的起始位置,len:写入字符的长度
public void write(String s, int off, int len)
//写入一个行分隔符。
public void newLine()
//关闭此流,但要先刷新它。
public void close()
//刷新该流的缓冲。
public void flush()
3. 案例:
将字符串写入文件
黑色的眼睛(换行)
,(换行)
欢迎来到德莱联盟
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
String filePath = "/Users/tensionxu/Desktop/test.txt";
//创建字符缓冲流BufferedReader
BufferedWriter writer=new BufferedWriter(
new FileWriter(filePath)
);
writer.write("黑色的眼睛");
//写入一个行分隔符。
writer.newLine();
writer.write(",");
writer.newLine();
writer.write("欢迎来到德莱联盟");
//关闭流
writer.close();
}
}
8.使用字符缓冲流实现文件复制(文本)
案例分析:
实现文件复制,要求使用字符缓冲流:
将文件test.txt拷贝到haha文件夹下面,haha文件夹有可能不存在
- 需要将文件里面的内容全部拷贝,文件名称(test.txt)也需要拷贝
- 需要在一个目录下面创建一个文件,文件名称使用拷贝的(test.txt),文件里面的内容也使用拷贝的文件内容
案例代码:
public class Test {
public static void main(String[] args) throws Exception {
//1. 拷贝文件名字并且创建对应的目标文件
// /Users/tensionxu/Desktop/test.txt:文件绝对路径,要拷贝的就是这个文件
String name = "/Users/tensionxu/Desktop/test.txt";
//创建一个源文件对象
File srcFile = new File(name);
//目标文件将要存储的文件夹
String destDir = "/Users/tensionxu/Desktop/haha";
//目标文件夹
File dir = new File(destDir);
//目标文件夹不存在,创建
if (!dir.exists()){
dir.mkdir();
}
//目标文件的路径:File.separator文件分割符 '/',file.getName()源文件的名字
String destPath = destDir+File.separator+srcFile.getName();
//目标文件
File destFile = new File(destPath);
//如果目标文件不存在,创建文件
if (!destFile.exists()){
//到这里,文件就创建出来了,但是仅仅拷贝了个相同名字的文件,里面内容还没有拷贝
destFile.createNewFile();
}
//2.将源文件的内容拷贝到目标文件中
//创建字符输入流,用来读取源文件内容
BufferedReader reader = new BufferedReader(new FileReader(srcFile));
//创建字符输出流,用来向目标文件写内容
BufferedWriter writer = new BufferedWriter(new FileWriter(destFile));
String str = "";
while ((str = reader.readLine())!= null) {
//字符输出流写文件
writer.write(str);
writer.newLine();
}
// 释放资源
reader.close();
writer.close();
}
}