Java---IO 流

作者: 我是小徐同学 | 来源:发表于2017-05-28 15:17 被阅读0次

    导语:

    打开简书,看到自己的文章被浏览了五十多次的时候真的很开心,然后发现有几个喜欢一个粉丝的时候,真的是非常开心,同时谢谢你们给了我继续写下去的动力,非常感谢。这篇文章写一写关于IO 流中自己学到的一些基本知识,以及遇到的问题,希望能够帮助到大家。

    1.IO 流的引入

    我们可以利用 File 类将 java 程序跟硬盘上的文件联系起来。我们可以获取其中某些属性。但是,文件的内容我们不能操作,读取。这时候就引入了 对文件进行操作的 IO 流。

    IO 流示意图.png

    我们可以生动形象的把 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 方法,加序列号。 调试方法
    谨记:以后如果要对类的对象进行网络传输一定要实现序列化!!!

    如果您能看到这里,我真的表示万分感谢,这篇文章如果有什么错误的地方,或者您不理解的地方,欢迎留言。另外我这儿有这部分学习的视频,如果需要的话,也可以留下邮箱,我发给您。就这样,谢谢各位!

    相关文章

      网友评论

        本文标题:Java---IO 流

        本文链接:https://www.haomeiwen.com/subject/qddvxxtx.html