字符流

字符流存在的意义:

字节流都文本文件数据时候:
— 一个汉字存储如果是GBK编码,占用2个字节,如果是UTF-8编码,占用3个字节
— 无论采用什么编码方式存储中文,第一个字节永远都是负数(-xx) 这样编译器就知道该字节为中文字符并进行拼接

字符流 = 字节流+编码表

// 定义一串字符串
// String s = “abc”; //[97, 98, 99]
// UTF-8编码,占用3个字节
String s = “中国”; // [-28, -72, -83, -27, -101, -67]
// 使用String类的getBytes()方法获取对应的字符串字节数组
byte[] _bytes = s.getBytes();
// GBK编码,占用2个字节 输出:[-42, -48, -71, -6]
// 使用String类的getBytes()方法中的构造方法指定编码
byte
[] gbks = s.getBytes(“GBK”);
// 使用Arrays类中的toString()方法重写字节数组为String
System._out
.println(_Arrays._toString(_gbks))_;

编码表

基础知识:

  1. 1. 计算机中存储的信息都是用二进制表示的, 我们在屏幕在看到的英文和汉字等字符都是二进制数转换之后的结果
  2. 1. 按照某种规则,将字符存储在计算机中,称为编码。 反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。这里强调一下:按照A编码存储,必须按照A编码解析,这样才能显示正确的文本符号。否则就会导致乱码现象
  3. 1. 字符编码: 就是一套自然语言的字符与二进制数之间的对应规则(A,65)

字符集:

  1. 1. 字符集是指多个字符的集合。不同的字符集包含的字符个数不一样、包含的字符不一样、对字符的编码方式也不一样。例如GB2312是中国国家标准的简体中文字符集,GB2312收录简化汉字(6763个)及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。而ASCII字符集只包含了128字符,这个字符集收录的主要字符是英文字母、阿拉伯字母和一些简单的控制字符。
  2. 1. 常用的字符集有 GBK字符集、GB18030字符集、Big5字符集、Unicode字符集等。


ASCII字符集

  1. 1. 上个世纪60年代,美国制定了一套字符编码规则,对英语字符与二进制位之间的关系做了统一规定,这编码规则被称为ASCII编码,一直沿用至今。
  2. 1. ASCII编码一共规定了128个字符的编码规则,这128个字符形成的集合就叫做ASCII字符集。在ASCII编码中,每个字符占用一个字节的后面7位,最前面的1位统一规定为0。在ASCII编码中,0~31 是控制字符如换行回车删除等,32~126 是可打印字符,可以通过键盘输入并且能够显示出来。


GBXXX字符集

  1. 1. GB2312编码方案出现最早,占用2个字节,但是能表示的字符较少;
  2. 1. GBK也占用2个字节,采用了不同的编码方式,对GB2312进行了扩展,增加了近20000个新的汉字(包括繁体字)和符号;
  3. 1. GB18030采用变长编码,可以是1个字节、2个字节和4个字节。是对GB2312GBK的扩展,完全兼容两者。

总结:GB2312是我们中文自己的字符集,是国家标准。由于不能包含全部汉字,后面有扩展出现了GBK,GBK是固定长度编码的字符集,后面又出现了变长编码的GB18030字符集。

Unicode字符集

  1. 1. ASCII码字符集,总共才能容纳256个字符,对于全世界各国语言来说,很难全部包含在内,所有后来就出现了Unicode字符集。
  2. 1. Unicode字符集是一个很大的字符集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母AinU+0041表示英语的大写字母AU+4E25表示汉字“严”。


UTF-8编码

  1. 1. 互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是:UTF-8编码是Unicode的实现方式之一。
  2. 1. UTF-88-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码规则,又称万国码。由Ken Thompson1992年创建。现在已经标准化为RFC 3629UTF-814个字节编码Unicode字符。用在网页上可以统一页面显示中文简体繁体及其它语言(如英文,日文,韩文)
  3. 1. UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度(UTF-8编码可以容纳2^21个字符,总共200多万个字符)。
  4. 1. 下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围 | UTF-8编码方式
UTF字节数 (十六进制) | (二进制)
一个字节 0000 0000-0000 007F | 0xxxxxxx
两个字节 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
三个字节 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
四个字节 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

字符串中的编码以及解码问题

编码:(IDEA平台编写)

  1. 1. byte[] getBytes() :使用平台的默认字符集合将该String编码为一系列字节,将结果存储到新的字节数组中
  2. 1. byte[] getBytes(String charsetName):使用指定的字符集合(参数:GBK,UTF-8...)将String编码为一系列字节,将结果存储到新的字节数组中

解码:(IDEA平台编写)

  1. 1. String(byte[] bytes) :使用平台的默认字符解码指定的字节数组来构造新的String
  2. 1. String(byte[] bytes,String charsetName):使用指定的字符解码指定的字节数组(参数:GBK,UTF-8...)来构造新的String

public static void main(_String[] args) throws IOException {
String s = “中国”;
// 编码:
// byte[] getBytes() :
// 使用平台的默认字符集合将该String编码为一系列字节,将结果存储到新的字节数组中
// UTF-8:编码 [-28, -72, -83, -27, -101, -67]
byte
[] bytes = s.getBytes();
System._out
.println(_Arrays._toString(_bytes));
// byte[] getBytes(String charsetName):
// 使用指定的字符集合(参数:GBK)将String编码为一系列字节,将结果存储到新的字节数组中
// GBK编码,占用2个字节 输出:[-42, -48, -71, -6]
byte
[] gbks = s.getBytes(“GBK”);
// 使用Arrays类中的toString()方法重写字节数组为String
System._out
.println(_Arrays._toString(_gbks));
// 解码:
// 使用String(byte[]:
// 使用平台的默认字符解码指定的字节数组来构造新的String
String s1 = new String
(bytes);
System._out
.println(_s1);
// String(byte[] bytes,String charsetName):
// 使用指定的字符解码指定的字节数组(参数:GBK)来构造新的String
String s2 =new String
(gbks, “GBK”);
System._out
.println(_s2); }_

字符流抽象基类

Reader: 字符输入流的抽象类

Writer: 字符输出流的抽象类

字符流中和编码问题相关的两个类

InputStreamReader: 字符输入流

  1. 1. 具体的类extends Reader
  2. 1. 是字节流到字符流的桥梁 它读取字节,并使用指定的**charset**将其解码为字符
  3. 1. 指定字符集:可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

OutputStreamReader: 字符输出流

  1. 4. 具体的类extends Writer
  2. 4. 是字符流到字节流的桥梁 ,使用指定的**charset**将写入的字符编码为字符
  3. 4. 指定字符集:可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集

Tips: 这两中方法是将字符流数据通过字节流FileOutputStream写入数据

public static void main(_String[] args) throws IOException {
// OutputStreamWriter(OutputStream out): 创建一个使用默认字符编码的OutputStreamWriter
// OutputStream是一个抽象类
// FileOutputStream fos = new FileOutputStream(“D:\PgProject\test11\test.txt”);
// OutputStreamWriter osw = new OutputStreamWriter(fos);
OutputStreamWriter osw = new OutputStreamWriter
(new FileOutputStream(“D:\PgProject\test11\test.txt”));
osw.write
(“中国”);
osw.close
();
// OutputStreamWriter(OutputStream out, Charset cs) 创建一个使用给定字符集的OutputStreamWriter。
OutputStreamWriter osw1 = new OutputStreamWriter
(new FileOutputStream(“D:\PgProject\test11\test.txt”),”GBK”);
osw1.write
(“加油”);
osw1.close
();
// 使用GBK与编译器UTF-8默认的编码冲突,无法读取,使用InputStreamReader读取
InputStreamReader isr = new InputStreamReader
(new FileInputStream(“D:\PgProject\test11\test.txt”));
// 指定字符集与OutputStreamWriter字符集一致
InputStreamReader isr1 = new InputStreamReader
(new FileInputStream(“D:\PgProject\test11\test.txt”),”GBK”);
// 字符流读取字符流数据
// 一次读取一个字符数据, 与字节流读数据相同
int ch;
while
((ch = isr1.read()) != -1){
System._out.print((_char) ch); }
isr1.close(); }_

字符流中写数据的5种方式

方法体与字节流基本一致:

  1. - void **write(int c) 写一个字符**
  2. - void **write(char[] cbuf) 写一个字符数组**
  3. - void **write(char[] cbuf, int off,int len) 写一个字符数组的一部分**
  4. - **write(**[**String**](../../java/lang/String.html)** str) 写一个字符串**
  5. - **write(**[**String**](../../java/lang/String.html)** str, int off,int len) 写一个字符串的一部分**
  6. - void **flush() 刷新流**
  7. - void **close() 先刷新流后关闭流**
  • Tips :
    • 使用字符流写入数据必须使用flush() 否则无法写入,因为字符流相对于字节流有缓冲
    • flush() 刷新流后可以接着写入数据 ,但是close()关闭流无法继续写入

public static void main(_String[] args) throws IOException {
// OutputStreamWriter(OutputStream out): 创建一个使用默认字符编码的OutputStreamWriter
OutputStreamWriter ops = new OutputStreamWriter
(new FileOutputStream(“D:\PgProject\test11\test.txt”));
// void write(int c)写一个字符
ops.write
(97);
// void flush() 刷新流
ops.flush
();
ops.write
(98);
ops.flush
();
ops.write
(99);
// flush() 刷新流后可以接着写入数据,但是close()关闭流无法继续写入
ops.flush
();
// 定义一个字符流数组
char
[] chars = {‘a’,’b’,’c’,’d’,’e’};
// void write(char[] cbuf)写一个字符数组
ops.write
(chars);
// void write(char[] cbuf,int off,int len)写一个字符数组的一部分
ops.write
(chars,0,chars.length); // abcde
ops.write
(chars,1,3); // bcd
// write(String str) 写一个字符串
ops.write
(“abcde”);
// write(String str,int off,int len)写一个字符串的一部分
ops.write
(“abcde”,0, “abcde”.length()); // abcde
ops.write
(“abcde”,1,3); // bcd
// close()关闭流无法继续写入
ops.close
(); }_

字符流中读数据的2种方式

方法体与字节流基本一致:

  1. - void **read(int c) 一次读一个字符**
  2. - void **read(char[] cbuf) 一次读一个字符数组数据**

public static void main(_String[] args) throws IOException {
// InputStreamReader(InputStream in): 创建一个使用默认字符编码的InputStreamReader
InputStreamReader isr = new InputStreamReader
(new FileInputStream(“D:\PgProject\test11\test.txt”));
// void read(int c) 一次读一个字符
int ch;
while
((ch= isr.read())!=-1){ System._out.print((_char) ch); }
// void read(char[] cbuf) 一次读一个字符数组数据
char
[] chars = new char[1024];
int len;
while
((len = isr.read(chars))!=-1){
// String(String str,int off,int len) 读一个字符串的一部分
System._out
.print(_new String(chars,0,len)); }
// 释放资源
isr.close
(); }_

案例: 复制Java文件

需求: 把模块目录下的”CopyAvi.java” 复制到模块目录下的”Copy.java”
思路:

  1. 1. 根据数据源创建字符输入流对象
  2. 1. 根据目的地创建字符输出流对象
  3. 1. 读写数据,复制文件
  4. 1. 释放资源

public static void main(_String[] args) throws IOException {
// 根据数据源创建字符输入流对象
InputStreamReader isr = new InputStreamReader
(new FileInputStream(“D:\PgProject\test11\src\com_21\CopyAvi.java”));
// 根据目的地创建字符输出流对象
OutputStreamWriter osw = new OutputStreamWriter
(new FileOutputStream(“D:\PgProject\test11\Copy.java”));
// 读写数据,复制文件
// 两种方式写入数据 一次一个字符
int ch;
while
((ch= isr.read())!= -1){ osw.write(ch); }
// 一次写入一组字符
char
[] chars = new char[1024];
int len;
while
((len = isr.read(chars))!=-1){ osw.write(new String(chars,0,len)); }
// 释放资源
isr.close
();
osw.close
();}_

案例: 复制Java文件(改进版本)

需求: 把模块目录下的”CopyAvi.java” 复制到模块目录下的”Copy.java”
分析:

  1. 1. **转换流的名字类比较长, 而我们操作都是按照本地默认编码实现的, 所以为了简化书写, 转换流提供了对应的子类**
  2. 1. **FileWriter(String fileName) 用于写入字符文件的便捷类,构造一个给定文件名的FileWriter对象**
  3. 1. **FileReader(String fileName) 用于读取字符文件的便捷类 创建一个新的 FileReader ,给定要读取的文件的名称**
  4. 2. 可以使用提供的子类写入,但是如果要使用字符流中的编码问题还是需要使用InputStreamReaderOutputStreamWriter这个父类
  5. 2. 数据源和目的地分析
  6. 1. 数据源: test11\\CopyAvi.java --**读数据 --Reader --InputStreamReader--FileReader**
  7. 1. 目的地: test11\\Copy.java -- **写数据 -- Writer -- OutputStreamWriter -- FileWriter**

思路:

  1. 1. 根据数据源创建字符输入流对象
  2. 1. 根据目的地创建字符输出流对象
  3. 1. 读写数据,复制文件
  4. 1. 释放资源

public static void main(_String[] args) throws IOException {
// 根据数据源创建字符输入流对象
FileReader fr = new FileReader
(“D:\PgProject\test11\src\com_21\CopyAvi.java”);
// 根据目的地创建字符输出流对象
FileWriter fw = new FileWriter
(“D:\PgProject\test11\Copy.java”);
// 读写数据,复制文件
char
[] chars = new char[1024];
int len;
while
((len = fr.read(chars))!=-1){
fw.write(chars,0,len); }
// 释放资源
fw.close
();
fr.close
(); }_

字符缓冲流

BufferedReader extends Reader
— 字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
— 可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
— BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流
— BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流
BufferedWriter extends Writer
— 将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
— 可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途。
— BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
— BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。
public static void main(_String[] args) throws IOException {
// BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流
// FileWriter fw = new FileWriter(“D:\PgProject\test11\test.txt”);
// BufferedWriter bw = new BufferedWriter(fw);
BufferedWriter bw = new BufferedWriter
(new FileWriter(“D:\PgProject\test11\test.txt”));
bw.write
(“hello\r\n”);
bw.write
(“world\r\n”);
bw.write
(“java”);
bw.close
();
// BufferedReader(Reader in, int sz)创建使用指定大小的输入缓冲区的缓冲字符输入流
BufferedReader br = new BufferedReader
(new FileReader(“D:\PgProject\test11\test.txt”));
int ch;
while
((ch= br.read())!=-1){ System._out.print((_char)ch); }
br.close();
char
[] chars = new char[1024];
int len;
while
((len = br.read(chars))!=-1){ System._out.print(_new String(chars,0,len)); }
br.close(); }_

案例: 复制Java文件(字符缓冲流改进版)

需求: 把模块目录下的”CopyAvi.java” 复制到模块目录下的”Copy.java”
思路:

  1. 1. 根据数据源创建字符缓冲输入流对象
  2. 1. 根据目的地创建字符缓冲输出流对象
  3. 1. 读写数据,复制文件
  4. 1. 释放资源

public static void main(_String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new FileReader(“D:\PgProject\test11\src\com_21\CopyAvi.java”));
BufferedWriter bw = new BufferedWriter
(new FileWriter(“D:\PgProject\test11\Copy.java”));
int ch;
while
((ch =bf.read())!=-1){ bw.write(ch); }
char[] chars = new char[1024];
int len;
while
((len = bf.read(chars))!=-1){ bw.write(chars,0,len); }
bw.close();
bf.close
(); }_

字符缓冲流特有功能

BufferedWriter
void newLine() 写一行行分隔符。行分隔符字符串由系统属性line.separator定义
— Windows系统 单个换行符(’\ r\ n’)字符
— Linux系统 单个换行符(\ n’)字符
— MAC系统 单个换行符(’\ r’)字符

BufferedReader
public String readLine()读一行文字。包含行的内容的字符串,不包括任何行终止字符(例如换行符等)如果已达到流的末尾,则为null
public static void main(_String[] args) throws IOException {
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter
(new FileWriter(“D:\PgProject\test11\test.txt”));
// 写数据
for
(int i = 0; i < 10; i++) {
bw.write(“hello”+i);
// bw.write(“\r\n”); 只适用于Windows系统
// void newLine() 写一行行分隔符
bw.newLine
();
// 写入数据后需要flush才能更新数据
bw.flush
(); }
// 释放资源
bw.close
();
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader
(new FileReader(“D:\PgProject\test11\test.txt”));
// String readLine()读一行文字
/ 第一次读取数据
System.out.println(br.readLine()); // hello0
第二次读取数据
System.out.println(br.readLine()); // hello1
第三次读取数据
System.out.println(br.readLine()); // null
第四次读取数据
System.out.println(br.readLine()); // null
/
// 使用while循环语句改进读取数据
String line;
// readLine()只读内容,不读取换行符
while
((line = br.readLine())!=null){ System._out.print(_line); // hello0hello1 } }_

案例: 复制Java文件(字符缓冲流特有功能改进版)

需求: 把模块目录下的”CopyAvi.java” 复制到模块目录下的”Copy.java”
思路:

  1. 1. 根据数据源创建字符缓冲输入流对象
  2. 1. 根据目的地创建字符缓冲输出流对象
  3. 1. 读写数据,复制文件
  4. 1. 释放资源

public static void main(_String[] args) throws IOException {
// 根据数据源创建字符缓冲输入流对象
BufferedReader br = new BufferedReader
(new FileReader(“D:\PgProject\test11\src\com_21\CopyAvi.java”));
// 根据目的地创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter
(new FileWriter(“D:\PgProject\test11\Copy.java”));
// 读写数据,复制文件,一次读取一个字符串
String Line;
while
((Line = br.readLine())!=null){
// 使用特有的字符缓冲流方法写数据三个步骤
// 1. 写入数据
bw.write
(Line);
// 2. 使用特有的字符缓冲流方法写数据换行方法,否则数据在一行上
bw.newLine
();
// 3. 最后使用刷新方法没写一行刷新一下继续写入
bw.flush
(); }
// 读写数据,复制文件,一次读取一个字符串
// 释放资源
bw.close
();
br.close
(); }_

IO流小结

字节流:

字节输入流 InputStream

字节输出流 OutputStream

  1. 1. **InputStream(抽象类)-- 使用FileInputStreamBudderedInputStream(缓冲输入流)实现**
  2. 1. **OutputStream(抽象类)--使用FileOutputStream实现和BudderedOutputStream(缓冲输出流)实现**
  3. 1. **字节流读数据的两种方式**
  4. 1. **int read() 一次读取一个字节**
  5. 1. **int read(byte[] bys) 一次读取一个字节数组**
  6. 4. **字节流写数据的两种方式**
  7. 1. **void write() 一次写一个字节**
  8. 1. **void wtire(byte[] bys,int index, int len) 一次写一个字节数组**

TIPS:字节流可以赋值任意文件数据,有4中方式一般采用字节缓冲流一次读写一个字节数组的方式

字符流:

字符输入流 Reader

字符输出流 Writer

  1. 1. **Reader(抽象类) -- 使用InputStreamReader(FileReader)和BudderedReader(缓冲输入流)实现**
  2. 1. **BufferedReader 缓冲字符输入流特殊方法 -String readLine(): 一次读取一个字符串**
  3. 1. **InputStreamReader书写过长可以使用子类FileReader,但是涉及编码问题还是使用父类**
  4. 2. **Writer(抽象类)--使用OutputStreamWriter(FileWriter)和BudderedWriter(缓冲输出流)实现**
  5. 1. **BufferedWriter 缓冲字符输入流特殊方法 -**
  6. 1. **void newLine(): 写一个换行符**
  7. 1. **void write(String line): 一次写一个字符串**
  8. 1. **使用特殊类写入字符串有三个步骤 **
  9. 1. **void write()写入 **
  10. 1. **void newLine() 换行**
  11. 1. **void flush() 刷新**
  12. 2. **OutputStreamWriter书写过长可以使用子类FileWriter,但是涉及编码问题还是使用父类**
  13. 3. **字符流读数据的两种方式**
  14. 1. **int read() 一次读取一个字符**
  15. 1. **int read(char[] chs)一次读取一个字符数组**
  16. 4. **字符流写数据的两种方式**
  17. 1. **void write(int ch) 一次写一个字符**
  18. 1. **void wtire(char[] chs,int index, int len)一次写一个字符数组**

TIPS:字符流只能复制文本数据, 有5种方式, 一般采用字符缓冲流的特有功能