• java IO(二):字节流


    数据流分为输入、输出流,无论是输入流还是输出流,都可看作是在源和目标之间架设一根"管道",这些管道都是单向流动的,要么流入到内存(输入流),要么从内存流出(输出流)。

    应用于java上,输入流和输出流分别为InputStream和OutputStream。输入流用于读取(read)数据,将数据加载到内存(应用程序),输出流用于写入(write)数据,将数据从内存写入到磁盘中。

    数据流可分为字节流和字符流,字节流按字节输入、输出数据,字符流按字符个数输入、输出数据。本文介绍的是字节流。

    1.OutputStream类和FileOutputStream类

    OutputStream类是字节输出流的超类。它只提供了几个方法,注意这几个方法都会抛出IOExcepiton异常,因此需要捕获或向上抛出:

    • close():关闭输出流,即撤掉管道。
    • flush():将缓存字节数据强制刷到磁盘上。
    • write(byte[] b):将byte数组中的数据写入到输出流。
    • write(byte[] b, int off, int len):将byte数组中从off开始的len个字节写入到此输出流。
    • write(int b):将指定的单个字节写入此输出流。

    FileOutputStream类是OutputStream的子类,专门用于操作文件相关的流,例如向文件中写入数据。该类有以下几个构造方法:

     FileOutputStream(File file):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。  FileOutputStream(File file, boolean append):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。  FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。  FileOutputStream(String name, boolean append):创建一个向具有指定 name 的文件中写入数据的输出文件流。

    例如:创建一个新文件,向其中写入"abcde"。

    import java.io.*;
    
    public class OutStr1 {
        public static void main(String[] args) throws IOException {
    
            File tempdir = new File("D:/temp");  //create a tempdir
            if(!tempdir.exists()) {
                tempdir.mkdir();
            }
    
            File testfile = new File(tempdir,"test.txt");
            FileOutputStream fos = new FileOutputStream(testfile)//在内存和testfile之间架一根名为fos的管道 
    
            fos.write("abcde".getBytes());  //将字节数组中所有字节(byte[] b,b.length)都写入到管道中
            fos.close();
        }
    }
    

    注意:
    (1).此处的FileOutputStream()构造方法会创建一个新文件并覆盖旧文件。如果要追加文件,采用FileOutputStream(file,true)构造方法构造字节输出流。
    (2).上面的for.write("abcde".getBytes())用的是write(byte[] b)方法,它会将字节数组中b.length个字节即所有字节都写入到输出流中。可以采用write(byte[] b,int off,int len)方法每次写给定位置、长度的字节数据。
    (3).无论是构造方法FileOutputStream(),还是write()、close()方法,都会抛出异常,有些是IOException,有些是FileNotFoundException。因此,必须捕获这些异常或向上抛出。

    追加写入和换行写入

    向文件中追加数据时,采用write(File file,boolean append)方法。

    File testfile = new File(tempdir,"test.txt");
    FileOutputStream fos = new FileOutputStream(testfile,true);
    
    byte[] data = "abcde".getBytes();
    fos.write(data,0,2);
    fos.close();
    

    换行追加时,需要加上换行符,Windows上的换行符为" ",unix上的换行符为" "。如果要保证良好的移植性,可获取系统属性中的换行符并定义为常量。

    import java.io.*;
    
    public class OutStr1 {
        private static final String LINE_SEPARATOR = System.getProperty("line.separator"); //newline
    
        public static void main(String[] args) throws IOException {
            File tempdir = new File("D:/temp");  //create a tempdir
            if(!tempdir.exists()) {
                tempdir.mkdir();
            }
    
            File testfile = new File(tempdir,"test.txt");
            FileOutputStream fos = new FileOutputStream(testfile,true);
    
            String str = LINE_SEPARATOR+"abcde";  //
            fos.write(str.getByte());
            fos.close();
        }
    }
    

    2.捕获IO异常的方法

    输出流中很多方法都定义了抛出异常,这些异常必须向上抛出或捕获。向上抛出很简单,捕获起来可能比想象中的要复杂一些。

    以向某文件写入数据为例,以下是最终的捕获代码,稍后将逐层分析为何要如此捕获。

    File file = new File("d:/tempdir/a.txt");
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(file);
        fos.write("abcde".getBytes());
    } catch (IOException e) {
        ...
    } finally {
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                throw new RuntimeException("xxxx");
            }
        } 
    }
    

    向某文件写入数据,大致会包含以下几个过程。

    File file = new File("d:/tempdir/a.txt");
    FileOutputStream fos = new FileOutputStream(file);   //---->(2)
    fos.write("abcde".getBytes());        //------------------->(3)
    fos.close();      //--------------------------------------->(4)
    

    其中(2)-(4)这三条代码都需要去捕获,如果将它们放在一个try结构中,显然逻辑不合理,例如(2)正常实例化了一个输出流,但(3)异常,这时无法用(4)来关闭流。无论异常发生在何处,close()动作都是应该要执行,因此需要将close()放入finally结构中。

    File file = new File("D:/tempdir/a.txt");
    try {
        FileOutputStream fos = new FileOutputStream(file);   //---->(2)
        fos.write("abcde".getBytes());        //------------------->(3)
    } catch (IOException e) {
        ...
    } finally {
        fos.close();      //--------------------------------------->(4)
    }
    

    但这样一来,fos.close()中的fos是不可识别的,因此,考虑将fos定义在try结构的外面。因此:

    File file = new File("D:/tempdir/a.txt");
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(file);   //---->(2)
        fos.write("abcde".getBytes());        //-->(3)
    } catch (IOException e) {
        ...
    } finally {
        fos.close();      //---------------------->(4)
    }
    

    如果d: empdira.txt文件不存在,那么(2)中的fos将指向空的流对象,即空指针异常。再者,finally中的close()也是需要捕获异常的,因此加上判断并对其try...catch,于是:

    File file = new File("D:/tempdir/a.txt");
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(file);   //---->(2)
        fos.write("abcde".getBytes());        //-->(3)
    } catch (IOException e) {
        ...
    } finally {
        if(fos != null) {
            try {
                fos.close();//-------------------->(4)
            } catch (IOException e) {
                throw new RuntimeException("xxx");
            }
        }
    }
    

    3.InputStream类和FileInputStream类

    以下是InputStream类提供的方法:

    • close():关闭此输入流并释放与该流关联的所有系统资源。
    • mark(int readlimit):在此输入流中标记当前的位置。
    • read():从输入流中读取数据的下一个字节,返回的是0-255之间的ASCII码,读到文件结尾时返回-1
    • read(byte[] b):从输入流中读取一定数量的字节存储到缓冲区数组b中,返回读取的字节数,读到文件结尾时返回-1
    • read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组,返回读取的字节数,读到文件结尾时返回-1
    • reset():将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
    • skip(long n):跳过和丢弃此输入流中数据的 n 个字节。

    示例:d: emp est.txt文件中的内容为"abcde",读取该文件。

    import java.io.*;
    
    public class InStr1 {
        public static void main(String[] args) throws IOException {
            File file = new File("d:/temp/test.txt");
            FileInputStream fis = new FileInputStream(file);
    
            System.out.println(fis.read());    
            System.out.println((char)fis.read());
            System.out.println((char)fis.read());
            System.out.println((char)fis.read());
            System.out.println((char)fis.read());
            System.out.println(fis.read());
            System.out.println(fis.read());
            fis.close();
        }
    }
    

    执行结果为:

    97
    b
    c
    d
    e
    -1
    -1
    

    从结果可知:

    (1).read()每次读取一个字节并返回0-255之间的数值。可以强制转换为char字符。
    (2).每次读取后指针下移一位,直到文件的结尾。
    (3).读取文件结束符返回-1,且指针一直停留在结束符位置处,因此后面继续读取还是返回-1。

    因此,可以使用如下代码来读取整个文件。

    int ch = 0 ;
    while ((ch=fis.read())!= -1) {
        System.out.println((char)ch);
    }
    

    当文件很大时,这样一个字节一个字节读取的速度必然极慢。因此,需要一次读取多个字节。下面是使用read(byte[] b)一次读取多个字节的代码。

    int len = 0;
    byte[] buf = new byte[2];
    while ((len=fis.read(buf))!=-1) {
        System.out.println(new String(buf,0,len));
    }
    

    执行结果为:

    ab
    cd
    e
    

    几个注意点:
    (1).read(byte[] b)是从文件中读取b.length个字节存储到字节数组中,第一个字节存储到b[0],第二个字节存储到b[2],以此类推,注意存储在字节数组中的每一个字节都是0-255的ASCII码。例如文件中有3个字节abc,字节数组b的长度为5,则b[0]=a,b[1]=b,b[2]=c,b[3]和b[4]不受影响(即仍为初始化值0),如果字节数组b长度为2,则第一批读取两个字节ab存储到b[0]和b[1],第二批再读取一个字节c存储到b[0]中,此时字节数组b中存储的内容为cb。
    (2).read(b)返回的是读取的字节数,在到达文件末尾时返回-1。
    (3).只有read()到字节数组才需要使用while的循环判断,因为自由读才需要考虑是否到达文件结尾。而write操作无需while循环和字节数组。

    因此,上面的代码读取文件的过程大致为:读取a和b存放到buf[0]和buf[1]中,此时len=2,转换为字符串后打印得到ab,再读取c覆盖buf[0],读取d覆盖buf[1],转换为字符串后打印得到cd,最后读取e覆盖到buf[0],到了文件的末尾,此时buf[1]=d。也就是说到此为止,b数组中存放的仍然有d,但因为String(buf,0,len)是根据len来转换为字符串的,因此d不会被转换。但如果将new String(buf,0,len)改为new String(buf),将得到abcded。

    由此可知,字节数组的长度决定了每次读取的字节数量。通常来说,可以将byte[]的长度设置为1024的整数倍以达到更高的效率,例如设置为4096,8192等都可,但也不应设置过大。

    4.复制文件(字节流)

    原理:

    1.在源文件上架起一根输出流(read)管道。
    2.在目标文件上架起一根输入流(write)管道。
    3.但注意,这两根管道之间没有任何联系。可以通过中间媒介实现中转。每read()一定数量的字节到内存中,可以将这些字节write到磁盘中。
    4.应该使用字节数组作为buffer做缓存,而不应该使用单字节的读取、写入,这样会非常非常慢。

    import java.io.*;
    
    public class CopyFile {
        public static void main(String[] args) throws IOException {
            // src file and src Stream
            File src = new File("d:/temp/1.avi");
            FileInputStream fis = new FileInputStream(src);
    
            // dest file and dest Stream
            File dest = new File("d:/temp/1_copy.avi");
            FileOutputStream fos = new FileOutputStream(dest);
    
            int len = 0;
            byte[] buf = new byte[1024];
            while((len=fis.read(buf)) != -1) {        // read
                fos.write(buf,0,len);              // write
            }
            fis.close();
            fos.close();
        }
    }
    

    注:若您觉得这篇文章还不错请点击右下角推荐,您的支持能激发作者更大的写作热情,非常感谢!

  • 相关阅读:
    大数据技术之_16_Scala学习_04_函数式编程-基础+面向对象编程-基础
    大数据技术之_16_Scala学习_03_运算符+程序流程控制
    大数据技术之_16_Scala学习_01_Scala 语言概述
    通过创建一个简单的骰子游戏来探究 Python
    在Linux系统中创建SSH服务器别名
    DNS原理及劫持问题
    详细介绍:Kubernetes1.4版本的新功能
    Linux系统中五款好用的日志分析工具
    wc命令——Linux系统高效数据统计工具
    Linux系统内核正式进入5.0版本时代
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/8146358.html
Copyright © 2020-2023  润新知