35 IO流
概念和分类
1IO流示意图,IO分别对应输入输出input,output,而我们硬盘文件存储如图a.txt存着abc字符串,以内存来看,输入就是将文件读取到内存中,输出就是内存写入到硬盘文件中
其中IO流分为字节流和字符流,每个顶层有不同的类,如图,1个字符=2个字节=16bit位,所有跟IO有关的类都在java.io中
字节流
一切皆为字节,所有文件均是以二进制格式存储,大小以字节为单位,所有跟
字节输出流OutputStream
是所有字节流的超类,也是抽象类
2其方法如上,当然字节输出流有很多实现子类
3如上,分别是 字节数组输出流,文件字节输出流,过滤字节输出流,对象字节输出流,字节输出流(其他包下的),管道输出流。这里我们重点使用FileOutputStream,以为之前都是铺垫文件操作,这回是往文件里写数据
4构造方法如上,我们先研究File对象或者String对象的单参数构造,字符串名称或者File对象都是我们写入对象的目标文件,这个构造方法的作用是创建实例对象,根据提供路径创建空文件,并把创建好的FileOutputStream对象指向这个文件
写入文件
我们写入文件是通过java程序->jvm->os操作系统->执行文件写入
1创建FileOutputStream对象2 使用其write方法 3使用close释放资源(流会占用内存资源)
5如上,我们创建字符串路径,然后给文件写了个byte类型的97,发现指定位置创建了文件,同时可以看见内容为字母a,这里是ascii码的写入,我们最后别忘了关闭文件流。当然这只是调用了write的第一个重载,一次写一个字节
如果我们写多个字符就用到第二个重载,写入byte数组
6我们写byte数组,如果某个字节的值是负的,那么2个字节拼成一个中文字符(这是查询系统默认使用gbk)
7 8当然重载还支持偏置,将byte数组的从off开始len长度写入到文件中
9当然我们比较重要的还是希望通过字符串写入文件,我们记ASCII码无异于累自己,当然别忘了字符串有getBytes方法,我学到这里都快忘了,这里使用下
最后说明下,如果未close多次write都是追加写,而close后再写就是覆盖写,不保留原来的
字节输入流InputStream
同OutputStream一样,其是所有字节输入流的超类,也是抽象类
10同样,我们可以看到很多子类,我们这里还是关心文件输入流
11InputStream方法如上
12FileInputStream构造方法如上,我们可以使用File对象或者字符串路径创建文件输入流对象
代码演示一次读取一个字节
13如上,当文件读到结尾后会返回-1,但是我们知道就a,b,c3个字母,97.98.99是正常内容,前3个是怎么来的呢,这是utf8带BOM头的结果,可以用Notepad++改编码为无BOM头
14修改后满足要求
读取多个字节int read(byte[] b)
15通过如上代码可以知道,我们要创建byte数组接收,由于其长度需要事先指定,比如我们设置2,因为read会返回值,返回的就是读取的长度2,内容就是前2个字符,最后别忘了close释放资源
16我们知道字符串的构造方法可以传递byte数组,我们也是习惯看字符串而不是ascii,所以可以如上转换字符串显示
17当然多次读取,是按照下一个位置开始,如上,当然如果读到结尾也是返回-1,
也许你会好奇,byte[]的作用是什么,是起到缓冲作用,每次读取部分内容,当然我们数组的长度定义的有点小,一般定义为1024的整数倍,int返回值是返回的有效读取字节数
18因为我们往往不知道文件内容长度,所以文件读取一般定义数组长度1024,然后循环读取,设置int变量len接收read参数,如果不为-1就打印内容。结果出来了,但是因为1024数组有很多长度是空的,可以看到鼠标选上还有很多内容,
19所以我们String的另一个重载方法就来了,使用byte数组,可以加上偏置和长度,这里0开始,长度当然就是获得读取到的有效长度
demo复制文件hello.txt->hello2.txt
20当然上面是我自己想的,实际上无论什么文件都可以参照的复制请看下面
21其步骤是创建输入流和输出流,while循环读取,将获得的内容写给输出流,最后关闭先关输出流再关输入流
字节流读取中文会出现问题
一个中文字符,gbk两字节,utf8三字节
22我们hello.txt存的是你好,如上可以看到是按gbk存的,2个汉字4个字节,因此我们就需要使用字符流来处理,防止乱码
字符流
字符输入流Reader
其也是字符输入流的顶层类也是抽象类
23成员方法如下
24我们主要关心的是read()读取单个字符,read(char[] cbuf)以及close
这里我们实例使用InputStreamReader的子类,FileReader,是读取字符文件的便捷类(当然我们要读取的文件是文档类,不是一般二进制文件)
25构造方法如上,我们关心的是1,3种,filename就是文件路径,File就是文件File对象
读取步骤还是3步,创建实例,read读取(通过是否-1判断是否读到结尾),close释放资源
26如上,但是这里是我修改了编码为utf-8的,其实默认是gbk,如果不修改编码就是乱码,而且需要强转
27当然我们也可以使用read的第二个重载,使用char数组接收,然后使用String构造传入char数组的len位有效值进行输出
字符输出流Writer
同样是字符输出流顶层类,抽象类
28常见方法如下
29当然我们实例使用的是FileWriter extends OutputStreamWritere extends Writer
30 31构造方法如上,我开门一般是使用File对象或者字符串文件名来参加Writer,其中可以添加第二个参数作为是否附加写入还是覆盖写入
使用FileWriter过程,创建实例,write写入到内存的缓冲区(因为写入的是字符要转换成字节),使用flush把内存缓冲区的内容刷新到文件,close(也会实现先刷新文件内容,多次写入需要刷新,使用flush,如果次数少,其实close就够了)释放资源,
32 33如上,我们在构造方法时传入附加bool值为true,就可以附加写入
flush和close都能刷新缓冲区到文件,同时释放系统资源,IO流对象就不可用了(被销毁),而flush可以多次使用
34当然可以试试其他write重载,如上,对字符串加入偏置和写入长度
35写入字符数组如上,同样可以加上偏置和长度就不额外写了
下面说下文件的换行windows \r\n linux \n mac \r
36如上,我们还要记得追加是构造时使用二参数boolean设置true
IO流异常处理
37JDK1.7以前,只能是一款throws或者try catch处理,这里我们FileWriter创建就有可能出现异常,我们想象要在finally里close,但是fw不能只定义在try块内,否则finally提示未定义,而且要调用close,我们只能初始赋值为null,然后finally里判断trycatch,如果是非null就要关闭资源。
上面感觉是不是很麻烦,但是1.7以前就是这样
JDK1.7以后try后可以跟()小括号,我们在小括号内定义的流对象,在try跳出会自动销毁(小括号一句可以不加分号)
38如上,使用这种方法,就简化了不少
而JDK1.9又强化了特性,可以在try前定义流对象,然后try后小括号内使用对象名,一样会在try块转出销毁流对象
39唯一比较坑的是,创建部分还是需要throws IOException==,那还用trycatch干嘛