导语:
打开简书,看到自己的文章被浏览了五十多次的时候真的很开心,然后发现有几个喜欢一个粉丝的时候,真的是非常开心,同时谢谢你们给了我继续写下去的动力,非常感谢。这篇文章写一写关于IO 流中自己学到的一些基本知识,以及遇到的问题,希望能够帮助到大家。
1.IO 流的引入
我们可以利用 File 类将 java 程序跟硬盘上的文件联系起来。我们可以获取其中某些属性。但是,文件的内容我们不能操作,读取。这时候就引入了 对文件进行操作的 IO 流。
我们可以生动形象的把 IO 流当作一根根管子,管子的一端怼到目标文件,另一端怼到程序中。我们写的程序在这里相当于一个数据传输的中转站。
2. IO 流分类
2.1 按照读取单位划分:
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
2.2 按照功能划分
- 节点流:直接从源文件读取数据到程序中 —— 一根管。
- 处理流:需要多个流结合使用 —— 多根管。
在我目前所看到的所有程序中,想要利用 IO 流,必须先用输入输出字节流,或者输入输出字符流连接到目标文件!!!
下面主要写一下各个流的用法。如有疑问以及不妥的地方欢迎指正,感激不尽!
3. 字节流--FileInputStream,FileOutputStream
3.1 文件 ---> 程序(以程序为主体,对于程序来说属于对内输入,所以要用输入流)
3.1.1 利用单个字节
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.确定文件:
4 File f=new File("i:/test/haha.txt");
5 //2.创建一根管,然后连接文件:
6 FileInputStream fis=new FileInputStream(f);
7 //3.进行动作: 吸 (流:读取)
8 int n=fis.read();
9 while(n!=-1){//读到文件末尾就是-1
10 System.out.print(n+"\t");
11 n=fis.read();
12 }
13 //4.关闭流(无论什么时候,关闭流是必须的):
14 fis.close();
15 }
16 }
首先要确定被读取文件的地址(代码第 4 行),然后创建一根管(就是 IO 流),一端怼到该文件上去,另一端怼到程序中,进行吸的动作(也就是程序读取文件)。(怎么样,理解的还可以不,哈哈)
从上面的代码第 8 行和第 11 行可以看出 这种方法是一个字节一个字节的将文件中的信息读入到程序中。
缺点:
- 运行结果会出现乱码;
- 一个字节一个字节读取 ,效率太低;
3.1.2 利用数组缓冲区
1. public class Test02 {
2. public static void main(String[] args) throws IOException {
3. //创建文件:
4. File f=new File("i:/test/haha.txt");
5. //创建一根管,然后连接文件:
6. FileInputStream fis=new FileInputStream(f);
7. //进行动作: 吸 (流:读取)
8. byte[] b=new byte[8];// 随便创建了一个byte数组,长度为8
9. int len=fis.read(b);// len是数组中被占用的长度
10. while(len!=-1){// 读到文件末尾就是-1
11. System.out.print(len+"\t");
12. len=fis.read(b);
13. }
14. //关闭流:
15. fis.close();
16. }
}
这种方法是定义了一个数组(第 8 行)8个字节,通俗一点说,就是每 8 个字节为一组进行读取,用数组将文件中的信息读入到程序中,效率比比上一种方法高。
3.2 程序 ---> 文件(以程序为主体,对于程序来说属于对外输出的,所以要用输出流)
3.2.1 利用单个字节
1 public class Test03 {
2 public static void main(String[] args) throws IOException {
3 //创建文件:
4 File f=new File("i:/test/haha.txt");
5 //创建一根管,然后连接文件:
6 FileOutputStream fos=new FileOutputStream(f);
7 //进行动作: 吐 (流:写入)
8 String str="abc123你好";
9 //将字符串转化成字节:
10 byte[] bytes=str.getBytes();
11 for (int i = 0; i < b.length; i++) {
12 fos.write(b[i]);
13 }
14 //关闭流:
15 fos.close();
16 }
17 }
首先要确定要把文件读取到哪儿(代码第 4 行),然后创建一根管(就是 IO 输出流),一端怼到该文件上去,另一端怼到程序中,进行吐的动作(也就是把内容从程序中写出去)。因为字节输出流只能一个字节一个字节的向外读取,所以要调用 String 的 getBytes() 方法将 String 类型数据准换成 byte 类型,而该方法返回值为一个 byte 类型数组。
3.2.2 利用缓冲数组
1 public class Test04 {
2 public static void main(String[] args) throws IOException {
3 //创建文件:
4 File f=new File("i:/test/haha.txt");
5 //创建一根管,然后连接文件:
6 FileOutputStream fos=new FileOutputStream(f);
7 //进行动作: 吐 (流:写入)
8 String str="abc123你好";
9 byte[] bytes=str.getBytes();//将字符串转化成字节:因为fos只能一个字节一个字节的写
10 for(byte b:bytes){
11 fos.write(b);
12 }
13 //关闭流:
14 fos.close();
15 }
16 }
定义了一个数组(第 9 行),调用 String 类型的 getBytes() 方法,将字符串转化为字节,用一个数组接住该方法的返回值。然后再用循环遍历将数组中的信息读取到文件中,效率比比上一种方法高。
一定要记得关流!!!
3.3 文件的复制
3.3.1 利用单个字节进行复制
1 public class TestCopyDoc {
2 public static void main(String[] args) throws IOException {
3 //先确定文件:
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //创建两个输入输出流(两个管):
7 FileInputStream fis=new FileInputStream(f1);//输入流
8 FileOutputStream fos=new FileOutputStream(f2);//输出流
9 //开始动作: 吸-----吐
10 int n=fis.read();//吸;
11 while(n!=-1){
12 fos.write(n);//吐;
13 n=fis.read();//吸;
14 }
15 //关闭流:
16 fis.close();
17 fos.close();
18 }
19 }
功能:就是利用输入输出字节流,一个字节一个字节的将 i:/test/haha.txt 文件中的内容复制到 i:/test/demo.txt 中去。
程序相当于一个中转站(开篇的 IO 流示意图),利用字节输入流(FileInputStream)将 haha.txt 中的内容读取到程序中,然后利用字节输出流(FileOutputStream)从程序中写出到目标文件 demo.txt.
3.3.2 利用数组缓冲区
1 public class TestCopyDoc02 {
2 public static void main(String[] args) throws IOException {
3 //先确定文件:
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //创建两个输入输出流(两个管):
7 FileInputStream fis=new FileInputStream(f1);//输入流
8 FileOutputStream fos=new FileOutputStream(f2);//输出流
9 //开始动作: 吸-----吐
10 byte[] b=new byte[8];
11 int len=fis.read(b);//len---是这个数组中被占用的数量
12 while(len!=-1){
13 fos.write(b,0,len);
14 len=fis.read(b);
15 }
16 //关闭流:
17 fis.close();
18 fos.close();
19 }
20 }
功能:定义了一个数组(第 10 行)8个字节,每 8 个字节为一组进行读取,FileInputStream 利用数组将 haha.txt 中的信息读入到程序中(第 11 行),其中 len 表示这个数组中被占用的数量,当读取到文件结尾的时候 len = -1(这是规定,我也不知道为啥是 -1 ,不是其他的数字,这个记住就好。。。);然后 FileOutputStream 把程序中读取到的信息写入到目标文件中(第 14 行)。
4. 字符流--FileReader,FileWriter
- 和字节流一样,用字符流进行文件的复制也分为两种方法,一种是利用单个字符进行读取和写出,另一种是利用数组进行读取和写出。
4.1 利用单个字符
1 public class TestFileCopy001 {
2 public static void main(String[] args) throws IOException {
3 //1.创建目标文件,源文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.创建字符流
7 FileReader fr=new FileReader(f1);
8 FileWriter fw=new FileWriter(f2);
9 //3.读取
10 int n=fr.read();
11 while(n!=-1){
12 fw.write(n);
13 n=fr.read();
14 }
15 //4.关闭流
16 fw.close;
17 fr.close;
18 }
19 }
- 功能:和 3.3.1 中一样,只不过是利用字符流来进行操作。
4.2 利用数组缓冲区---char[]
1 public class TestFileCopy001 {
2 public static void main(String[] args) throws IOException {
3 //1.创建目标文件,源文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.创建字符流
7 FileReader fr=new FileReader(f1);
8 FileWriter fw=new FileWriter(f2);
9 //3.读取
10 char[] ch=new char[8];
11 int len=fr.read(ch); //len---是这个数组中被占用的数量
12 while(len!=-1){
13 fw.write(ch,0,len);
14 len=fr.read(ch);
15 }
16 //4.关闭流
17 fw.close;
18 fr.close;
19 }
20 }
- 功能:和 3.3.2 类似,只不过是定义了一个字符数组(代码第 10 行)进行读取和写出操作。
另外要说明的是:用字符流复制非纯文本的文件都是不行的,都是耍流氓!因为用字符流复制的时候,它会按照系统的字符码表进行查找和替换,把二进制数据全部按照码表替换了,但是图片的一些代码能在码表中找到相对应的编码,就转换成编码,另外还有一些找不到,JVM就会用类似的编码代替,那么你再打开的时候就肯定不是图片了。
总之,记住千万不要耍流氓啊!!
5. 缓冲字节流--BufferedInputStream,BufferedOutputStream
先上图(以下图都是按照个人理解画出来的,如有错误还请指正):
我们上面写的代码都是利用字节流进行文件的复制。以上图为例,每次读取或写出都会对硬盘上的文件访问一次,缺点就是对硬盘的访问次数太多,对硬盘来说这是有害的。这时,就可以利用缓冲字节流。
再上图:
根据下面的代码来理解一下这张图:
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.源文件,目标文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.创建流:4个
7 FileInputStream fis =new FileInputStream(f1);
8 FileOutputStream fos=new FileOutputStream(f2);
9 BufferedInputStream bis=new BufferedInputStream(fis);//创建缓冲流bis
10 BufferedOutputStream bos=new BufferedOutputStream(fos);//创建缓冲流bos
11 //3.读取
12 byte[] b=new byte[8];
13 int len=bis.read(b);//bis 读取数据
14 while(len!=-1){
15 bos.write(b,0,len);//bos 写出数据
16 len=bis.read(b);
17 }
18 //4.关闭流:
19 bis.close();
20 bos.close();
21 fis.close();
22 fos.close();
23 }
24 }
程序解释:先创建输入字节流 fis (第 7 行)怼到目标文件(i:/test/haha.txt),然后创建缓冲字节流 bis(如图红色),bis 套在 fis 上使用(相当于一根管子上套了另一根管子)。使用缓冲流会有一个缓冲区,fis(字节输入流)会尽可能多的把源文件中的数据读取到缓冲区,然后 bis 再利用缓冲数组从缓冲区中每 8 个字节为一组的读取。写出的过程和读取的过程正好相反就不在此赘述啦。(如果还不理解可以留下评论)
这种方式属于一根流套在另一根流,也就是管套管。上使用这样的话就减少了对硬盘的访问次数。
6. 缓冲字符流--BufferedReader,BufferedWriter
缓冲字符流和上面的缓冲字节流的运行原理一样,只不过一个使用字节流,一个使用字符流而已。
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //1.源文件,目标文件
4 File f1=new File("i:/test/haha.txt");
5 File f2=new File("i:/test/demo.txt");
6 //2.创建流:4个
7 FileReader fis =new FileReader(f1);
8 FileWriter fos=new FileWriter(f2);
9 BufferedReader bis=new BufferedReader(fis);//创建缓冲字符输入流 bis
10 BufferedWriter bos=new BufferedWriter(fos);//创建缓冲字符输出流 bos
11 //3.读取
12 char[] b=new char[8];
13 int len=bis.read(b);//bis 读取数据
14 while(len!=-1){
15 bos.write(b,0,len);//bos 写出数据
16 len=bis.read(b);
17 }
18 //4.关闭--先关高级流,再关闭低级流
19 bis.close();
20 bos.close();
21 fis.close();
22 fos.close();
23 }
24 }
这种方式也是属于管套管来操作数据,效率比上面的缓冲字节流要高(因为使用的是字符流)。
之前操作数据,要么是一个字节一个字节的读取,或者是一个数组一个数组的读取。那么下面介绍一种效率更高的方式:一整行一整行的读取数据。
1 //3.读取
2 String str=bis.readLine();//利用缓冲字符输入流 bis 整行整行的读取数据;
3 while(str!=null){
4 bos.write(str);
5 bos.newLine();//在目标文件中换行
6 str=bis.readLine();//利用缓冲字符输出流 bos 整行整行的写出数据;
7 }
程序其他部分不变,只是读取的方式不一样。
7. System对 IO 的支持
在这里补充一下,我们写程序的时候经常会用到键盘输入这个语句:Scanner sc=new Scanner(System.in);
那么我有没有考虑过键盘输入这件事到底是谁来完成的呢,是 Scanner 还是 Sytem.in ?
其实,键盘录入这个功能是由 System.in 来完成的,Scanner 只是起到一个扫描器的作用。System.in 会返回一个 InputStream 类型的变量,也就是返回一个流。那么Scanner sc=new Scanner(System.in);
这条语句可以通俗的理解为有一个扫描器 Scanner,一个键盘,它们俩之间是用一根管子(流)连接起来,键盘录入的数据通过这根管子传进扫描器中。
还有一个比较坑的地方,写出来给大家做个提醒:
1 public class TestIO {
2 public static void main(String[] args) throws IOException {
3 InputStream in = System.in;
4 byte[] b=new byte[8];
5 int n = in.read(b);//用数组读取键盘输入的数据,返回结果为 b 被占用的长度
6 System.out.print(n);
7 }
8 }
代码第 5 行中,in.read(b) 按理说返回结果为 b 被占用的长度啊,那么,我输入数字 1,结果输出 3,也就是说,占用了 3 个字节!哇,当时整的我很郁闷,不就占用了一个字节吗,应该是 1 才对嘛。
后来才搞清楚,运行这段代码的时候,你输入 1 之后,按下了 Enter 键,也就是输入完数据之后进行了回车、换行,而回车和换行在 ASCII 表中对应的数值分别是 13 和 10,它们俩又各占了一个字节,所以是占用了 3 个字节,输出结果为 3。不知道我有没有表达清楚?
- 补充一点:字节流转化为字符流
1 public class Test {
2 public static void main(String[] args) throws IOException {
3 //输入:
4 InputStream in = System.in;
5 //转换流--单向转换:字节流-->字符流转换
6 InputStreamReader isr=new InputStreamReader(in);
7 BufferedReader br=new BufferedReader(isr);
8 //输出:
9 FileWriter fw=new FileWriter("d:/bjsxt/t.txt");
10 BufferedWriter bw=new BufferedWriter(fw);
11 //读取:
12 String str=br.readLine();
13 while(!str.equals("byebye")){
14 bw.write(str);
15 bw.newLine();
16 str=br.readLine();
17 }
18 //关闭流:
19 bw.close();
20 br.close();
21 fw.close();
22 isr.close();
23 in.close();
24 }
25 }
这段代码中,主要想表达的就是第 6 行,将字节流转化为字符流,其他的就是和之前的代码差不多,都是读取写入。
8. 数据流--对基本数据类型处理--DataInputStream,DataOutputStream
8.1 将基本数据类型的东西输入到目标文件中去:
1 public class Test001 {
2 public static void main(String[] args) throws IOException {
3 DataOutputStream dos=new DataOutputStream(new 4FileOutputStream(new File("d:/haha/demo001.txt")));//将流套在一起使用
5 dos.writeInt(12);//int
6 dos.writeChar('\n');
7 dos.writeDouble(12.0);//double
8 dos.writeChar('\n');
9 dos.writeUTF("hellojava你好");//
10 //关闭流
11 dos.close();
12 }
13 }
代码第 3 行是将各个需要的流套在一起直接一句代码写出来了,应该能看懂吧。
执行完这段程序之后如果打开目标文件,将会看到...乱码,哈哈。别慌,因为这是给程序看的,咱可能看不懂的。那么要是想看该怎么办呢?有办法,在一个程序中执行下段代码:
1 DataInputStream dis=new DataInputStream(new FileInputStream(new File("d:/bjsxt/demo001.txt")));
3 System.out.println(dis.readInt());//int
4 System.out.println(dis.readChar());//char
5 System.out.println(dis.readDouble());//double
6 System.out.println(dis.readChar());//char
7 System.out.println(dis.readUTF());
8 dis.close();//关闭流
这段代码是为了将文件中的内容写入到程序中,然后在控制台输出。第一句不用解释了吧。而且,重要的是写进文件的顺序和读到程序中的顺序必须一致!就是说,假如你写进文件的第一个是 int 类型的,那么输出的第一个也必须是 int 型的,这样一一对应才能保证不出错。
9. 对象流--对引用数据类型处理--ObjectInputStream,ObjectOutputStream
9.1 放入String 类型数据:
1 public class Test002 {
2 public static void main(String[] args) throws FileNotFoundException, IOException {
3 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File("d:/haha/t.txt")));
4 oos.writeObject("java");
5 oos.close();//关闭流
6 }
7 }
这段代码就是将 “java” 写到目标文件中去。
9.3 现在要写入 Person 的一个对象:
假如说我自定义了一个 Person 类。,然后我现在要把一个 Person 类型的对象放进目标文件中去,按照上面的放 String 类型数据的方法:
1 public class Test002 {
2 public static void main(String[] args) throws FileNotFoundException, IOException {
3 ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File("d:/bjsxt/t.txt")));
4 oos.writeObject(new Person("lili", 18));
5 oos.close();
6 }
7 }
这段程序运行结果如下:
发现出错了,“NotSerializableExeption”,啥意思呢,就是说** Person 类没有序列化!!**
那么怎么解决呢?方法:实现 Serializable 方法,加序列号。
谨记:以后如果要对类的对象进行网络传输一定要实现序列化!!!
如果您能看到这里,我真的表示万分感谢,这篇文章如果有什么错误的地方,或者您不理解的地方,欢迎留言。另外我这儿有这部分学习的视频,如果需要的话,也可以留下邮箱,我发给您。就这样,谢谢各位!