为什么出现字符流
需求:字节流读文本文件数据 由于字节流操作中文不是特别方便,所以Java就提供字符流。字符流=字节流+编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?如下 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
为什么出现字符流的练习
xxxxxxxxxx
package ch19;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
public class a_3_1测试 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt");
//一次读取一个字节数据
int by;
while ((by=fis.read())!=-1){
System.out.print((char) by);//不换行输出
}
//可以正常读取英文或数字
//但是上面的代码读取不了中文
//原因:一个汉字的存储,如果是GBK编码则占2个字节。如果是UTF-8编码则占用3个字节。一次只读一个字节就会导致中文数据出现乱码
System.out.println("");//这行的作用只是换行,因为上面的输出是print,为了方便后续的输出,我们就换行一下
System.out.println("-----------------------");
fis.close();
//----------------------------------------------------------------------------------------------------------------
//上面反映的问题就是字节流不方便读取带有中文的文本,解决:使用字符流,如下
//我们可以让系统当我们我们读取到一个汉字的字节,就把拼接成一个汉字,那系统是如何知道读取的是汉字还是非汉字的字节呢,如下
String s = "abc";
byte[] bys = s.getBytes();//getBytes方法可以得到字符串对应的字节数组
System.out.println(Arrays.toString(bys));//输出字节数组的数据。输出[97, 98, 99]
System.out.println("-----------------------");
String s2 = "汉字";
byte[] bys2 = s2.getBytes();//默认是UTF-8编码
System.out.println(Arrays.toString(bys2));//输出[-26, -79, -119, -27, -83, -105]。前面三个字节是"汉",后面三个字节是"字"
System.out.println("-----------------------");
//上面的汉字对应的是UTF-8编码,我们改成GBK编码,即指定汉字的编码,如下
String s3 = "汉字";
byte[] bys3 = s3.getBytes("GBK");
System.out.println(Arrays.toString(bys3));//输出[-70, -70, -41, -42]。前面两个字节是"汉",后面两个字节是"子"
//总结:汉字对应的字节前面是负号,所以我们可以先判断正负,再判断编码,就可以区分汉字、非汉字、UTF-8编码、GBK编码
}
}
编码表
基础知识: 1、计算机中存储的信息都是用二进制数表示的。我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果 2、按照某种规则,将字符存储到计算器中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。这里强调一下:按照 A编码存储,就必须按照A编码解析,这样才显示正确的文本符号,否则就会导致乱码现象 3、字符编码就是一套自然语言的字符与二进制数之间的对应规则,比如A对应的是65
什么是字符集: 1、是一个系统支持的所有字符的结合,包括各个国家文件、标点符号、圆形符号、数字等 2、计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码,常见的字符集有ASCII、GBXXX、Unicode
什么是ASCII字符集:是基于拉丁字母的一套电脑编码系统,主要包括控制字符、可显示字符 基本ASCII字符集:使用7位表示一个字符,共128个字符 扩展ASCII字符集:使用8位表示一个字符,共256个字符
什么是GBXXX字符集,即GB系列的字符集 1、GB2312:简体中文码表。一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就可以表示一个汉字 2、GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,采用双字节编码方案(实际开始中常用) 3、GB18030:最新的中文码表。采用多字节编码,每个汉字可以由1个、2个、4个字节组成
什么是Unicode字符集: 1、为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号、文件。 其中有三种编码方案,UTF-8、UTF-16、UTF32.最为常用的是UTF-8编码 2、UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用中优先采取的编码(实际开始中常用) 互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用1至4为每个字符编码
编码规则 1、28个US-ASCII字符,只需一个字节编码 2、拉丁文等字符,需要两个字节编码 3、大部分常用中文或非中文字,使用三个字节编码 4、其他极少使用的Unicode辅助字符,使用四字节编码
小结:采用何种规则编码,就要采用对应规则来解码,否则就会出现乱码
字符串中的编码解码问题
字符串中的编码,我们需要用到两种方法,如下 byte[] getBytes():使用平台(比如我们这里用的是IDEA平台)的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中。默认编码 byte[] getBytes(String charsetName):使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中。指定编码
字符串中的解码,我们需要用到两种构造方法,如下 String(byte[] bytes):通过使用平台(比如我们这里用的是IDEA平台)的默认字符集解码指定的字节数组来构造新的String String(byte[] bytes,String charsetName:通过指定的字符集解码指定的字节数组来构造新的String
注意:采用何种规则来编码,就要采用对应规则来解码,否则就会出现乱码
字符串中编码解码问题的练习
xxxxxxxxxx
package ch19;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class a_5_1测试 {
public static void main(String[] args) throws UnsupportedEncodingException {
//定义一个字符串
String s = "中国";
String a = "你好";
//-----------------------------------------------------------------------------------------------------------------
//第一种编码方法 byte[] getBytes()
byte[] bys = s.getBytes();
System.out.println(Arrays.toString(bys));//输出[-28, -72, -83, -27, -101, -67]
//该方法的默认编码格式为UTF-8,验证如下,看上下输出是否一样
byte[] bys2 = s.getBytes("UTF-8");//选中getBytes,按Alt+Enter抛出异常。这行是第二种解码方法哦
System.out.println(Arrays.toString(bys2));//输出[-28, -72, -83, -27, -101, -67]
System.out.println("-------------------------");
//第二种遍码方法 byte[] getBytes(String charsetName)
//比如我们指定解码为GBK编码格式,如下
byte[] bys3 = a.getBytes("GBK");
System.out.println(Arrays.toString(bys3));//输出[-60, -29, -70, -61]
System.out.println("-------------------------");
//-----------------------------------------------------------------------------------------------------------------
//第一种解码方法 String(byte[] bytes)
String ss = new String(bys);
System.out.println(ss);
System.out.println("--------------------------");
//第二种解码方法 String(byte[] bytes,String charsetName
String aa = new String(bys3,"GBK");//我们的bys3是通过GBK编码格式编码的,在这里解码的时候必须使用同编码格式
//String aa = new String(bys3,"UTF-8");//注意这行是典型的错误,解码和编码的编码格式不一致,无法解码
System.out.println(aa);
}
}
字符流中的编码解码问题
字符流抽象基类: 1、Reader:字符输入流的抽象类 2、Writer:字符输出流的抽象类
字符流中和编码解码问题相关的两个类: 1、InputStreamReader。看后缀我们可以判断该类是字符输入流,也就是可以把后缀理解为基类 2、OutputStreamWriter。看后缀我们可以判断该类是字符输出流,也就是可以把后缀理解为基类
InputStreamReader类在java.io包下。该类是具体类,继承自Reader类 该类是从字节流到字符流的桥梁:该类读取字节,并使用指定的charset将其解码为字符,该类可以指定字符集\
OutputStreamWriter类在java.io包下。该类是具体类,继承自Writer类 该类是字符流到字节流的桥梁:使用指定的字符集charset将写入的字符编码为字节,该类可以指定字符集
字符流中编码解码问题的练习
xxxxxxxxxx
package ch19;
import java.io.*;
//写数据的结果去对应的文件查看,读数据的结果直接在控制台就可查看
public class a_6_1测试 {
public static void main(String[] args) throws IOException {
//写数据
//默认字符编码,默认就是UTF-8
//先根据OutputStreamWriter的构造方法来创建字符输出流对象。选中FileOutputStream,按Alt+Enter,抛出异常
//FileOutputStream fos = new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt");
//OutputStreamWriter osw = new OutputStreamWriter(fos);
//上面那行可以合并为一行,如下
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt"));
//首先需要写数据,即用字符输出流OutputStreamWriter
osw.write("中国123zbc你好世界321def");
//释放资源
osw.close();
//------------------------------------------------------------------------------------------------------------------
//写数据
//指定字符编码,这里指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos6.txt"),"GBK");
osw2.write("中国123zbc你好世界321def");//则fos6.txt文件里面的"中国"是乱码的,因为我们IDEA平台使用的编码是UTF-8,两者编码不一致就会出现乱码
//注意,上面那行的乱码只是我们人类看不懂,机器是可以看懂的,即机器可以使用对应的编码格式读取,比如这里的编码格式是GBK,如下读数据
osw2.close();
//-------------------------------------------------------------------------------------------------------------------
//读数据
//默认字符编码,默认就是UTF-8,只能读UTF-8写入的数据,如下
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt"));
//字节流有两种读数据的方式,如下
//第一种方式,一次读取一个字符数据
int ch;
while ((ch=isr.read())!=-1){
System.out.print((char)ch);//输出中国
}
isr.close();
System.out.println("");//这行的作用仅仅是换行,避免跟下面的输出结果混合在一起
System.out.println("----------------------");
//读数据
//指定字符编码,这里指定GBK,只能读GBK写入的数据
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos6.txt"),"GBK");
//第一种方式,一次读取一个字符数据
int ch2;
while ((ch2=isr2.read())!=-1){
System.out.print((char)ch2);//输出中国
}
isr2.close();
System.out.println("");//这行的作用仅仅是换行,避免跟下面的输出结果混合在一起
System.out.println("----------------------");
//---------------------------------------------------------------------------------------------------
//读数据
//默认字符编码
InputStreamReader isr3 = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt"));
//第二种方式,一次读取一个字符数组数据
char[] chs = new char[1024];
int len;
while ((len= isr3.read(chs)) !=-1 ){
System.out.print(new String(chs,0,len));
}
isr3.close();
System.out.println("");//这行的作用仅仅是换行,避免跟下面的输出结果混合在一起
System.out.println("----------------------");
//读数据
//默认字符编码
InputStreamReader isr4 = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos5.txt"));
//第二种方式,一次读取一个字符数组数据
char[] chs2 = new char[1024];
int len2;
while ((len2= isr4.read(chs2)) !=-1 ){
System.out.println(new String(chs2,0,len2));
}
isr4.close();
}
}
字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写一个字符数组 |
void write(char[] cbuf,int off,int len) | 写字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str,int off,int len) | 写一个字符串的一部分 |
方法名 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会主动先刷新流。一旦关闭就不能写数据 |
字符流写数据5种方式的练习
xxxxxxxxxx
package ch19;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
//写数据的结果不是在控制台查看,要去对应文件看
public class a_7_1测试 {
public static void main(String[] args) throws IOException {
//字符输出流的构造方法OutputStreamWriter(OutputStream out),默认的编码,即UTF-8
//使用构造方法,创建字符流对象,如下。报红线就选中它,按Alt+Enter,抛出异常
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos7.txt"));
//第一种写数据的方式 void write(int c) 写一个字符
osw.write(97);//很奇怪为什么不会把数据写到fos7.txt文件。原因:该数据写在了缓存区。解决:使用字符输出流的flush方法,作用是刷新流,如下
//数据停在缓冲区的本质原因:字符流无法直接写数据,是通过该字符流底层的FileOutputStream字节流写入的数据。总结:字节流没有缓冲区,字符流有缓冲区
osw.flush();//此时输出a,即正常写入了数据
//如果此时再往fos7.txt文件里面写如一个数据,不调用flush方法,则同理数据不会被写入,如下
osw.write(98);//要想该行的数据比写入,则要继续调用flush方法,如下
osw.flush();
//如果没有调用flush方法刷新流,直接释放资源,那缓冲区的数据会怎么样,如下、
osw.write(99); //现在99 对应的数据在缓冲流,没有fos7.txt写进文件
osw.close();//直接释放资源。很奇怪数据最后就写进去了,原因:close方法在释放资源之前,会主动刷新一次缓存流,所以数据就写进文件了
//---------------------------------------------------------------------------------------------------------------------
//先创建字符流对象
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\java.txt"));
//第二种写数据的方式 void write(char[] cbuf) 写一个字符数组
//再定义一个字符数组
char[] chs2 = {'a','b','c','d','e'};
//写数据
osw2.write(chs2);//写入abcde
//不用调用flush刷新缓冲流,因为close会帮我们完成这步,如下
//释放资源
osw2.close();
//--------------------------------------------------------------------------------------------------------------------
//先创建字符流对象
OutputStreamWriter osw3 = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos8.txt"));
//第三种写数据的方式 void write(char[] cbuf,int off,int len) 写字符数组的一部分
//再定义一个字符数组
char[] chs3 = {'a','b','c','d','e'};
//osw3.write(chs3,0,chs3.length);//从chs3数组的索引0的数据开始写,把chs3里面的全部数据都写进对应文件
//当然也可以写几个数据,比如只把chs3数组里面的从0索引开始,共把3个数据写入对应文件,如下
osw3.write(chs3,0,3);//这一行和48行不能同时解开注释,不然报错,只能解开其中一个
//释放资源
osw3.close();
//------------------------------------------------------------------------------------------------------------------
//先创建字符流对象
OutputStreamWriter osw4 = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos9.txt"));
//第三种写数据的方式 void write(String str) 写一个字符串
//写数据
osw4.write("abcde你好");
//释放资源
osw4.close();
//-----------------------------------------------------------------------------------------------------------------
//先创建字符流对象
OutputStreamWriter osw5 = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos10.txt"));
//第三种写数据的方式 void write(String str,int off,int len) 写一个字符串的一部分
//再定义一个字符数组
//osw5.write("abcde你好",0,"abcde你好".length());//从"abcde你好"的索引0的数据开始写,把"abcde你好"里面的全部数据都写进对应文件
//当然也可以写几个数据,比如只把"abcde你好"里面的从0索引开始,共把3个数据写入对应文件,如下
osw5.write("abcde你好12345asdfg",0,15);//这一行和48行不能同时解开注释,不然报错,只能解开其中一个
//释放资源
osw5.close();
}
}
字符流读数据的2种方式
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
字符流读数据2种方式的练习
xxxxxxxxxx
package ch19;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
//读数据,看结果可以直接看控制台
public class a_8_1测试 {
public static void main(String[] args) throws IOException {
//创建字符流输入对象,使用默认字符编码。报红线就选中它,按Alt+Enter,抛出异常
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\fos.txt"));
//第一种方式读数据 int read() 一次读一个字符数据
int ch;
while ((ch=isr.read())!=-1){
System.out.print((char)ch); //把fos.txt文件的所有内容,保持原样输出
}
System.out.println("");//这行的作用仅仅是换行,避免跟下面的输出结果混合在一起
System.out.println("---------------------------------");
//-------------------------------------------------------------------------------------------------------------------
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\bos.txt"));
//第二种方式读数据 int read(char[] cbuf) 一次读一个字符数组数据
//首先需要定义一个字符数组
char[] chs = new char[1024];
//读数据
int len;
while ((len=isr2.read(chs))!=-1){
System.out.print(new String(chs,0,len)); //把bos.txt文件的所有内容,保持原样输出
}
//释放资源
isr2.close();
}
}
字符流复制Java文件
需求:把"D:\huanf\java\src\ch18_ch19储物区\copy.txt"复制到"D:\huanf\java\src\ch18_ch19储物区\JavaSE"里面
思路: 1、根据数据源创建字符输入流对象 2、根据目的地创建字符流输出流对象 3、读写数据,复制文件 4、释放资源
字符流复制Java文件的练习
xxxxxxxxxx
package ch19;
import java.io.*;
public class a_9_1测试 {
public static void main(String[] args) throws IOException {
//1、根据数据源创建字符输入流对象。报红线就选中它,按Alt+Enter,抛出异常
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\copy.txt"));
//2、根据目的地创建字符流输出流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\huanf\\java\\src\\ch18_ch19_储物区\\JavaSE\\copy.txt"));
//3、读写数据,复制文件
//注意字符流有两种读数据的方式。下面两种方式任选其一
//第一种读数据的方式,一次读写一个字符数据
/*int ch;
while ((ch=isr.read())!=-1){
osw.write(ch);
}*/
//第二种读数据的方式,一次读取一个字符数组数据
char[] chs = new char[1024];
int len;
while ((len=isr.read(chs))!=-1){
osw.write(chs,0,len);
}
//释放资源
osw.close();
isr.close();
}
}
字符流复制Java文件改进版
需求:把"D:\huanf\java\src\ch18_ch19储物区\copy2.txt"复制到"D:\huanf\java\src\ch18_ch19储物区\JavaSE"里面
分析: 1、转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类 2、InputStreamReader有一个直接子类为FileReader,该子类是具体类,是读取字符文件的便捷类,该子类有3个构造方法,自己文档查查,我们只用FileReader(String fileName),该构造方法的作用是创建一个新的FileReader,给定要读取的文件的名称 3、OutputStreamWriter有一个直接子类为FileWriter,该子类是具体类,是写入字符文件的便捷类,该子类有5个构造方法,自己文档查查,我们只用FileWriter(String fileName),该构造方法的作用是构造一个给定文件名的FileWriter对象 4、当不涉及转换流的编码和解码问题时,我们就可以用新学的FileReader、FileWriter代替InputStreamReader、OutputStreamWriter
思路: 1、根据数据流创建字符输入流对象 2、根据目的地创建字符输出流对象 3、读写数据,复制文件 4、释放资源
字符流复制Java文件改进版的练习
xxxxxxxxxx
package ch19;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class a_10_1测试 {
public static void main(String[] args) throws IOException {
//1、根据数据流创建字符输入流对象。报红线就选中它,按Alt+Enter,抛出异常
FileReader fr = new FileReader("D:\\huanf\\java\\src\\ch18_ch19_储物区\\copy2.txt");
//2、根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("D:\\huanf\\java\\src\\ch18_ch19_储物区\\JavaSE\\copy2.txt");
//3、读写数据,复制文件
//上面读数据有两种方式,直接子类会享有父类的特性
//第一种读数据的方式,一次读一个字符数据。只需要一种方式就可以哦,自行注释另一种
int ch;
while ((ch=fr.read())!=-1){
fw.write(ch);
}
//第二种读数据的方式,一次读一个字符数组数据
//定义数组
/*char[] chs = new char[1024];
int len;
while ((len=fr.read(chs))!=-1){
fw.write(chs,0,len);
}*/
//4、释放资源
fw.close();
fr.close();
}
}