Java-输入输出流

序言

Java流类图结构:
这里写图片描述

(1) 流的概念和作用

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

注意:在实际的项目中,所有的IO操作都应该放到子线程中操作,避免堵住主线程。

(2) IO流的分类

1、根据处理数据类型的不同分为:字符流和字节流
2、根据数据流向不同分为:输入流和输出流
3、按照流的角色不同划分为:节点流和处理流。

(3) 字符流和字节流

字符流的由来:因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。

字节流和字符流的区别:
A、读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
B、处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
字节流:一次读入或读出是8位二进制。
字符流:一次读入或读出是16位二进制。

设备上的数据无论是图片、视频,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流

(4) 输入流和输出流

输入流只能进行读操作,输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。

输入流:InputStream或者Reader:从文件中读到程序中
输出流:OutputStream或者Writer:从程序中输出到文件中

(5) 节点流和处理流

节点流:可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,直接与数据源相连,读入或读出。节点流也被称为低级流。直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。

图15.3显示了节点流的示意图。从图中可以看出,当使用节点流进行输入和输出时,程序直接连接到实际的数据源,和实际的输入/输出节点连接。

常用的节点流
1、父 类 :InputStreamOutputStream、 Reader、 Writer
2、文 件 :FileInputStream 、 FileOutputStrean 、FileReader 、FileWriter 文件进行处理的节点流
3、数 组 :ByteArrayInputStream、 ByteArrayOutputStream、 CharArrayReader 、CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
4、字符串 :StringReader、 StringWriter 对字符串进行处理的节点流
5、管 道 :PipedInputStream 、PipedOutputStream 、PipedReader 、PipedWriter 对管道进行处理的节点流

这里写图片描述
处理流 则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。图15.4显示了处理流的示意图。

从图15.4可以看出,当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。使用处理流的一个明显的好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源,随着处理流所包装的节点流的变化,程序实际所访问的数据源也相应的发生变化。

常用的处理流
1、缓冲流:BufferedInputStrean 、BufferedOutputStream、 BufferedReader、 BufferedWriter增加缓冲功能,避免频繁读写硬盘。
2、转换流:InputStreamReader 、OutputStreamReader实现字节流和字符流之间的转换。
3、数据流: DataInputStream 、DataOutputStream 等-提供将基础数据类型写入到文件中,或者读取出来。

2、浅析流的原理

java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。

InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

   对于InputStream和Reader而言,它们把输入设备抽象成为一个”水管“,这个水管的每个“水滴”依次排列,如图15.5所示:
   这里写图片描述
    从图15.5可以看出,字节流和字符流的处理方式其实很相似,只是它们处理的输入/输出单位不同而已。输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或者Reader里面取出一个或者多个“水滴”后,记录指针自定向后移动;除此之外,InputStream和Reader里面都提供了一些方法来控制记录指针的移动。

对于OutputStream和Writer而言,它们同样把输出设备抽象成一个”水管“,只是这个水管里面没有任何水滴,如图15.6所示:
这里写图片描述
   正如图15.6所示,当执行输出时,程序相当于依次把“水滴”放入到输出流的水管中,输出流同样采用隐示指针来标识当前水滴即将放入的位置,每当程序向OutputStream或者Writer里面输出一个或者多个水滴后,记录指针自动向后移动。
   
   图15.5和图15.6显示了java Io的基本概念模型,除此之外,Java的处理流模型则体现了Java输入和输出流设计的灵活性。处理流的功能主要体现在以下两个方面。

1、性能的提高:主要以增加缓冲的方式来提供输入和输出的效率。
2、操作的便捷:处理流可能提供了一系列便捷的方法来一次输入和输出大批量的内容,而不是输入/输出一个或者多个“水滴”。

处理流可以“嫁接”在任何已存在的流的基础之上,这就允许Java应用程序采用相同的代码,透明的方式来访问不同的输入和输出设备的数据流。图15.7显示了处理流的模型。


3、流的分类表

注:下表中粗体字所标出的类代表节点流,必须直接与指定的物理节点关联:斜体字标出的类代表抽象基类,无法直接创建实例。
这里写图片描述

4、 IO体系的基类

Io体系的基类即(InputStream/Reader,OutputStream/Writer)。

字节流和字符流的操作方式基本一致,只是操作的数据单元不同——字节流的操作单元是字节,字符流的操作单元是字符。所以字节流和字符流就整理在一起了。

(1) InputStream和Reader

InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可使用的方法。

在InputStream里面包含如下3个方法:
(1) int read(); 从输入流中读取单个字节(相当于从图15.5所示的水管中取出一滴水),返回所读取的字节数据(字节数据可直接转换为int类型)。
(2) int read(byte[] b)从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
(3) int read(byte[] b,int off,int len); 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。
在Reader中包含如下3个方法:
(1) int read(); 从输入流中读取单个字符(相当于从图15.5所示的水管中取出一滴水),返回所读取的字符数据(字节数据可直接转换为int类型)。
(2) int read(char[] b)从输入流中最多读取b.length个字符的数据,并将其存储在字节数组b中,返回实际读取的字符数。
(3) int read(char[] b,int off,int len); 从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。

对比InputStream和Reader所提供的方法,就不难发现这两个基类的功能基本是一样的。InputStream和Reader都是将输入数据抽象成如图15.5所示的水管,所以程序即可以通过read()方法每次读取一个”水滴“,也可以通过read(char[] chuf)或者read(byte[] b)方法来读取多个“水滴”。当使用数组作为read()方法中的参数, 我们可以理解为使用一个“竹筒”到如图15.5所示的水管中取水,如图15.8所示read(char[] cbuf)方法的参数可以理解成一个”竹筒“,程序每次调用输入流read(char[] cbuf)或read(byte[] b)方法,就相当于用“竹筒”从输入流中取出一筒“水滴”,程序得到“竹筒”里面的”水滴“后,转换成相应的数据即可;程序多次重复这个“取水”过程,直到最后。程序如何判断取水取到了最后呢?直到read(char[] chuf)或者read(byte[] b)方法返回-1,即表明到了输入流的结束点。
这里写图片描述

(2) OutputStream和Writer

OutputStream和Writer的用法也非常相似,它们采用如图15.6所示的模型来执行输入。

两个流都提供了如下三个方法:
(1) void write(int c); 将指定的字节/字符输出到输出流中,其中c即可以代表字节,也可以代表字符。
(2) void write(byte[]/char[] buf); 将字节数组/字符数组中的数据输出到指定输出流中。
(3) void write(byte[]/char[] buf, int off,int len ); 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。

因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。

Writer里面还包含如下两个方法:
(1) void write(String str); 将str字符串里包含的字符输出到指定输出流中。
(2) void write (String str, int off, int len); 将str字符串里面从off位置开始,长度为len的字符输出到指定输出流中。

5、IO流应用实例

(1) 文件流的使用

前面说过InputStream和Reader都是抽象类,本身不能创建实例,但它们分别有一个用于读取文件的输入流:FileInputStream和FileReader,它们都是节点流——会直接和指定文件关联。下面程序示范使用FileInputStream和FileReader。
使用FileInputStream读取文件:

public class MyClass {
  public  static void main(String[] args)throws IOException{
      FileInputStream fis=null;
      try {
          //创建字节输入流
          fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
          //创建一个长度为1024的竹筒
          byte[] b=new byte[1024];
          //用于保存的实际字节数
          int hasRead=0;
          //使用循环来重复取水的过程
          while((hasRead=fis.read(b))>0){
              //取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出
            System.out.print(new String(b,0,hasRead));
          }
      }catch (IOException e){
        e.printStackTrace();
      }finally {
          fis.close();
      }
  }
}

注:上面程序最后使用了fis.close()来关闭该文件的输入流,与JDBC编程一样,程序里面打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示的关闭打开的IO资源。

使用FileReader读取文件:

public class FileReaderTest {
    public  static void main(String[] args)throws IOException{
        FileReader fis=null;
        try {
            //创建字节输入流
            fis=new FileReader("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建一个长度为1024的竹筒
            char[] b=new char[1024];
            //用于保存的实际字节数
            int hasRead=0;
            //使用循环来重复取水的过程
            while((hasRead=fis.read(b))>0){
                //取出竹筒中的水滴(字节),将字节数组转换成字符串进行输出
                System.out.print(new String(b,0,hasRead));
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            fis.close();
        }
    }
}

可以看出使用FileInputStream和FileReader进行文件的读写并没有什么区别,只是操作单元不同而且。

FileOutputStream和FileWriter

FileOutputStream/FileWriter是Io中的文件输出流,下面介绍这两个类的用法。

FileOutputStream的用法:

public class FileOutputStreamTest {
    public  static void main(String[] args)throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try {
            //创建字节输入流
            fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建字节输出流
            fos=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\newTest.txt");

            byte[] b=new byte[1024];
            int hasRead=0;

            //循环从输入流中取出数据
            while((hasRead=fis.read(b))>0){
                //每读取一次,即写入文件输入流,读了多少,就写多少。
                fos.write(b,0,hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            fis.close();
            fos.close();
        }
    }
}

运行程序可以看到输出流指定的目录下多了一个文件:newTest.txt, 该文件的内容和Test.txt文件的内容完全相同。FileWriter的使用方式和FileOutputStream基本类似,这里就带过。

注: 使用java的io流执行输出时,不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,可能还可以将输出流缓冲区中的数据flush到物理节点中里(因为在执行close()方法之前,自动执行输出流的flush()方法)。java很多输出流默认都提供了缓存功能,其实我们没有必要刻意去记忆哪些流有缓存功能,哪些流没有,只有正常关闭所有的输出流即可保证程序正常。

(2) 缓冲流的使用

(BufferedInputStream/BufferedReader, BufferedOutputStream/BufferedWriter)。

下面介绍字节缓存流的用法(字符缓存流的用法和字节缓存流一致就不介绍了):

public class BufferedStreamTest {
    public  static void main(String[] args)throws IOException {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        BufferedInputStream bis=null;
        BufferedOutputStream bos=null;
        try {
            //创建字节输入流
            fis=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\Test.txt");
            //创建字节输出流
            fos=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\newTest.txt");
            //创建字节缓存输入流
            bis=new BufferedInputStream(fis);
            //创建字节缓存输出流
            bos=new BufferedOutputStream(fos);

            byte[] b=new byte[1024];
            int hasRead=0;
            //循环从缓存流中读取数据
            while((hasRead=bis.read(b))>0){
                //向缓存流中写入数据,读取多少写入多少
                bos.write(b,0,hasRead);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            bis.close();
            bos.close();
        }
    }
}

可以看到使用字节缓存流读取和写入数据的方式和文件流(FileInputStream,FileOutputStream)并没有什么不同,只是把处理流套接到文件流上进行读写。缓存流的原理下节介绍。

上面代码中我们使用了缓存流和文件流,但是我们只关闭了缓存流。这个需要注意一下,当我们使用处理流套接到节点流上的使用的时候,只需要关闭最上层的处理就可以了。java会自动帮我们关闭下层的节点流。

(3) 转换流的使用

(InputStreamReader/OutputStreamWriter)

下面以获取键盘输入为例来介绍转换流的用法。java使用System.in代表输入。即键盘输入,但这个标准输入流是InputStream类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其包装成BufferedReader,利用BufferedReader的readLine()方法可以一次读取一行内容,如下代码所示:

public class InputStreamReaderTest {
    public  static void main(String[] args)throws IOException {
        try {
            // 将System.in对象转化为Reader对象
            InputStreamReader reader=new InputStreamReader(System.in);
            //将普通的Reader包装成BufferedReader
            BufferedReader bufferedReader=new BufferedReader(reader);
           String buffer=null;
           while ((buffer=bufferedReader.readLine())!=null){
            // 如果读取到的字符串为“exit”,则程序退出
               if(buffer.equals("exit")){
                   System.exit(1);
               }
               //打印读取的内容
               System.out.print("输入内容:"+buffer);
           }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
        }
    }
}

上面程序将System.in包装成BufferedReader,BufferedReader流具有缓存功能,它可以一次读取一行文本——以换行符为标志,如果它没有读到换行符,则程序堵塞。等到读到换行符为止。运行上面程序可以发现这个特征,当我们在控制台执行输入时,只有按下回车键,程序才会打印出刚刚输入的内容。

(4) 对象流的使用

(ObjectInputStream/ObjectOutputStream)

该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口

写入对象:

public static void writeObject(){
    OutputStream outputStream=null;
    BufferedOutputStream buf=null;
    ObjectOutputStream obj=null;
    try {
          //序列化文件輸出流
          outputStream=new FileOutputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\myfile.tmp");
          //构建缓冲流
          buf=new BufferedOutputStream(outputStream);
          //构建字符输出的对象流
          obj=new ObjectOutputStream(buf);
          //序列化数据写入
          obj.writeObject(new Person("A", 21));//Person对象
          //关闭流
          obj.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读取对象:

 /**
   * 读取对象
   */
 public static void readObject() throws IOException {
     try {
            InputStream inputStream=new FileInputStream("E:\\learnproject\\Iotest\\lib\\src\\main\\java\\com\\myfile.tmp");
            //构建缓冲流
            BufferedInputStream buf=new BufferedInputStream(inputStream);
            //构建字符输入的对象流
            ObjectInputStream obj=new ObjectInputStream(buf);
            Person tempPerson=(Person)obj.readObject();
            System.out.println("Person对象为:"+tempPerson);
            //关闭流
            obj.close();
            buf.close();
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

使用对象流的一些注意事项:
1.读取顺序和写入顺序一定要一致,不然会读取出错。
2.在对象属性前面加transient关键字,则该对象的属性不会被序列化。

(5) 特殊流的使用

(DataInputStream、DataOutputStream)

有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,成员数据的类型假设都是Java的基本数据类型,这样的需求不必使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。

DataInputStream的好处在于在从文件读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时将停止,使用对应的readUTF()和readInt()方法就可以正确地读入完整的类型数据。

下面是一个例子:

    package com.hxw;  
    public class Member {  
        private String name;  
        private int age;  

        public Member() {  
        }  
        public Member(String name, int age) {  
            this.name = name;  
            this.age = age;  
        }  
        public void setName(String name){  
            this.name = name;  
        }  
        public void setAge(int age) {  
            this.age = age;  
        }  
        public String getName() {  
            return name;  
        }  
        public int getAge() {  
            return age;  
        }  
    }  

打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。下面的代码简单示范了如何实现这个需求。

  package com.hxw;  
  import java.io.*;  
  public class DataStreamDemo {  
       public static void main(String[]args)  {  
           Member[] members = { newMember("Justin",90),  
                                newMember("momor",95),  
                                newMember("Bush",88)
                              };  
           try{  
               DataOutputStreamdataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));  
               for(Member member:members){  
                  //写入UTF字符串  
                  dataOutputStream.writeUTF(member.getName());  
                  //写入int数据  
                  dataOutputStream.writeInt(member.getAge());  
                }  
              //所有数据至目的地  
              dataOutputStream.flush();  
              //关闭流  
              dataOutputStream.close();  
              DataInputStreamdataInputStream = new DataInputStream(new FileInputStream(args[0]));  
             //读出数据并还原为对象  
             for(inti=0;i<members.length;i++) {  
               //读出UTF字符串  
               String name =dataInputStream.readUTF();  
               //读出int数据  
               int score =dataInputStream.readInt();  
               members[i] = newMember(name,score);  
             }  
            //关闭流  
            dataInputStream.close();  
            //显示还原后的数据  
            for(Member member : members)  
            {  
               System.out.printf("%s\t%d%n",member.getName(),member.getAge());  
            }  
         }  
         catch(IOException e)  
         {  
                e.printStackTrace();  
         }  
      }  
    }  

猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/82156169