Java - IO基本流¶
用于文件的读写.
分类:¶
tip:.md文件其实也是纯文本文件.xml.lrc都是(lrc就是歌词文件)
整体结构¶
但是下面几个都是抽象类,实际上的类:
in/out是以程序本身为参照物的.
OutputStream:¶
注意构造方法会抛出异常.
我们写入的97,如果用记事本打开就是'a'
细节:
另外,我们多次调用write,实际上是在向文件缓冲区里追加内容.
写数据的多个方式:¶
off表示起始索引(数组下标,0开始).len表示写的个数
- 如果想要使用传string:getBytes方法:
- 换行的时候写入换行符就是.(win:\r\n linux:\n max:\r)
但实际上,如果win中我们写入\n,那么jvm会自动帮我们补成\r\n.但是我们一般还是手动写出来比较好.
- 续写/追加:
这样即可:
InputStream¶
构造函数也会抛出异常
这样只会读取一个字符.如果想要读完,那么就循环读取.
如果读到文件结尾,那么就会返回-1.
细节:¶

循环读取:¶

文件拷贝:¶
如果一个字符一个字符拷贝,那么太慢.
依次读取多个即可:
创建buffer数组长度一般是1024的整数倍.
返回值:本次读取了多少个字节数据:
如果读不到数据了,那么就返回-1.
buffer数组传入到read之后,是不会清空的,read会直接修改原本数组的内容.
因此,我们构造String的时候只用buffer数组当中前几部分即可(使用String(byte[],off,len)这个构造函数).
也就是说,假如文件长度是10,buffer大小是3,那么依次读取之后,len的大小分别为 3 3 3 1 -1
假如文件内容是abcdefghij
那么buffer中的内容依次会变成:
abc
def
ghi
jhi
循环读取:¶

这里write构造函数的参数也用的是部分写buffer. 释放资源的顺序一般是先创建的后释放.
try-catch的误区:¶
如果这么写,那么就导致如果write出异常,跳过了close那句,导致资源泄露.
因此我们使用finally.finally里面的代码一定会被执行,除非JVM退出.(如执行了exit)
因此:
但是这样写有问题:fos fis都是局部变量,因此finally里不能使用.
能否放到外面:
不行,因为fos/fis没有初始化,那么close就没法处理.
解决方法:使用null:
这样还是有问题,因为close可能抛出异常:
还有问题,比如new FileInputStream时,这里失败了,会进入catch语句,然后进入finally.fos这时候仍然为null,对它close,会导致空指针异常.因此,需要这样:

这是最终的代码.
实际上,以后使用spring之后,这里的异常全部会抛出去,统一处理.
有没有RAII,像cpp一样?
有,AutoCloseable
JDK7中,创建流对象中的对象必须实现AutoCloseable接口.(FileInput/outputStream有)
JDK7的方法可读性很差,因此最后就出现了JDK9的那种写法.
JDK7写法:
JDK9写法:
注意外面的new会有异常,因此还是得抛出去.
字节流读取中文会有乱码.这是字符集的问题:
字符集¶
GB2312 国标(大陆地区使用)¶
而台湾地区使用了BIG5字符集.为了统一这种情况,2000年发布了GBK字符集(GB的拓展).收录了所有汉字. win默认使用的就是GBK.(大陆地区的win)
不同地区使用的默认字符集不一样:¶
这么多字符集难以处理,因此微软把它们统称为ANSI.ANSI就是系统显示的默认字符集.
因此我们的电脑上,ANSI就是GBK.
存储规则:¶
由于a对应的还是97,因此GBK是兼容ascll码的.
为什么需要规定高位是负数?因为是为了区分中文和英文.
读取:¶
对于汉字在编码时由于没有额外操作,因此解码的时候也没有额外操作.
Unicode 万国码/统一码¶
Unicode中,英文和数字的映射是固定的(也就是拿着a去查询Unicode的结果是固定的),但是编码的方案不一样.

UTF8是最常见的:¶
因此UTF-8不是字符集,只是一种编码方式.
我们还需要记住一个结论,英文是1个字节,以0开头.中文是3个字节,以1开头.
为什么有乱码?¶
- 读取的时候没有读完整个汉字.
这样,由于字节流一次读取一个字节,那么11100100读取出来是28,查询不到对应的数据,系统就会显示? 或者 方框
2.编码解码的方式不统一:

因此:不要用字节流读取文本文件,编码解码的时候要使用同一个码表和编码方式.
Java默认的编码解码方式¶
默认:IDEA是Utf8,eclipse是GBK.
这样实际上是把String底层的数据打印出来了.
注意,指定字符集的那两个方法会抛出异常.
开发的注意问题:¶
由于乱码的产生原因有两个: 1. 读取的时候没有读完整个汉字 2. 编码解码的方式不统一
由于实际上我们对于2,我们默认用的都是utf8,因此我们只需要处理1.
如果有这样一种流:
那么就很好解决了.这就是字符流:
字符流¶

空参读取示例¶

带参读取示例¶
注意这里是char类型的数组.
这里的len返回的是字符(可能是中文字符,也可能是英文字符)的个数.

写方法示例:¶
构造:
和字节流的output有什么区别?字节流的output只能一次写一个字节,而write会编码,把编码后的数据写到文件当中.
也就是说,可以写多个字节.

底层原理:¶
reader缓冲区¶
事实上,Java本身也是有文件缓冲区的.在一次read时,会先从缓冲区读.如果读的时候发现缓冲区没有数据可以读取,就会从文件中读取数据,尽可能填满缓冲区.
缓冲区默认大小为8192byte数组.
注意,只有字符流有缓冲区,字节流是没有缓冲区的.
这个缓冲区和之前手写buffer都是一样的,也就是说,buffer不会经过清空的过程,一直都是覆盖缓冲区.
缓冲区误区¶
实际上,FileWriter确实会清空文件,但是不会清空缓冲区.因此,reader是可以在清空文件之后仍然从缓冲区读取数据的. 直到缓冲区读完,缓冲区主动到磁盘去找文件更新的时候,才会发现文件已经清空了,于是reader就读不到数据了,读取停止.
write缓冲区¶
write也会有缓冲区.默认情况下,只有
1. 缓冲区装满了
2. 手动flush
3. close时
会刷新一次缓冲区.
tip:缓冲区也是在内存中的.
这个缓冲区也是8192字节.

综合应用¶
拷贝文件夹¶
字节流用于拷贝.

- 如果我就是想往一个字节流写一个数字怎么办? fw.write(num+"")这样就会把数字和""拼接成一个字符串,从而达到把数字变成String的目的.
bom头:¶
如果我们在做上面的代码实验的时候,会发现自己创建的文件(不在IDEA中创建的文件)可能会添加bom头,如记录了一些文件数据.
在"另存为"某个文本文件时,可以选择
这样新存的文件就可以选择编码格式了.
我们选utf8,就没有字符头了.
idea里新建的,也默认没有bom头.
IO的原则:随用随创建,用完就销毁. 否则,一不小心创了一个writer,可能会把文件清空,导致Reader读不到数据.