I/O类库中使用“流”这个抽象概念。Java对设备中数据的操作是通过流的方式。表示任何有能力产出数据的数据源对象,或者是有能力接受数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。IO流用来处理设备之间的数据传输。设备是指硬盘、内存、键盘录入、网络等。

IO的分类可以为:
流按操作数据类型的不同分为两种:字节流与字符流。
流按流向分为:输入流,输出流(以程序为参照物,输入到程序,或是从程序输出)

学习大数据:Java基础篇之流_第1张图片

一、字节流
1、Inpustream
InputStream有read方法,一次读取一个字节,OutputStream的write方法一次写一个int。这两个类都是抽象类。意味着不能创建对象,那么需要找到具体的子类来使用。操作流的步骤都是:
第一步:1:打开流(即创建流)
第二步:2:通过流读取内容
第三步:3:用完后,关闭流资源

例1:使用 read()方法,一次读取一个字节,读到文件末尾返回-1.

 private static void showContent(String path) throws IOException {
        // 打开流
        FileInputStream fis = new FileInputStream(path);
        int len;
        while ((len = fis.read()) != -1) {
            System.out.print((char) len);
        }
        // 使用完关闭流
        fis.close();

例2:使用read()方法的时候,可以将读到的数据装入到字节数组中,一次性的操作数组,可以提高效率。

private static void showContent2(String path) throws IOException {
        // 打开流
        FileInputStream fis = new FileInputStream(path);
        // 通过流读取内容
        byte[] byt = new byte[1024];
        int len = fis.read(byt);
        for (int i = 0; i 

例3:使用read(byte[] b,int off,int len)

把数组的一部分当做流的容器来使用
    private static void showContent3(String path) throws IOException {
            // 打开流
            FileInputStream fis = new FileInputStream(path);

            // 通过流读取内容
            byte[] byt = new byte[1024];
            // 从什么地方开始存读到的数据
            int start = 5
            // 希望最多读多少个(如果是流的末尾,流中没有足够数据)
            int maxLen = 6;
            // 实际存放了多少个
            int len = fis.read(byt, start, maxLen);

            for (int i = start; i < start + maxLen; i++) {
                    System.out.print((char) byt[i]);
            }

            // 使用完关闭流
            fis.close();
    }

例4(推荐使用):使用缓冲(提高效率),并循环读取(读完所有内容).

使用字节数组当缓冲
private static void showContent7(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    byte[] byt = new byte[1024];
    int len = 0;
    while ((len = fis.read(byt)) != -1) {
        System.out.println(new String(byt, 0, len));
    }
    fis.close();
}

2、OutputStream
OutputStram 的write方法,一次只能写一个字节。成功的向文件中写入了内容。但是并不高效,如何提高效率呢?
可以使用缓冲,在OutputStram类中有write(byte[] b)方法,将 b.length个字节从指定的 byte 数组写入此输出流中。

private static void writeTxtFile(String path) throws IOException {
        // 1:打开文件输出流,流的目的地是指定的文件
        FileOutputStream fos = new FileOutputStream(path,true);

        // 2:通过流向文件写数据
        byte[] byt = "java".getBytes();
        fos.write(byt);

        // 3:用完流后关闭流
        fos.close();
    }

3、输入输出流综合使用——文件拷贝实现

public static void copyFile(String srcPath, String destPath) throws IOException {
        // 打开输入流,输出流
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);

        // 读取和写入信息
        int len = 0;

        // 使用字节数组,当做缓冲区
        byte[] byt = new byte[1024];
        while ((len = fis.read(byt)) != -1) {
            fos.write(byt, 0, len);
        }

        // 关闭流
        fis.close();
        fos.close();
    }

可以根据拷贝的需求调整数组的大小,一般是1024的整数倍。使用缓冲后效率大大提高。目前我们是抛出处理,一旦出现了异常,close就没有执行,也就没有释放资源。那么为了保证close的执行该如何处理呢。那么就需要使用try{} catch(){}finally{}语句。try中放入可能出现异常的语句,catch是捕获异常对象,fianlly是一定要执行的代码。

public static void copyFile(String srcPath, String destPath) {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(srcPath);
            fos = new FileOutputStream(destPath);

            byte[] byt = new byte[1024 * 1024];
            int len = 0;
            while ((len = fis.read(byt)) != -1) {

                fos.write(byt, 0, len);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

大量的异常捕获代码使得以上代码变得十分臃肿难看,好在java7提供了TWR(try-with-resource)语法糖,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。`

    public static void copyByTWR(String srcPath, String destPath){
            try( FileInputStream fis = new FileInputStream(srcPath);
                     FileOutputStream fos = new FileOutputStream(destPath)){
                    byte[] byt = new byte[1024 * 1024];
                    int len = 0;
                    while ((len = fis.read(byt)) != -1) {
                            fos.write(byt, 0, len);
                    }
            }catch (IOException e){
                    throw new RuntimeException(e);
            }
    }

当try语句块运行结束时,FileInputStream / FileOutputStream会被自动关闭。这是因为FileInputStream实现了java中的java.lang.AutoCloseable接口。所有实现了这个接口的类都可以在try-with-resources结构中使用上面的例子在try关键字后的括号里创建了两个资源——FileInputStream 和FileOutputStream。当程序运行离开try语句块时,这两个资源都会被自动关闭,这些资源将按照他们被创建顺序的逆序来关闭。首先FileOutputStream会被关闭,然后FileInputStream会被关闭。

4、缓冲流
Java其实提供了专门的字节流缓冲来提高效率。BufferedInputStream 和 BufferedOutputStream。BufferedOutputStream和BufferedOutputStream类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看API文档,发现可以指定缓冲区的大小。其实内部也是封装了字节数组。没有指定缓冲区大小,默认的字节是8192。显然缓冲区输入流和缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据读入缓冲区,当缓冲区满时,或者调用flush方法,缓冲输出流会将数据写出。
注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能的提升。但是文件稍微大一些的话,就可以看到实质的性能提升了。

public class Test {
    public static void main(String[] args) throws IOException {
        String srcPath = "c:\\a.mp3";
        String destPath = "d:\\copy.mp3";
        copyFile(srcPath, destPath);
    }

    public static void copyFile(String srcPath, String destPath)
            throws IOException {
        // 打开输入流,输出流
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(destPath);

        // 使用缓冲流
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        // 读取和写入信息
        int len = 0;

        while ((len = bis.read()) != -1) {
            bos.write(len);
        }

        // 关闭流
        bis.close();
        bos.close();    
    }
}

二、字符流
计算机并不区分二进制文件与文本文件。所有的文件都是以二进制形式来存储的,因此,从本质上说,所有的文件都是二进制文件。所以字符流是建立在字节流之上的,它能够提供字符层次的编码和解码。可以说字符流就是:字节流 + 编码表,为了更便于操作文字数据。字符流的抽象基类:Reader , Writer。由这些类派生出来的子类名称都是以其父类名作为子类名的后缀,如FileReader、FileWriter。

1、Reader
int read()
读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1.
int read(char[])
将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1.
close()
读取字符其实用的是window系统的功能,就希望使用完毕后,进行资源的释放
由于Reader也是抽象类,所以想要使用字符输入流需要使用Reader的实现类——FileReader。
1,用于读取文本文件的流对象。
2,用于关联文本文件。
构造函数:在读取流对象初始化的时候,必须要指定一个被读取的文件。如果该文件不存在会发生FileNotFoundException。

使用字符流读取文件内容

 public static void readFileByReader(String path) throws Exception {
     Reader reader = new FileReader(path);
     int len = 0;
     while ((len = reader.read()) != -1) {
         System.out.print((char) len);
     }
     reader.close();
 }

2、Writer
write(ch): //将一个字符写入到流中。
write(char[]):// 将一个字符数组写入到流中。
write(String): //将一个字符串写入到流中。
flush(): //刷新流,将流中的数据刷新到目的地中,流还存在。
close(): //关闭资源:在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭。
基本方法和OutputStream 类似,有write方法,功能更多一些。可以接收字符串。Writer是抽象类无法创建对象,Writer的实现子类为FileWriter。默认的FileWriter方法新值会覆盖旧值,想要实现追加功能需要使用如下构造函数创建输出流 append值为true了。
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)

3、文件拷贝——完善异常处理风格
一次读一个字符就写一个字符,效率不高。把读到的字符放到字符数组中,再一次性的写出,缓冲数组可以提高效率。

 使用字符流拷贝文件,有完善的异常处理
 public static void copyFile2(String path1, String path2) {
     Reader reader = null;
     Writer writer = null;
     try {
         // 打开流
         reader = new FileReader(path1);
         writer = new FileWriter(path2);
         // 进行拷贝
         int ch = -1;
    char [] arr=new char[1024];
         while ((ch = reader.read(arr)) != -1) {
             writer.write(ch);
         }
     } catch (Exception e) {
         throw new RuntimeException(e);
     } finally {
         // 关闭流,注意一定要能执行到close()方法,所以都要放到finally代码块中
         try {
             if (reader != null) {
                 reader.close();
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         } finally {
             try {
                 if (writer != null) {
                     writer.close();
                 }
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         }
     }
 }

三、装饰器模式:
使用分层对象来动态透明的向单个对象中添加责任(功能)。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些对象是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。装饰器必须具有和他所装饰的对象相同的接口。Java I/O类库需要多种不同的功能组合,所以使用了装饰器模式。Filterxxx类是JavaIO提供的装饰器基类,即我们要想实现一个新的装饰器,就要继承这些类。
继承实现的增强类:
优点:代码结构清晰,而且实现简单
缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。
修饰模式实现的增强类:
优点:内部可以通过多态技术对多个需要增强的类进行增强
缺点:需要内部通过多态技术维护需要增强的类的实例。进而使得代码稍微复杂。