Java中字符是采用Unicode标准,Unicode 编码中,一个英文字母或一个中文汉字为两个字节。
而在UTF-8中,一个英文或数字占1个字节,一个常用汉字占用3个字节,不常用汉字占用4个字节。
字节流操作的单元是数据单元是8位的字节(一个字节),字符流操作的是数据单元为16位的字符(两个字节)(不同的编码操作的字节数不同)。
- 一个字节等于8位(固定)
- 两个字节等于一个字符(和编码有关,在Java中的Unicode是这样的)
- 所以在Java中,一个字符等于16位
在字节流中,一次读取一个字节,而中文本身有两个字节(UTF-8有三个字节),那么把一个字分成三部份来读,肯定会出现乱码(除非你一次读在当前编码汉字所占的字节数),所以用字符流读取汉字则不会出现乱码。
字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。
这篇文章很好讲述了字节流和字符流的东西,我主要是简单了讲述了一下,细节看下面博主的。
【Java基础-3】吃透Java IO:字节流、字符流、缓冲流_云深i不知处的博客-CSDN博客_javaio流
目录
节点流和处理流
- 节点流比较底层,是直接和特定的数据源,就是存放数据的地方(数组,文件,管道,字符串)进行操作的,灵活性比较低。
- 而处理流(包装流),对节点流或者处理流进行包装(更多是节点流),提供比节点流更多的功能,使用了装饰模式(修饰器模式),不会直接和数据源相联系,真正处理数据的还是节点流。
- 比如BufferReader,能对Read的子类进行封装。条件是Reader子类即可 。
装饰模式(理解)
(1)首先有个Reader的基类,在最顶层,是一个抽象类,里面有两个readFile和readString方法,这两个方法是普通方法,不是抽象方法,方法体为空,而不是没有方法体,然后让子类去做。
(2)FileReader类,继承了Reader类,类似一个节点流,直接和底层数据进行交互,只针对文件进行操作,所以只重写了readFile()方法。
(3)StringReader类,继承了Reader类,类似一个节点流,直接和底层数据进行交互,只针对字符串进行操作,所以只重写了readString()方法。
(4)BufferReader类,也继承了Reader类,但是它是处理流,不直接和底层数据做交互,可以针对不同节点流进行操作,里面有私有属性private Reader reader;,且此类的构造方法是传入一个Reader类的对象进去,也就是说只要是Reader的子类都可以被传进来。每个对应对象就有对应类中的方法,创建对象后调用各自类中的方法即可。
(5)然后就可以在BufferReader类自己创建方法,再在方法中调用他们类的方法进行封装即可(进行方法的拓展),比如想用他们底层的方法进行读取,但又想一次读取多次或者加上缓冲。
(6)在主函数中,new一个BufferReader对象,然后调用BufferReader类中的方法即可(如果调用父类Reader里面的方法就是空的,要调用Reader子类中重写的方法)
额外补充一点当然也可以用多态,Reader类只有一个Read()的抽象方法,然后让全部子类去重写,利用不同对象的动态绑定机制,就知道调用的是哪个类的Read()方法了。
此图来自韩顺平老师IO流课程课件中的图片。
节点流读取和写入数据(需要异常处理)
1.用字节读取和输入(二进制文件)
(1)FileInputStream
从文件中读取英文,数字,二进制文件的数据,如果文件读取完毕,返回-1
(2)FileOutputStream
将数据写到文件中,如果文件不存在,则创建该文件
综合运用:从C盘读取音频或者图片(输入流),再写入到D盘中(输出流)
2.用字符读取和输入(文本文件)
(1)FileReader
从文件中读取文字,英文,读取完毕返回-1
(2)FileWriter
- 将数据写到文件中,如果文件不存在,则创建该文件
- 里面有个write的构造有个,字符串转化为char[]数组,"文件不存在".toCharArray()
- 一定要关闭流close()或者flush(),才能真正的把数据写入到文件,最好用close()
- 底层两个方法都是用FileOutputStream的write方法。
处理流读取和写入数据(需要异常处理)
1.用字节读取和输入(文本文件)
(1)BufferInputStream
读取的时候会返回实际读取的长度,read()方法,读不到会返回-1,用while循环读取二进制文件
(2)BufferOutputStream
用write(byte[],第几位读取,第几位结束)方法写入文件,最后关闭外层处理流即可
但是用这两个方法也可以读取输入文本文件?
可以,但是我还没明白是什么原因。。。
2.用字符读取和输入(二进制文件)
(1)BufferReader
需要一个路径来读取文件,new一个BufferReader对象,构造参数内传一个节点流对象(装饰模式),可以按行读取readLine,如果为null即读取完毕,关闭流,直接调用close外层流(BufferReader)即可,底层其实是调用节点流的close()以此来关闭流。
(2)BufferWriter
写入文件(如果没有这个文件就会创建一个并写入),创建new一个BufferWriter对象,构造参数传new一个FileWriter对象,如果在BufferWriter中需要追加内容,那么在FileWriter的对象中new的过程中加true即可。
处理流读取和写入对象(需要异常处理)
序列化:保存值和数据类型到文件中
反序列化:从文件中恢复值和数据类型到内存中
(1)ObjectInputStream:提供反序列化功能
- 把.dat文件反序列化恢复数据
- 确认反序列化文件的位置
- new一个ObjectInputStream
- 抛出异常
- 读取文件(读取的顺序要和保存数据的顺序一致)
(2)ObjectOutputStream:提供序列化功能
- oos.writeObject(new Dog("旺财",9));
- 前提Dog类要实现序列化接口,在序列化保存的时候是按这个类的固定格式保存,不会因为后缀是.txt就是txt文件,用记事本打开.dat文件显示的是乱码
- 序列化不保存static和transient修饰的成员
- 你要序列化一个类,但是这个类有个属性没有被序列化会报错(比如new一个没有序列化的自定义类在全局变量中)
- 父类实现序列化,那么继承它的子类也默认实现序列化
标准输入输出流(System.in/out)
(1)System.in
-
表示标准输入 键盘
- 是System类的 public final static InputStream in=null;
- 编译类型是InputStream
- 运行类型是BufferedInputStream(用字节处理输入流来进行输入)(缓冲流)
举例:Scanner scanner =new Scanner(System.in);
String next=scanner.next();
扫描器到键盘上获取内容
(2)System.out
- 表示标准输出 显示器
- 是System类的 public final static PrintStream out=null;
- 编译类型是PrintStream
- 运行类型是PrintStream(用字节处理输出流来进行输入)(打印流)
举例:System.out.println();
转换流(字符处理流)
把一种字节流转换成字符流
一旦操作文本涉及到具体的指定编码表时,必须使用转换流 。
默认情况下,读取文本文件是按照UTF-8来读取,如果文本文件换成了其他编码,则会出现乱码,所以出现了转换流???????
那什么时候转换呢???
当你处理纯文本时,字符流效率更高,并且可以有效解决中文问题
(1)InputStreamReader(输入流)
InputStream包装成Reader(字节输入流——>字符输入流)
构造器中可以传入一个InputStream类的子类,且可以指定编码(Charset参数)
举例:
//此处通过br对象直接利用FileReader对象来调用里面的方法,但是Java默认编码和路径获取的文本编码方式不同的话,则会引起乱码
BufferReader br=new BufferReader(new FlieReader(路径));
//此处起到了一个中间转换的效果,但是同时也处理了文本的编码方式和Java兼容
InputStreamReader isr=new InputStreamReader(new FileInputStream(路径),"gbk")
BufferReader br=new BufferReader(isr);
//可以合在一起
BufferReader br=new BufferReader(new InputStreamReader(new FileInputStream(路径),"gbk"));
(2)OutputStreamWriter(输出流)
OutputStream包装成Writer(字节输出流——>字符输出流)
先把FileOutputStream字节流转换成字符流OutputStreamWriter,再进行指定用XX编码方式进行保存文件
//把FileOutputStream输出字节节点流转换成了OutputStreamWriter输出字符处理流,把保存的编码转换成了gbk码
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream(路径),"gbk");
打印处理流
它们的构造器都可以传一个文件,Writer的子类,String其中之一。
(1)PrintStream(字节输出处理流)
System.out的返回值就是PrintStream,此时打印的时候System.out.println不指定位置默认输出在显示器上(标准输出)
修改打印流输出的位置
System.setout(new PrintStream(xxx磁盘位置))
System.out.println(“内容”)
(2)PrintWriter(字符输出处理流)
修改打印流输出的位置
PrintWriter printWriter=new PrintWriter(new FileWriter(xxx磁盘位置));
printWriter.print(“内容”);(你写的内容 底层其实是用ASCLL码去传输的)
printWriter.close();
Properties配置文件
是Hashtable的子类,专门用于读写配置文件的集合类
格式:键值对(键=值)
注意:键值对不需要有空格,默认String类型
传统方法获取配置文件(自己定义类)
IP="192.168.0.1"
地址="北京"
电话="12312321312"
BufferedReader br=new BufferedReader(new FileReader("src\\xxx.properties));
String line=" ";
while((line=br.readLine())!=null){
String[] split=line.split("=");
if("IP".equals(split[0])){
System.out.println(split[0]+"值:"+split[1]);
}
}
br.close();
Properties类获取配置文件
//创建Properties对象
Properties ppt=new Properties();
//加载指定配置文件
ppt.load(new FlieReader("src\\xxx.properties"));
//K-V显示到控制台
ppt.list(System.out);
//根据K获取对应的值(返回字符串)
ppt.getProperty("xxx");
Properties类创建配置文件
//用Properties 类创建一个配置文件Properties
Properties ppt2=new Properties();
//创建里面的键值对保存到ppt2对象
//如果value有中文,则转化成unicode码值
ppt2.setProperty("charset","UTF-8");
ppt2.setProperty("name","bom");
ppt2.setProperty("hobby","boy");
//把K-V存到文件中(支持传入所有输出流对象)
//第二个参数是注释,就像我这条一样,放在最顶上,#开头
ppt2.store(new FlieOutputStream("xxx"),null);
Properties类修改配置文件
和Hashtable同理,有一个相同Key,Value不同,则会替换。
setProperty()即可替换修改
注意:
try{}和catch{}的变量不互通,需要设置成方法内的局部变量