上一篇我们提到过,用字节流读取(文本文件)汉字打印在控制台上,会出现乱码,为解决这个问题引入了字符流
字符流是建立在字节流的基础上,能够提供字符层次的编码和解码
编码:将字符数据转换为字节数据(输入流--读数据);解码:将字节数据转换为字符数据(输出流--写数据)
类比:参考字符串的编码和解码
现在用字符流来读取处理不会出现乱码,是因为读取的不再是单个字节,而是能代表字符的多个字节,打印在控制台时,会通过编码表找到对应的字符。
注意1:默认采用Unicode编码,即在java里一个字符是由2个字节组成,所以对应的是0-65535之间(一个字节--0-255之间),会将指定格式的字符(对应的字节)转换为Unicode字符存储
注意2:指定的输入流的编码格式必须与要读的文本文件的编码格式一致
Reader和Writer类---抽象类----与编码方式有关类
字符输入流读数据的方式:
public int read():一次读取一个字符
public int read(char[] chs):一次读取一个字符数组
字符输出流写数据的方法:
public void write(int c):写单个字符
public void write(char[] cbuf):写一个字符数组
public abstract void write(char[] cbuf,int off,int len):写入字符数组的一部分
public void write(String str):写字符串
public void write(String str, int off,int len):写字符串的一部分
转换流----字符流与字节流的桥梁----处理流
InputStreamReader(InputStream in):字符转换输入流
说明:读取一个字符,实际上读取的是该字符在此编码下(自己设置编码方式)对应的字节,然后转换为java平台默认的Unicodeb编码格式的字节,然后为该字符分配Unicodeb编码格式对应字节的内存空间。
注意:那么计算机是如何识别字符的?打印在控制台不会出现乱码?通过编码表(由字符及其对应数值组成的一张表)来唯一识别字符
OutputStreamWriter(Outputstream out):字符转换输出流
说明:对于每一个写入的字符,都会把它转换成指定的字符编码或者默认的方式,也即向输出流写入对应字符编码的字节
注意:从转换流得到的字符流,它读取的字符必须在编码表可以查到,否则会出现乱码,对于像图片、视频这样的文件不适宜用字符流来处理
实例1 一次读取一个字符
package 测试2;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 字符输入流读数据的方式:
* public int read():一次读取一个字符
*/
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//1)创建字符输入流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"a.txt"));
//2)方式1:一次读取一个字符
/*
* 说明:好----Unicode编码:89(高八位)、125(低八位)
* 对应的Int值为22909
*/
int ch = 0 ;//read()返回值--每个字符对应的Unicode编码对应的字节的Int值
while((ch=isr.read())!=-1){
System.out.print(ch);
System.out.print((char)ch);
}
//释放资源
isr.close() ;
}
}
实例2 一次读取一个字符数组
package 测试2;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 字符输入流读数据的方式:
* public int read(char[] chs):一次读取一个字符数组
*
*/
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//1)创建字符输入流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"a.txt"));
//2)方式2:一次读取一个字符数组
char[] chs = new char[1024] ;//字符数组缓冲区
int len = 0 ;//字符缓冲区中读取字符的实际个数
while((len=isr.read(chs))!=-1){
System.out.println(new String(chs, 0, len));
}
/*
* 说明:当文本中的字符数超过1024个时,
* 比如说1025个,那么下次实际读取的字符数是一个
* 为了避免打印空白等情况采用了String(chs, 0, len)的形式
* 打印字符缓冲区中真正的字符数
*/
//释放资源
isr.close() ;
}
}
实例3 一次写入一个字符(文件复制)
package 测试2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//1)创建字符输入流、输出流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"a.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
"b.txt"));
//2)方式1:一次读取一个字符,写入一个字符
int ch = 0 ;//
while((ch=isr.read())!=-1){
osw.write(ch);//写入字符
}
//释放资源
isr.close() ;
osw.close();
}
}
实例4 一次写入一个字符数组(文件复制)
package 测试2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//1)创建字符输入流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream(
"a.txt"));
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(
"b.txt"));
//2)方式2:一次写入一个字符数组
char[] chs = new char[1024] ;//字符数组缓冲区
int len = 0 ;//字符缓冲区中读取字符的实际个数
while((len=isr.read(chs))!=-1){
osw.write(chs, 0, len);
}
//释放资源
isr.close() ;
osw.close();
}
}
注意:如果不采用平台默认的编码格式,例如采用utf-8的编码格式,会发现复制文件后,打开文件会出现乱码(不影响我们通过读取识别)
原因:就是系统的编码和程序的编码采用了不同的编码格式
使用字符转换流进行操作数据的时候:字节流+编码格式(默认GBK),在书写代码名称非常长,Java提供了中更简单的类--便捷类
说明:便捷类是InputStreamReader和OutputStreamWriter的子类
FileWrite和FileReader类
特点(局限性):只能按照本地平台的字符编码(GBK),不能用户指定字符编码类型
实例5
package org.westos_09;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
//1)封装数据源和目的地
FileReader fr = new FileReader("a.txt") ;
FileWriter fw = new FileWriter("b.txt") ;
//一次读取一个字符数组
char[] chs = new char[1024] ;
int len = 0 ;
while((len=fr.read(chs))!=-1){
fw.write(chs, 0, len) ;
fw.flush() ;
}
//关闭资源
fw.close() ;
fr.close() ;
}
}
由于便捷类的性能达不到人们的需求,为了提高读写速度,Java就提供了一个字符缓冲流的类
BufferedReader:字符缓冲输入流
BufferedWriter:字符缓冲输出流
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
通过构造方式可以指定缓冲区的大小,或者接受默认的大小
常见读取方式:读取字节、读取字节数组
构造方式:
public BufferedWriter(Writer out):创建默认缓冲区大小的一个字符缓冲输出流
关于字符缓冲输入流的特有功能:
BufferedWriter:
public void newLine():写入一个换行符号(等价==“\r\n”)
BufferReader:
public String readLine():一次读取一行(内容,字符串)
实例6 缓冲区读写
package org.westos_10;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedDemo {
public static void main(String[] args) throws IOException {
write();
read();
}
private static void read() throws FileNotFoundException, IOException {
//代码重复度高,使用循环改进
String line = null ;
while((line=br.readLine())!=null){
System.out.println(line);
}
//释放资源
br.close() ;
}
private static void write() throws IOException {
//写数据
//创建一个字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("bw2.txt")) ;
//写数据
for(int x = 0 ; x <10 ; x ++){
bw.write("hello"+x) ;
//没有使用这个方法之前:使用写入换行符号
//bw.write("\r\n") ;
//使用特有功能
bw.newLine() ;
//刷新该流
bw.flush() ;//必须刷新!!!
}
//释放资源
bw.close() ;
}
}
字符缓冲区流作用机理:由于带有缓冲区把一批数据写入到缓冲区内,当缓冲区满的时候,才把缓冲区的数据写入到字符缓冲流中
好处:避免每次都执行物理写操作,从而提高了I/O操作的效率
缺点:字符缓冲区的数据不满,会一直等待,如果不刷新就无法从缓冲区把数据写入到文件中
因此必须强制刷新