IO
最近,因为学习项目,就想总结前面的知识。下面我们就简单归纳归纳JAVA-IO知识。
IO是java中十分重要的知识,那么什么是IO,即in和out,java中是通过“流”来处理io的,进而实现了程序与外设的数据传输。
- 流的特点:
1、先进先出:最先写入的输出流,会最先被输入流读取到
2、顺序存储:写入是可以先一个一个的写入一串字节,读入时,也可以一串一串的读入,不能随机访问(randomAccessFile)除外。
3、每一种流的功能是单一的,只能是只读或者只写
- 流的分类
1、按照数据的方向:输入流,输出流
2、按处理数据单位:字节流,字符流
3、按照功能: 节点流,处理流
输入流和输出流就比较好理解,从外部读取文件,就是输入流,将数据写入到外部文件,就是输出流。
字节流和字符流:总的来说就是,字节流能够处理一切的文件,而字符流只能处理纯文本文件。那么,我们都有字节流能处理一切的文件,为什么还要有字符流呢,原因是:在java中采用Unicode编码,在Unicode中,一个英文占用一个字节(8bits),我们中文为2个字节(16bits)。但是在UTF-8编码中,英文占用2个字节,中文占用3个字节,我们使用字节流处理中文,如果一次读写一个字符对应的字节数,就不会有问题,但是如果如果我们将字符对应的的字节分裂开来,就会出现乱码了。
区别:字节流本身是没有缓冲区的,但是缓冲字节流,相比较于字节流,效率提升就比较大。但是字符流本身是有缓冲区的,所以说缓冲字符流对于字符流的效率提升就不是很大。还有一点就是,字符流在
读入或者写出的时候,要注意刷新管道,或者关闭流(关闭时,回去刷新管道)
下面需要介绍的常用IO类
1、FileOutputStream(字节输出流):,继承自OutputStream,一般用于写入诸如图像数据的原始字节流。 要编写字符流,请考虑使用FileWriter。
下面我们来简单演示一下fileOutputStream的常用方法和构造方法
FileOutputStream(File file) |
创建文件输出流以写入由指定的
File 对象表示的文件。 |
---|---|
FileOutputStream(FileDescriptor fdObj) |
创建要写入指定文件描述符的文件输出流,该文件描述符表示与文件系统中实际文件的现有连接。
|
FileOutputStream(File file, boolean append) |
创建文件输出流以写入由指定的
File 对象表示的文件。 |
FileOutputStream(String name) |
创建文件输出流以写入具有指定名称的文件。
|
FileOutputStream(String name, boolean append) |
创建文件输出流以写入具有指定名称的文件。
|
可以看到FileOutputStream主要有两种构造方法,一种是传入文件对象,另外一种方法时传入一个文件路径(保证文件存在),另外我们会发现还有一个boolean append的参数,这个参数的意义就是,是否追加,
就是如果原文件如果有字节,我们传入不写,或者是传入false的时候,它就会覆盖原来的内容。下面看演示↓:
1 FileOutputStream fileOutputStream = new FileOutputStream("a.txt"); 2 byte[] bytes1 = new byte[]{66,67,68}; //BCD 3 byte[] bytes2 = new byte[]{69,70,71,72,73}; //EFGHI 4 byte[] bytes3 = "XYZ".getBytes(); 5 6 fileOutputStream.write(65); //会写入A 7 fileOutputStream.write(bytes1); //会写入B,C,D 8 fileOutputStream.write(bytes2,1,2); //传入一个字节数组,从指定下标开始写入指定长度,从F开始,写入两个长度就是FG 9 fileOutputStream.write(bytes3); //会写入XYZ 10 11 fileOutputStream.close(); //关闭流,一定不要忘记,一定不要忘记,一定不要忘记 12 System.out.println("ok");
字节输出流->>>>>>:结果
可以看到:我们可以写入一个字节,一个字节数组,或者是一个从指定下标开始,写入指定长度的数组,最后不要忘记关闭就行。
2、FileInputStream (字节读取流):继承自inputStream,用于读取字节,并返回一个int类型的数组或者字节。下面我们演示一下构造方法和常用api
构造器 | 描述 |
---|---|
FileInputStream(File file) |
通过打开与实际文件的连接来创建
FileInputStream ,该文件由文件系统中的 File 对象 file 命名。 |
FileInputStream(FileDescriptor fdObj) |
使用文件描述符
fdObj 创建 FileInputStream ,该文件描述符表示与文件系统中实际文件的现有连接。 |
FileInputStream(String name) |
通过打开与实际文件的连接来创建
FileInputStream ,该文件由文件系统中的路径名 name 命名。 |
和FileOutputStream类似,FileInputStream 主要也是两种构造方法,一个是传入文件对象,另外一个是传入文件路径(该文件存在)。
int |
read() |
从此输入流中读取一个字节的数据。
|
---|---|---|
int |
read(byte[] b) |
从此输入流
b.length 最多 b.length 字节的数据读 b.length 字节数组。 |
int |
read(byte[] b, int off, int len) |
从此输入流
len 最多 len 字节的数据读入一个字节数组。 |
Read(byte[] b):读取一个字节数组的数据
Read():读取一个字节的数据
read(byte[] b int off ,int len):从指定长度开始,读取指定的长度字节的数据
演示一次读取一个数据:
1 FileInputStream fileInputStream = new FileInputStream("a.txt"); //a.txt的内容是:ABCDEF 2 while (true){ 3 int i = fileInputStream.read(); 4 if (i == -1){ //字节读取流,返回的是一个int类型,如果读取到结尾,就会返回-1,返回-1就跳出循环 5 break; 6 } 7 System.out.print((char) i+" "); 8 } 9 fileInputStream.close();
输出结果:A B C D E F
分析:::可以看出,一次读入一个字节数据,读到末尾就返回-1
下面演示一次读取一个byte数组的数据:
1 ////txt的内容是小写字母表,一共26个 2 FileInputStream fileInputStream = new FileInputStream("a.txt"); 3 //创建一个10个字节长度的字节数组 4 byte[] bytes = new byte[10]; 5 int len = fileInputStream.read(bytes); 6 //每读取一次,就new为字符串,并打印 7 System.out.println(new String(bytes)+"长度为:"+len); 8 fileInputStream.read(bytes); 9 System.out.println(new String(bytes)+"长度为:"+len); 10 fileInputStream.read(bytes); 11 System.out.println(new String(bytes)+"长度为:"+len); 12 fileInputStream.close();
输出结果:
abcdefghij长度为:10
klmnopqrst长度为:10
uvwxyzqrst长度为:10
分析:可以看到我们每一次读取了10b长度的数组,但是细心就会发现,我们最后一次明明没有10个b长度的数据,但是也读取了10个,这里我们就分析其中的具体过程,我们第一次读取abcdefghij,
然后第二次读取klmnopqrst,其中就会替换掉原来第一次读取的,我们第三其实读取的是uvwxyz,替换掉第二次读取的klmnopqrst,但是只有uvwxyz,最后的qrst就没有被替换掉,所以就造成了这个结果,
这也是我们平时,下载的安装包,总会比官方的给出的大小会多出那么几kb。
解决方法,在构建为字符串时,将返回的长度,传给new String()构造方法,下面看代码:
1 ////txt的内容是小写字母表,一共26个 2 FileInputStream fileInputStream = new FileInputStream("a.txt"); 3 //创建一个10个字节长度的字节数组 4 byte[] bytes = new byte[10]; 5 int len = fileInputStream.read(bytes); 6 //每读取一次,就new为字符串,并打印 7 System.out.println(new String(bytes,0,len)+"长度为:"+len); 8 len = fileInputStream.read(bytes); 9 System.out.println(new String(bytes,0,len)+"长度为:"+len); 10 len = fileInputStream.read(bytes); 11 System.out.println(new String(bytes,0,len)+"长度为:"+len); 12 fileInputStream.close();
输出结果:
abcdefghij长度为:10
klmnopqrst长度为:10
uvwxyz长度为:6
3、上面我们介绍了常用的字节读取流和字节写出流,下面我们看看字符写出流:FileWriter:继承自writer,使用默认缓冲区大小将文本写入字符文件,编码采用平台默认编码或者是构造方法中指定的编码
构造器 | 描述 |
---|---|
FileWriter(File file) |
|
FileWriter(FileDescriptor fd) |
构造一个
FileWriter 给出的文件描述符,使用该平台的 default charset 。 |
FileWriter(File file, boolean append) |
|
FileWriter(File file, Charset charset) |
|
FileWriter(File file, Charset charset, boolean append) |
|
FileWriter(String fileName) |
构造一个
FileWriter 给出文件名,使用平台的 default charset |
FileWriter(String fileName, boolean append) |
使用平台的 default charset构造一个
FileWriter 给定一个文件名和一个布尔值,指示是否附加写入的数据。 |
FileWriter(String fileName, Charset charset) |
构造一个
FileWriter 给出文件名和charset 。 |
FileWriter(String fileName, Charset charset, boolean append) |
构造一个
FileWriter 给定一个文件名, charset和一个布尔值,指示是否附加写入的数据。 |
通过这些构造方法,可以发现其实跟我们的字节流,都差不多,只是增加了一个编码方式
下面看一下,常用的api(其中writer()和append()方法比较常用,注意一点就是,append()会返回一个this对象,而且append底层调用的也是writer方法),源码如下
Writer |
append(char c) |
将指定的字符追加到此writer。
|
---|---|---|
Writer |
append(CharSequence csq) |
将指定的字符序列追加到此writer。
|
Writer |
append(CharSequence csq, int start, int end) |
将指定字符序列的子序列追加到此writer。
|
abstract void |
close() |
关闭流,先冲洗它。
|
abstract void |
flush() |
刷新流。
|
static Writer |
nullWriter() |
返回一个新的
Writer ,它丢弃所有字符。 |
void |
write(char[] cbuf) |
写一个字符数组。
|
abstract void |
write(char[] cbuf, int off, int len) |
写一个字符数组的一部分。
|
void |
write(int c) |
写一个字符。
|
void |
write(String str) |
写一个字符串。
|
void |
write(String str, int off, int len) |
写一个字符串的一部分。
|
下面我们演示一下,writer方法:
1 FileWriter fileWriter = new FileWriter("a.txt",true); //向a.txt采用追加的模式 2 fileWriter.write(65); //写入对应的字符 3 fileWriter.write(66); 4 fileWriter.write(67); 5 fileWriter.write(68); 6 fileWriter.write(69); 7 fileWriter.write("\n"+"锄禾日当午,"+"\n"); //写入一个字符串 8 fileWriter.write("汗滴禾下土."+"\n"); 9 fileWriter.write("谁知盘中餐,"+"\n"); 10 fileWriter.write("粒粒皆辛苦."+"\n"); 11 char[] chars = {'我','爱','学','习','编','程'}; //写入一个char类型数组 12 fileWriter.write(chars,0,6); 13 System.out.println("ok"); 14 fileWriter.close(); 15 16 输出结果: 17 ABCDE 18 锄禾日当午, 19 汗滴禾下土. 20 谁知盘中餐, 21 粒粒皆辛苦. 22 我爱学习编程
1 FileWriter fileWriter = new FileWriter("a.txt",true); 2 FileWriter append = (FileWriter)fileWriter.append((char) 65); //调用append方法之后,就返回一个writer,我们强转一下 3 append.append((char)66).append((char)67).append((char)68).append((char)69); // 因为返回一个writer对象,所以我们就可以一直追加了 4 append.append("fghijk"); //append一个字符串 5 append.append("xyz",0,3); //append一个从指定位置开始的指定长度的字符串 6 System.out.println("ok"); 7 fileWriter.close(); 8 9 10 输出结果: 11 ABCDEfghijkxyz
4、下面我们看看字符读取流,FileReader:继承自Reader,使用默认缓冲区大小从字符文件中读取文本,FileReader
用于读取字符流。 要读取原始字节流,请考虑使用FileInputStream
。
构造方法也就是比字符写出流,少了append参数。大相径庭。
构造器 | 描述 |
---|---|
FileReader(File file) |
|
FileReader(FileDescriptor fd) |
|
FileReader(File file, Charset charset) |
|
FileReader(String fileName) |
|
FileReader(String fileName, Charset charset) |
给定要读取的文件的名称和
FileReader ,创建一个新的FileReader |
常用API:
int |
read() |
读一个字符。
|
---|---|---|
int |
read(char[] cbuf) |
将字符读入数组。
|
abstract int |
read(char[] cbuf, int off, int len) |
将字符读入数组的一部分。
|
演示read()方法,
1 //a.txt的内容是:计算机中的一切数据都是以二进制存储的 2 FileReader fileReader = new FileReader("a.txt"); 3 while (true){ 4 int read = fileReader.read(); 5 if (read == -1){ //读到末尾就直接跳出循环 6 break; 7 } 8 System.out.print((char) read+" "); 9 } 10 fileReader.close(); 11 12 输出结果:计 算 机 中 的 一 切 数 据 都 是 以 二 进 制 存 储 的
read(char[]):一次性读取一个字符数组
1 //a.txt的内容是:计算机中的一切数据都是以二进制存储的 2 FileReader fileReader = new FileReader("a.txt"); 3 char[] chars = new char[100]; //构造一个字符数组长度为100 4 int length = fileReader.read(chars); //返回读取的长度 5 String str = new String(chars); //将该字符数组构造为一个字符串 6 System.out.println(new String(chars)+"------读取字符串的长度是:"+length); 7 System.out.println("实际长度是:"+str.length()); //打印该字符串的长度 8 fileReader.close(); 9 10 输出结果: 11 计算机中的一切数据都是以二进制存储的 ------读取字符串的长度是:18 12 实际长度是:100
分析:我们读取的字符串数组长度明明是18,但是构造为字符串的长度为什么是100,原因就在于,我们创建字符数组时,字符数组的默认值是空格,我们读取的内容,就会替换掉掉空格,我们存储的内容
只有18个字,所有后面的空格,就没有被替换掉,这也就是我们第11行,中间间隔这么多。我们构造字符串,传入地址,所以字符串长度是100.那么我们如何解决呢,跟我们字节读取流的解决方法一样。下面看代码(黄色是修改部分)。
1 //a.txt的内容是:计算机中的一切数据都是以二进制存储的 2 FileReader fileReader = new FileReader("a.txt"); 3 char[] chars = new char[100]; //构造一个字符数组长度为100 4 int length = fileReader.read(chars); //返回读取的长度 5 String str = new String(chars,0,length); //将该字符数组构造为一个字符串 6 System.out.println(new String(chars)+"------读取字符串的长度是:"+length); 7 System.out.println("实际长度是:"+str.length()); //打印该字符串的长度 8 fileReader.close(); 9 10 11 计算机中的一切数据都是以二进制存储的 ------读取字符串的长度是:18 12 实际长度是:18
分析:我们读取的内容还是一样,会有空格们不会变,因为我们的字符数组长度就是100嘛,但是我们的字符串长度变为18,这正是我们所要的。
5、我们归纳完,字节流和字符流,那么想想这样一个场景,我们获取到网上的一个资源时,该资源是一个字节流,但是我们想获取其中的中文汉字,字节流肯定会出现乱码的,那么字节流如何转换为字符流呢
1、字节输入流------》字符输入流,我们先不将其转换,看看输入结果
1 //字节输入流 ----》 字符输入流
2 FileInputStream fileInputStream = new FileInputStream("a.txt"); //a.txt内容是:计 算 机 中 的 一 切 数 据 都 是 以 二 进 制 存 储 的 3 //InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); 4 while (true ){ 5 int read = fileInputStream.read(); 6 if (read == -1){ 7 break; 8 } 9 System.out.print((char)read+" "); 10 } 11 12 13 输入结果: 14 è ® ¡ ç ® æ º ä ¸ ç ä ¸ å æ ° æ ® é ½ æ ¯ ä » ¥ ä º è ¿ å ¶ å å ¨ ç
哈哈哈,可以看我们模拟从网络上获取的资源,要获取其中的汉字,是会乱码的。下面我们将第三行代码注释去掉。看看效果
//字节输入流 ----》 字符输入流 FileInputStream fileInputStream = new FileInputStream("a.txt"); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); while (true ){ int read = inputStreamReader.read(); if (read == -1){ break; } System.out.print((char)read+" "); } 输出结果: 计 算 机 中 的 一 切 数 据 都 是 以 二 进 制 存 储 的
可以看到转换为字符输入流之后,就没有乱码。
2、字节输出流------》字符输出流(一般是转换为打印流)
1 //字节输出流----》字符输出流 2 FileOutputStream fileOutputStream = new FileOutputStream("a.txt"); 3 PrintWriter printWriter = new PrintWriter(fileOutputStream); 4 printWriter.println("C语言,腻害"); 5 printWriter.println("java,腻害"); 6 printWriter.flush(); //字符流一定要刷新,或者说是关闭流,不然字符保存在缓冲区中,不会写入到指定文件中。 7 8 9 输出结果: 10 11 C语言,腻害 12 java,腻害
可以看到,字节流输出流转为打印流之后,输出是相当方便。腻害腻害!!!!
下面我们介绍一下:缓存读取流
注意,注意,注意:字符输入流(一定要是字符输入流)-------->缓冲读取流,缓冲读取流一次能够读取一行,是相当的nice
下面看看使用最多的方法readLine():读到最后一行会返回null
1 //字符输入流----》缓存读取流 2 FileReader fileReader = new FileReader("a.txt"); 3 BufferedReader bufferedReader = new BufferedReader(fileReader); 4 while (true){ 5 String s = bufferedReader.readLine(); 6 //读到最后一行会返回null,就跳出循环 7 if (s == null){ 8 break; 9 } 10 System.out.println(s); 11 } 12 13 14 读取结果: 15 C语言,腻害 16 java,腻害