Skip to content

Java - IO基本流

用于文件的读写.

分类:

aeb81ba6303ea392e26cbc676a54d627_MD5 tip:.md文件其实也是纯文本文件.xml.lrc都是(lrc就是歌词文件)


整体结构

4ee549add11c8fda20017dfedf405458_MD5 但是下面几个都是抽象类,实际上的类: db0c4e5f2f37abf58ed528a935135476_MD5 in/out是以程序本身为参照物的.


OutputStream:

注意构造方法会抛出异常. 17a167afeab5c31fd2a9140ccb5b21ac_MD5 我们写入的97,如果用记事本打开就是'a' 细节: feab6440eb118191a7e507caeb318d13_MD5 另外,我们多次调用write,实际上是在向文件缓冲区里追加内容.

写数据的多个方式:

4e2da3ec2f6957dd0a94b13976ac83f1_MD5 off表示起始索引(数组下标,0开始).len表示写的个数 - 如果想要使用传string:getBytes方法: 1c4e655c6f2596518ee6258d936049c2_MD5 - 换行的时候写入换行符就是.(win:\r\n linux:\n max:\r) 但实际上,如果win中我们写入\n,那么jvm会自动帮我们补成\r\n.但是我们一般还是手动写出来比较好. - 续写/追加: 这样即可:0ba829798dab4d4da612b95edee7f4da_MD5


InputStream

构造函数也会抛出异常 20d833646d565ae28fa791b6ff47f7ec_MD5 这样只会读取一个字符.如果想要读完,那么就循环读取. 如果读到文件结尾,那么就会返回-1.

细节:

c51a72f8faa1537a72ac7461b648a5bc_MD5

循环读取:

59cd509d04b7d0f226d396314c14f62e_MD5


文件拷贝:

如果一个字符一个字符拷贝,那么太慢. 依次读取多个即可: d17920a1f81761de0efdf523f30011b9_MD5 创建buffer数组长度一般是1024的整数倍. 返回值:本次读取了多少个字节数据: 4349ae8f5b38e9c2239367a9628f859c_MD5 如果读不到数据了,那么就返回-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

循环读取:

4c33af8ad90b55d728ece1e7567d3208_MD5

这里write构造函数的参数也用的是部分写buffer. 释放资源的顺序一般是先创建的后释放.


try-catch的误区:

47322488ecb1147dbbf943f070704ce5_MD5 如果这么写,那么就导致如果write出异常,跳过了close那句,导致资源泄露.

因此我们使用finally.finally里面的代码一定会被执行,除非JVM退出.(如执行了exit)

因此: a496a939fc51f781605ffda9bf3fe790_MD5 但是这样写有问题:fos fis都是局部变量,因此finally里不能使用. 能否放到外面: 05807749674cc26f97d61222c7889af7_MD5 不行,因为fos/fis没有初始化,那么close就没法处理. 解决方法:使用null: 7d4316b686ac2a7c1b8808c5fcbee8c9_MD5 这样还是有问题,因为close可能抛出异常: 6bad226e5132d39ea78215ec0f8ff453_MD5 还有问题,比如new FileInputStream时,这里失败了,会进入catch语句,然后进入finally.fos这时候仍然为null,对它close,会导致空指针异常.因此,需要这样: 20b956d3dc963f6f5c6e02b368a8b2d3_MD5

这是最终的代码. 实际上,以后使用spring之后,这里的异常全部会抛出去,统一处理. 有没有RAII,像cpp一样? 有,AutoCloseable 71a5fe3877e29f687e02d47502428d40_MD5 a6b2015d3bd20ed5ddca308ce4820652_MD5 JDK7中,创建流对象中的对象必须实现AutoCloseable接口.(FileInput/outputStream有) JDK7的方法可读性很差,因此最后就出现了JDK9的那种写法. JDK7写法: 5c2a53e981c6daddd19876f7b5f9ae69_MD5 JDK9写法: f10d6fa65195e07f9fff3e846f139883_MD5 注意外面的new会有异常,因此还是得抛出去.


字节流读取中文会有乱码.这是字符集的问题:

字符集

GB2312 国标(大陆地区使用)

而台湾地区使用了BIG5字符集.为了统一这种情况,2000年发布了GBK字符集(GB的拓展).收录了所有汉字. win默认使用的就是GBK.(大陆地区的win)

不同地区使用的默认字符集不一样:

679072c546cbc45d55000976b05238b3_MD5 这么多字符集难以处理,因此微软把它们统称为ANSI.ANSI就是系统显示的默认字符集. 因此我们的电脑上,ANSI就是GBK.

存储规则:

f155516fe30e1b517fc8c9aa45c3a875_MD5 由于a对应的还是97,因此GBK是兼容ascll码的. c8d50486e7ef3b9384d0873a91d884f8_MD5 为什么需要规定高位是负数?因为是为了区分中文和英文.

读取:

431ca19ddd05512d3c1246c4ed44beb7_MD5 对于汉字在编码时由于没有额外操作,因此解码的时候也没有额外操作.

Unicode 万国码/统一码

Unicode中,英文和数字的映射是固定的(也就是拿着a去查询Unicode的结果是固定的),但是编码的方案不一样. fab7911665be7f35c276689db00d3614_MD5 cea571ee2acfbd4d85ea0d6551612910_MD5

UTF8是最常见的:

484dc22e0017b5ec9e45277e26e377b2_MD5 00500c4b369e80e861a8ff4562b3a515_MD5 31648275e46020ebf1038510c9a25835_MD5 因此UTF-8不是字符集,只是一种编码方式. 我们还需要记住一个结论,英文是1个字节,以0开头.中文是3个字节,以1开头.

为什么有乱码?

  1. 读取的时候没有读完整个汉字. d706597b28bed07f8e11c32c360468fd_MD5 这样,由于字节流一次读取一个字节,那么11100100读取出来是28,查询不到对应的数据,系统就会显示? 或者 方框 2.编码解码的方式不统一: 3539cd9577b2920dd934daf8602af9ac_MD5

因此:不要用字节流读取文本文件,编码解码的时候要使用同一个码表和编码方式.

Java默认的编码解码方式

00816d9b5bb4d8738a31c0baeadf21bd_MD5 默认:IDEA是Utf8,eclipse是GBK. c7be07c998338aa4b8c95b0d3370408e_MD5 这样实际上是把String底层的数据打印出来了. 80feba8af9490821baaa5b84a36203a2_MD5 注意,指定字符集的那两个方法会抛出异常.

开发的注意问题:

由于乱码的产生原因有两个: 1. 读取的时候没有读完整个汉字 2. 编码解码的方式不统一

由于实际上我们对于2,我们默认用的都是utf8,因此我们只需要处理1. 如果有这样一种流: 04c76fd81e13fba715bcf21452a53ec7_MD5 那么就很好解决了.这就是字符流:


字符流

8a09cb63b11e77f704dc2a2133c810ce_MD5 c2e7a74a3cde2a077ec404ba0eb77448_MD5 3c35d761d25828a88325905dad7bf1d5_MD5 0b16080a5216a354ac4e395543021769_MD5

空参读取示例

1245372e53377f74b3217b7e9bf22456_MD5 7aae172cf3c995800bcc5fbebb3d1835_MD5

带参读取示例

注意这里是char类型的数组. aed42dc0cf8833e59dcc51231425cb9c_MD5 这里的len返回的是字符(可能是中文字符,也可能是英文字符)的个数. 38c9386aac605e039a2047a9ec3cd09e_MD5

写方法示例:

构造: b61e6c814a30de48dfd615d46c94307b_MD5 a2c49e263aef295856ec88e984484199_MD5 89c2d0a91f0015d7ac475af909bc144e_MD5 和字节流的output有什么区别?字节流的output只能一次写一个字节,而write会编码,把编码后的数据写到文件当中. 也就是说,可以写多个字节. b19c49c9a25ef26e58ab7c801eb43c92_MD5 970ce96f079ede86eda0577d8d4bb6da_MD5

底层原理:

reader缓冲区

事实上,Java本身也是有文件缓冲区的.在一次read时,会先从缓冲区读.如果读的时候发现缓冲区没有数据可以读取,就会从文件中读取数据,尽可能填满缓冲区. 缓冲区默认大小为8192byte数组. 注意,只有字符流有缓冲区,字节流是没有缓冲区的. e84a48e14032d506a2d8410a7bad619f_MD5 这个缓冲区和之前手写buffer都是一样的,也就是说,buffer不会经过清空的过程,一直都是覆盖缓冲区.

缓冲区误区

实际上,FileWriter确实会清空文件,但是不会清空缓冲区.因此,reader是可以在清空文件之后仍然从缓冲区读取数据的. 直到缓冲区读完,缓冲区主动到磁盘去找文件更新的时候,才会发现文件已经清空了,于是reader就读不到数据了,读取停止.

write缓冲区

write也会有缓冲区.默认情况下,只有 1. 缓冲区装满了 2. 手动flush 3. close时 会刷新一次缓冲区. tip:缓冲区也是在内存中的. 这个缓冲区也是8192字节. a634db9edce6c01aa8783b9339be50df_MD5


综合应用

拷贝文件夹

字节流用于拷贝. 0da87d8ba500eba4d45cb864ea68af74_MD5

  • 如果我就是想往一个字节流写一个数字怎么办? fw.write(num+"")这样就会把数字和""拼接成一个字符串,从而达到把数字变成String的目的.

bom头:

如果我们在做上面的代码实验的时候,会发现自己创建的文件(不在IDEA中创建的文件)可能会添加bom头,如记录了一些文件数据. 在"另存为"某个文本文件时,可以选择 b856a620f14036d8639318a84dccfc5e_MD5 这样新存的文件就可以选择编码格式了. 我们选utf8,就没有字符头了. idea里新建的,也默认没有bom头.


IO的原则:随用随创建,用完就销毁. 否则,一不小心创了一个writer,可能会把文件清空,导致Reader读不到数据.