概念

  • 流:从一端移动到另一端,有源头与目的地。

    源头即数据源:文件、数组、数据库、网络连接

    目的地:程序

    分类

  • 按流向:输入流与输出流
  • 按处理数据:

    字节类:二进制,可以处理一切文件(纯文 本、doc、音频、视频等)

    字符流:文本文件,只能处理纯文本(.txt、.htm、.java)
  • 按功能分类:

    1. 节点流(最接近源头的;以下都是四个(不包括括号的话)都是抽象类,不可以new对象):包裹源头
    • 字节流的节点流:(File)InputStream、(File)OutputStream
      • InputStream类是所有字节输入流的父类

- OutputStream类是所有字节输出流的父类

  • 字符流的节点流:(File)Reader、(File)Writer
    • Reader类是所有字节输出流的父类

- Writer类是所有字节输出流的父类


2. 处理流(提高性能):

  • (1)缓冲流(带缓冲区的输入输出流,减少访问磁盘次数,速度更快),是io处理大文件一般要用到的:
    • 字节缓冲流:BufferedInputStream、BufferedOutputStream;
    • 字符缓冲流:BufferedReader、BufferedWriter
    • 缓冲思想
      • 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多。这是加入了数组这样的缓冲区效果。java本身在设计的时候,也考虑到了这样的设计思想,所以提供了字节缓冲区流
    • 比如BufferedInputStream
      • BufferedInputStream内置了一个缓冲区(数组),BufferedInputStream会一次性从文件中读取8192个, 存在缓冲区中, 并返回给程序。程序再次读取时, 就不用找文件了, 直接从缓冲区中获取。直到缓冲区中所有的都被使用过, 才重新从文件中读取8192个。
    • 比如BufferedOutputStream
      • BufferedOutputStream也内置了一个缓冲区(数组),程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中。直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
    • 用缓冲流拷贝文件
public class FileTest {

    public static void main(String[] args) throws Exception{
        FileInputStream fis = new FileInputStream("aaa.txt");
        FileOutputStream fos = new FileOutputStream("bbb.txt");

        BufferedInputStream bis=new BufferedInputStream(fis);
        //使用装饰模式,把fis装饰进去bis中。使用缓冲读取速度变快
        BufferedOutputStream bos=new BufferedOutputStream(fos);

        int b;
        //如果自定义了缓冲数组并传入的话就会用自定义的
        while((b=bis.read())!=-1){ //数目随机地读到内置缓冲数组中,即使是很小的文本也要跑十几遍循环,一次几十~100个字节地读
         bos.write(b);
        }
        bis.close();
        bos.close();
    }
}
  • (2)转换流:字节流转为字符流(处理乱码 编码集与解码集之间的关系)(要知道字节流的编码集才能解回来)

    读字节文件的基本操作流程

  1. 建立联系(封装File对象)
  2. 选择流
  3. 操作:用缓冲数组多次读取 -> byte[] car = new byte10+read+循环读取
  4. 关闭资源

    public static void main(String[] args) {
        //1.建立联系
        File src = new File("e:/xp/test/a.txt"); //读取纯文本,应该会出现乱码,因为是用字节流来读取的,无妨,真实操作下文件应该是从源文件到目标文件的,像这样中途打印出来的不是主要目标
        //2.选择流
        InputStream is = null;
        //3.操作
        byte[] car = new byte[10]; //定长一点中文乱码概率小一点
        int len = 0;//实际读取长度(最后一次可能不到10个)
        try {
            is = new BufferedInputStream(new FileInputStream(src));
            //循环读取
            while(-1 != (len = is.read(car))) { //read(byte[] car)方法,将文件分批读入字节数组,返回实际读取个数,最后一次读取后,再读取一次并返回-1;除了最后一次,每次都是准确10个字节
                //字节数组转字符串
                String info = new String(car,0,len); //从car数组第0个开始读取并转换len长度个字节
                System.out.print(info); //要想恢复文件原样就不要用println
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("文件不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("读取文件失败");
        }finally {
            //4.释放资源
            if(null != is) { //占了资源才需要释放
                try {
                is.close(); //close()实际就是释放is的内存资源,相当于断开引用
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("释放资源失败");
                }
            }
        }
    }
    

    写字节文件的基本操作流程

  5. 建立联系:将目的地封装成File对象
  6. 选择流: OutputStream->FileOutputStream
  7. 操作:write()(方法而非Write字符输出流)+flush
  8. 关闭资源

    public static void main(String[] args) {
        //1.建立联系
        File dest = new File("e:/xp/test/a.txt");
        OutputStream os = null;
        try {
            //2.选择流(追加一层缓冲流提高性能)
            os = new BufferedOutputStream(new FileOutputStream(dest,true)); //true表示目标文件已有内容时追加,默认为false覆盖
            //3.写出
            String car = "DuDu i love you!\r\n";//写出内容
            byte[] car2 = car.getBytes();//转为字节数组才能写入文件
            os.write(car2, 0, car2.length); //从0位写出car2.length位出去
            os.flush();//强制刷出,否则最后一次不满可能会残留在流中
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("文件不存在");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("写入文件失败")
        }finally {
            //4.释放引用的内存
            if(null != os) {
                try {
                    os.close(); //实际上执行close方法也会先flush刷出
                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("关闭资源失败")
                }
            }
        }
    
    }
    

    拷贝文件(读+写)

    public static void main(String[] args) throws Exception{
        //1,建立联系
        File src=new File("E:/xp/test/1.jpg");
        File dest=new File("E:/xp/test/100.jpg");
        //2.选择流
        InputStream is=new FileInputStream(src);
        OutputStream os=new FileOutputStream(dest);
        //3.文件拷贝(循环读取+写出)
        byte[] flush=new byte[1024];
        int len=0;
        while(-1!=(len=is.read(flush))) { //读取
            os.write(flush, 0, len); //写出
        }
        os.flush();
        //4.关闭资源(先开的后关)
        os.close();
        is.close();
    }
    

    读字符文件的基本操作流程

  9. 建立联系(封装File对象)
  10. 选择流
  11. 操作:用缓冲数组多次读取 -> char[] flush = new char[20]+read+循环读取
  12. 关闭资源

    public static void main(String[] args) {
        //1.建立联系
        File src = new File("e:/xp/test/test.txt");
        //2.选择流
        Reader reader = null;
        char[] flush = new char[20];
        int len = 0;
        try {
            reader = new FileReader(src);
            //3.操作
            while(-1!=(len=reader.read(flush))) { //这一步报编码错误可能是文本不是用notepad写的
                /* 可以这样打印 (不过println自己会断行,再加上原文也有换行符,所以断行可能会乱)*/
                //String flush1 = new String(flush,0,len); //字符数组转字符串
                //System.out.println(flush1);
    
                /* 也可以这样  */
                for(int i=0;i<len;i++) {
                    System.out.print(flush[i]); //遇到换行符自动换行,断行不会乱
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //4.关闭资源
            if(null!=reader) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    写字符文件的基本操作流程

  13. 建立联系:将目的地封装成File对象
  14. 选择流: Writer->FileWriter
  15. 操作:write()/append()+flush
  16. 关闭资源

    public static void main(String[] args) {
        File dest = new File("e:/xp/test/test1.txt");
        Writer wr = null;
        String car = "PigPig i love you!\r\n";
        try {
            wr = new FileWriter(dest,true);//true则文件已有内容时追加
            wr.write(car);
            wr.append("PigPig love me,too\r\n");//同write()
    
            wr.flush();
        } 
        catch(FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=wr) {
                try {
                    wr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    其他流

    一、 节点流

  • 字节数组节点流:流的来源或目的地不一定是文件,也可以是内存中的一块空间,例如一个字节数组。字节数组节点流就是将数组当作输入来源、输出目的地的类。这种流用于支持内存虚拟文件或内存映像文件创建,对于要创建临时文件的程序以及网络数据的传输、数据压缩后的传输等都可以提高效率,避免访问磁盘。

    输入流(以io流的方式读取字节数组):ByteArrayInputStream read(byte[] b,int off,int len)+close

    输出流(以io流的方式写入字节数组):ByteArrayOutputStream write(byte[] b,int off,int len)+toByteArray()(新增方法,把文件写入到字节数组里,不能使用多态)

    操作:文件到字节数组(内存)
    1. 目的地 byte[] dest = null;
    2. 选择流 将文件包装成输入流
      <br>&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;开辟字节数组输出流bos
      
    3. 读取 边读到自定义字节数组作为缓冲 new byte[1024]
      <br>&ensp;&ensp;&ensp;&ensp;&ensp;边写入字节数组输出流
      
    4. 强制刷出
    5. 获取数据 dest = bos.toByteArray()
    6. 释放资源
  • 读字节数组

    public void readByteArray() throws IOException {
        /**
         * 准备字节数组:把这个想象成是我们要从服务器上读取的内容
         * 那为什么服务器不直接把这句话传给我们我们直接打印不就好了吗?
         * 因为直接传字符串涉及到编码问题,所以中间的传输部分还是用字节数组来的准确
         */
        String meg="操作与文件输入流一致";
        byte[] src=meg.getBytes();
        /**
         * 选择流
         * ByteArrayInputStream: 把字节数组包装成输入流
         * BufferedInputStream:再加个缓冲区调优
         * 这里不会抛异常(FileNotFoundException之类),不是跟外部资源建立联系就不会抛异常
         */
        InputStream is=new BufferedInputStream(new ByteArrayInputStream(src));
        //缓冲数组
        byte[] flush=new byte[1024];
        int len=0;
        while((len=is.read(flush))!=-1){
            System.out.println(new String(flush,0,len));
        }
        //释不释放资源都无所谓了
    }
    
  • 写字节数组

    public byte[] writeToByteArray(){
        //准备字节数组
        String msg="操作与文件输出流不一致";
        byte[] info=msg.getBytes();
        //目的地
        byte[] dest; //不知道多大,先搁着
        ByteArrayOutputStream bos=new ByteArrayOutputStream(); //不能直接把字节数组dest丢进来;不要使用多态,因为等下要使用新增方法toByteArray()
        //写入
        bos.write(info,0,info.length); //只是写到了管道里,没有写到目标字节数组中
        dest=bos.toByteArray();
        return dest;
    }
    
  • 拷贝文件

    /**
     * 利用字节数组为桥梁实现文件拷贝(任意文件形式均可)
     * 1、文件(文件)->字节数组(其实也是文件)
     *   文件输入流
     *   字节数组输出流
     * 2、字节数组->文件
     *   字节数组输入流
     *   文件输出流
     * @author 14103
     */
    public class ByteArrayDemo02 {
        public static void main(String[] args) throws IOException {
            byte[] dest = getBytesFromFile("e:/xp/test/CalendarTest.java");
            toFileFromByteArray(dest,"e:/xp/test/test3.java");
        }
    
        /**
         * 1、文件->字节数组
         *   文件输入流
         *   字节数组输出流
         * @param srcPath
         * @return
         * @throws IOException
         */
        public static byte[] getBytesFromFile(String srcPath) throws IOException {
            //数据源
            File src = new File(srcPath);
            //目的地
            byte[] dest = null;
            //选择流
            InputStream is = new BufferedInputStream(new FileInputStream(src));/
            ByteArrayOutputStream bos = new ByteArrayOutputStream();//ByteArrayOutputStream是没有包装对象的,因为根本就不能通过它直接写入字节数组(内存)
            //缓冲数组
            byte[] flush = new byte[1024];
            int len = 0;
            while(-1!=(len=is.read(flush))) {
                //写入通道
                bos.write(flush,0,len);
            }
            bos.flush();
            //写入字节数组
            dest = bos.toByteArray();
            //可以不关,因为操作的是内存,会自动关闭,执行close实际是无效的
            bos.close();
            return dest;
        }
    
        /**
         * 2、字节数组(内存)->文件
         *   字节数组输入流
         *   文件输出流
         * @param src
         * @throws IOException
         */
        public static void toFileFromByteArray(byte[] src,String destPath) throws IOException {
            //目的地
            File dest = new File(destPath);
            //选择流
            InputStream is = new BufferedInputStream(new ByteArrayInputStream(src)); //把字节数组包装成流
            OutputStream os = new BufferedOutputStream(new FileOutputStream(dest));
    
            byte[] flush = new byte[1024];
            int len = 0;
    
            //这里不能直接写os.write(src,0,src.length),因为通常都是另一台计算机上的内存传进来,比较大
            while(-1!=(len=is.read(flush))) {
                os.write(flush, 0, len);
            }
            //强制刷出
            os.flush();
            //释放资源
            os.close();
    
        }
    }
    
  • 有的时候我们需要对同一个InputStream对象使用多次,比如,客户端从服务器获取数据,利用HttpURLConnection的getInputStream()方法获得Stream对象,我们希望把数据显示到前台(第一次读取),同时写进文件缓存到本地。
  • 然而第一次读取后,已经走到Stream的结尾或者Stream已经关闭了,此时我们可以把InputStream转化成ByteArrayOutputStream,下次再从ByteArrayOutputStream转化回来就好

    public void byteArrayStreamApp() throws IOException {
        //目的地
        byte[] dest=null;
        //源
        InputStream is=httpconn.getInputStream();
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        byte[] buffer=new byte[1024];
        int len;
        while((len=is.read(buffer))!=-1){
            bos.write(buffer,0,len);
        }
        bos.flush();
        dest=bos.toByteArray();
    
        //第一次访问
        InputStream is1=new ByteArrayInputStream(dest);
        //do something to print the stream...
    
        //第二次访问
        InputStream is2=new ByteArrayInputStream(dest);
        //do something to save in local...
    }
    

    二、处理流

  1. 数据类型处理流 处理基本类型+String(能保留数据及其类型)

    输入流:DataInputStream readXxx()

    输出流:DataOutput
    sStream writeXxx()
  2. 引用类型(对象)处理流(保留数据+类型)

    序列化 输入流:ObjectInputStream readObject()

    反序列化 输出流:ObjectOutputStream writeObject()

    注意:先序列化再反序列化;反序列化必须与序列化顺序一致

       不是所有的对象都可以序列化,java.io.Serializable

       不是所有的属性都需要序列化,transient
  3. 打印流PrintStream
  • 释放资源
    • 手动关 流.close()
    • 写工具类关 close(is,os)
    • 新特性自动关 try/with/resouce
  1. 合并流SequenceInputStream
    sequence 队列

    将输入源合并成一个或统一写出

    操作:

    1. 创建容器

      Vector vi = new Vector();
    2. 装入

        for(int i=0;i<this.blockName.size();i++){
            bis=new BufferedInputStream(new FileInputStream(new File(blockName.get(i))));
            vi.add(bis);
      }
      

      3.选择流

      for(int i=0;i<blockName.size();i++) {

      sis = new SequenceInputStream(vi.elements());//返回一个元素;有序的、一个一个被返回
      ...
      while(-1!=(len=sis.read(flush)))
      ...
      sis.close();
      

      }

      IOException

  • 文件不存在、文件拒绝访问、读着读着突然被删了