Java中的IO流入门

版权声明:转载请注明出处 : https://blog.csdn.net/coder_what/article/details/90405254

Copyright©stonee

种一棵树最好的时间是十年前,其次是现在。

  • 使用前要导包,使用时进行IO处理,使用后释放资源
  • 流按照流向分为输入流和输出流
  • 操作分为字节流(InputStream+OutputStream)和字符流(Reader+Writer)
  • 建议打开相关API文档

流库和I/O流的区别

流库
  • 是Java8中的新特性,对集合对象功能的增强它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作(关于集合,可以参考我这篇博客)。
  • Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
  • 流操作类似于SQL,注重于结果而不是过程
  • 存在于java.util.stream包下
  • 成神之路专栏进行详细讲解流库
I/O流
  • I/O流可以理解为一个内存到硬盘的管道,Java程序通过调用操作系统内部的API来实现对数据的输入输出
  • I/O流和流库没有任何关系
  • 从硬盘读入一个字节序列叫做输入流,写入一个字节序列叫做输出流
  • 字节序列的来源和目的地可以是文件,也可以是网络连接,甚至是内存块
  • 存在于java.io包下

先导知识

资源泄露
  • 当程序已经和目的地或者来源通过操作系统建立了I/O流的连接,但是程序并没有指向这个连接,我们称之为内存泄露,在I/O流中,忘记使用close关闭通道容易产生资源泄露
数据类型
  • 1 byte(字节) = 8 bits(位)
  • Char 由2个byte组成,描述了一个UTF-16的代码单元
  • 16位称为一个代码单元
  • 某个字符对应的码表称为码点,一个码点可能包含多个代码单元
  • String类中的length返回的是代码单元,不是码点
  • ASCII:单字节编码,美国标准编码 ,一共规定了128个字符
  • ISO8859-1:编码属于单字节编码,最多只能表示0-255的字符范围,主要用于西欧语言
  • GBK/GB2312:中文的国际编码,专门用来表示汉字,是双字节编码
  • Unicode和UTF区别
    • Unicode:Java中就是使用此编码方式,也是最标准的一种编码,是使用16进制表示的编码。囊括了基本上所有的字符。但此编码不兼容ISO8859-1标准
    • UTF-8 将Unicode码点编码为1到4个字节的序列
    • UTF-16将Unicode码点编码为1个或者2个代码单元
    • Unicode是一个字符集,UTF是一种编码规则
    • 一个链接
为什么要分字节流和字符流
  • 因为面向字节的流不便于处理以Unicode形式储存信息,所以抽象类ReaderWriter继承了一个专门处理Unicode字符的单独类层次结构
  • 字节流是基于byte的,字符流是基于char的
  • 字节流的主要操作类是OutputStream、InputStream的子类;不用缓冲区,直接对文件本身操作。
  • 字符流的操作字符类型数据,主要操作类是Reader、Writer的子类;使用缓冲区缓冲字符,不关闭流就不会输出任何内容。
  • 字符流需要缓冲区的原因是它需要缓冲区中的字节进行编码
输入流和输入流
  • 输入流(Reader&InputStream)指将硬盘写入内存
  • 输出流(Writer&OutputStream)指将内存中数据写入硬盘
相对路径和绝对路径

I/O流框架

  • 字节流框架
    在这里插入图片描述

  • 字符流框架

在这里插入图片描述

  • 接口继承框架

在这里插入图片描述

字节流

通过I/O框架可以看出,字节流中输入输出流全部继承的抽象类InputStream&OutputStream,我们用的更多的是他们的派生类

派生类
  • FileInputStream&FileOutputStream

    他们可以提供一个附着在磁盘文件上的输入流和输出流,只需要提供路径名就可。这两个类主要用于从文件中读取数据。我们只能读取字节和字节数组,解决这个问题的办法是用DataInputStream类

    byte b = (byte) fin.read();
    
    • 一个基本的程序:
        public static void main(String[] args) throws IOException {
            FileInputStream fileInputStream = new FileInputStream("E://Images//666.jpg");
            FileOutputStream fileOutputStream = new FileOutputStream("copy.jpg");
    
            //这样子拷贝大文件效率特别低
            int a;
            while ((a = fileInputStream.read()) != -1){
                fileOutputStream.write(a);
            }
            fileInputStream.close();
            fileOutputStream.close();
        }
    
  • SequenceInputStream

    将多个输入流合成一个输入流

        public static void main(String[] args) throws IOException {
        
                FileInputStream fileInputStream = new FileInputStream("stonee.txt");
                FileInputStream fileInputStream1 = new FileInputStream("stonee.txt");
                FileInputStream fileInputStream2 = new FileInputStream("stonee.txt");
        
                //创建集合对象
                Vector<FileInputStream> vector = new Vector<>();
                //将流对象存储到集合中
                vector.add(fileInputStream);
                vector.add(fileInputStream1);
                vector.add(fileInputStream2);
        
                //将枚举中的输入流整合成一个
                Enumeration<FileInputStream> enumeration = vector.elements();
                SequenceInputStream sequenceInputStream = 
                    								new SequenceInputStream(enumeration);
        
                FileOutputStream fileOutputStream = new FileOutputStream("copy.txt");
                int a;
                while ((a = sequenceInputStream.read()) != -1){
                    fileOutputStream.write(a);
                }
                sequenceInputStream.close();
                fileOutputStream.close();
            }
    
  • ByteArrayInputStream&ByteArrayOutputStream

    ByteArrayInputStream将一个字节数组当作流输入的来源,而ByteArrayOutputStream则可以将一个字节数组当作流输出目的地

      		FileInputStream fileInputStream = new FileInputStream("stonee.txt");
              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      
              int a;
              while ((a = fileInputStream.read()) != -1){
                  //将读到的数据写入内存缓冲数组中
                  byteArrayOutputStream.write(a);
              }
      
              //获取缓冲区中的值,可以指定不同码表
              //byte[] bytes = byteArrayOutputStream.toByteArray();
              //System.out.println(new String(bytes));
      
              //这样只能用平台默认码表
              System.out.println(byteArrayOutputStream);
      
              //关了也没y用
              //byteArrayOutputStream.close();
              fileInputStream.close();
    
  • ObjectInputStream&ObjectOutputStream

    通过对象序列化可以储存多态集合,前提是类需要实现Serializable接口

    public class Demo4ObjectOutputStream {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Person person = new Person("java",23);
            Person person1 = new Person("python",3);
            Person person2 = new Person("C#",8);
    
            //通过集合一次性调用
            ArrayList<Person> arrayList = new ArrayList<>();
            arrayList.add(person);
            arrayList.add(person1);
            arrayList.add(person2);
    
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.txt"));
            objectOutputStream.writeObject(arrayList);
    
            objectOutputStream.close();
            input();
        }
        public static void input() throws IOException, ClassNotFoundException {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object.txt"));
            ArrayList<Person> arrayList = (ArrayList<Person>)objectInputStream.readObject();
    
    
            for (Person e:
                 arrayList) {
                System.out.println(e);
            }
    
            objectInputStream.close();
        }
    }
    
  • FilterInputStram&FilterOutputStream过滤器以及其子类

    这些文件的子类用于向处理字节的输入输出添加额外功能

    上面的程序栗子中的拷贝效率特别低,是因为每次只传输一个字节,降低了传输效率。解决办法是我们可以加一个缓冲区来:

            byte [] arr = new  byte[1024*8];
            //将file中的字节读入arr中,返回值是个数
            int len;
            while ((len = fileInputStream.read(arr)) != -1 ){
                //这种不会导致重复写
                fileOutputStream.write(arr,0,len);
    
    • BufferedInputStream&BufferedOutputStream

      子类中提供了一个缓冲区的类,,用来解决缓冲区的问题

      和ByteArrayOutStream的区别:

      • 前者是输入输出时创建一个缓冲区,有助于更高效率地利用传输。当缓冲区满了以后就会被写入硬盘,即缓冲区中不能存储所有数据
      • 后者在内存中创建了一个动态变化的数组,可以把数据暂存于这个数组中
    • DataOutputStream&DataInputStream

      可以将字节数组装到更有用的数据类型中

      DataInputStream din = 
          new DataInputStream( 
         		new BufferedInputStream( 
              	new FileInputStream("stonee.txt")));
      //解决了InputStream类不能读取出字节之外的其他数据结构的问题
      double x = din.readDouble();
      
      
注意
  • read()write()方法在执行时都将阻塞,意味着如果流不能被立即访问,那么当前线程就将会被阻塞。available()将会检查可读入的字节数量,可以防止阻塞:
  if(in.available > 0){
      byte[] data = new byte[in.availabe];
      in.read(data)
  }
  • close()关闭时会冲刷缓冲区中的数据,所以不关闭文件不仅可以产生内存泄露,最后一个包也有可能得不到传递。也可以通过flush()来人工冲刷缓冲区
  • 使用write()方法的时候,一定要转换为byte:

     fileOutputStream.write("天下武功,唯快不破".getBytes());
    
  • System.inInputStream类型,意味着我们不仅可从控制台读入信息,也可以从文件中读入信息

字符流

字符流和字节流大同小异,这里我们讨论一下派生类的功能

  • FileReader&FileWriter

  • InputStreamReader&OutputStreamWriter

    • 前者将包含字节(用某种方式表示的字符)输入流转换为可以产生Unicode码元的读入器
    • 后者使用选定的字符编码方式,将Unicode码元的输出流转换为字节流
    Reader in = InputStreamReader(System.in, StandardCharsets.UTF_8);
    
  • PrintWriter

    此类拥有以文本格式打印字符串和数字的方法。可以调用print等文本输出方法

    PrintWriter out = new PrintWrite("stonee.txt","UTF-8");
    //将harry写入stone.txt文件中
    out.print("harry");
    
    //将harry写入控制台上
    System.out.print("harry");
    //System.out属于PrintStream类中
    
    • PrintStreamOutputStream包下
    • PrintWriterWriter包下
  • BufferedReader&BufferedWriter

    用法和字节流中的一样,BufferedReader用于文本输入

    		String line;
            //字符串中没有-1,故返回null
            while ((line = bufferedReader.readLine()) != null){
                bufferedWriter.write(line);
                //输出换行,和/r/n区别,newLine跨平台
                bufferedWriter.newLine();
            }
    

    也可以用Scanner:

    Scanner in = new Scanner(
    	new FileInputStream("stonee.txt"), "UTF-8");
    in.nextInt();
    
注意
  • readline()返回null值时说明读完,read()返回-1说明读完

读写二进制数据

虽然文本格式是可阅读的,但是二进制数据在传递的时候会更高效

DataInput&DataOutput接口
writeChars();//将char字符以二进制形式写入
readChars();//将二进制以char字符读出
writexxx();
readxxx();
  • DataInputStream&DataOutputStream实现了上述接口

  • RandomAccessFile也实现了上述接口

    磁盘文件都是随机访问的,但是网络套接字的输入输出流却不是

    这个类一次性实现两个功能

    	RandomAccessFile randomAccessFile = new RandomAccessFile("copy.txt", "rw");
    	int x = randomAccessFile.read();
    	System.out.println(x);
    	randomAccessFile.seek(5);	//将文件指针移到5
    	randomAccessFile.write(97);
        randomAccessFile.close();
    

其他

  • 文件不关闭可能导致异常,关于带资源的trycatch语句请参考Java中的异常入门

  • 显示的时候用字符流,复制的时候用字节流

  • 本文涉及到的文件Files类和序列化没有详细说明

  • 参考

    • Java核心卷1,2

猜你喜欢

转载自blog.csdn.net/coder_what/article/details/90405254