欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java-Java I/O流解读之基于字节的I / O和字节流

程序员文章站 2022-04-03 16:25:11
...

概述

编写简单的 I / O操作很简单,仅仅涉及到很少的类和方法。 但是编写高效,便携式I / O非常困难,特别是如果我们必须处理不同的字符集。 这就解释了为什么有这么多的I / O包(在JDK 1.7中有9个)!

JDK有两套 I / O 包:

  • 自JDK 1.0引入的基于流的I / O的标准I / O(在包java.io中)
  • 在JDK 1.4中引入的新的I / O(在java.nio包中)用于更有效的基于缓冲区的I / O

JDK 1.5通过新类java.util.Scanner和Formatter引用格式化的文本I / O,并使用格式说明符格式化输出的C-like printf()和format()方法.

JDK 1.7通过新的包java.nio.file及其辅助包中的所谓的NIO.2(非阻塞I / O)来增强对文件I / O的支持。 它还引入了一种新的try-with-resources语法来简化close()方法的编码。


File and Directory

Class java.io.File (Pre-JDK 7)

java.io.File 可以表述一个文件 或者一个目录 (JDK1.7中 引入了一个更加强大的接类 java.nio,file.Path,克服了java.io.File中的很多限制。)

路径字符串用于定位文件或目录。 不幸的是,路径字符串是系统相关的

例如:
Windows中的“c:\myproject\java\Hello.java”
Unix / Mac中的“/myproject/java/Hello.java”

  • 目录分隔符: Windows使用反斜杠’\’作为目录分隔符; 而Unixes / Mac使用前斜杠’/’。
  • 路径分隔符:Windows使用分号’;’ 作为路径分隔符来分隔路径列表; 而Unixes / Mac使用冒号’:’
  • 行分隔符:Windows中使用“ \r\n”作为行分隔符的文本文件; 而Unix系统使用“ \n”和Mac使用“ \r”
  • 根目录:在“ c:\”或“ \”被称为根。Windows支持多根,每一个映射到一个驱动器(例如,“ c:\”,“ d:\“)。而Unix / Mac有一个根(” \“)

文件路径可以是绝对的(从根开始)或相对(相对于引用目录)。 特殊符号 ...分别表示当前目录和父目录。

java.io.File类维护这些依赖于系统的属性,我们可以编写可移植的程序

  • 目录分隔符: 静态字符串字段 File.separator 和 File.separatorChar [他们没有遵循从JDK 1.2采用的常规Java命名约定。]如上所述,Windows使用反斜杠’\’; 而Unixes / Mac使用正斜杠’/’。

  • 路径分隔符:静态字符串字段 File.pathSeparator和 File.pathSeparatorChar。 如前所述,Windows使用分号’;’ 分隔路径列表; 而Unixes / Mac使用冒号’:’。

public class SeparatorTest {
    public static void main(String[] args) {
        System.out.println(File.pathSeparator);
        System.out.println(File.pathSeparatorChar);

        System.out.println(File.separator);
        System.out.println(File.separatorChar);
    }
}

windows 环境下输出:

;
;
\
\

我们可以使用路径字符串或URI构造一个File实例,如下所示。
请注意,物理文件/目录可能存在或可能不存在。
文件URL采用file:// …的形式,例如file:/// d:/docs/programming/java/test.html。

public File(String pathString)
public File(String parent, String child)
public File(File parent, String child)
// Constructs a File instance based on the given path string.

public File(URI uri)
//Constructs a File instance by converting from the given file-URI "file://...."

举例:

// A file relative to the current working directory
File file1 = new File("xgj.txt");

// A file with absolute path D:\workspace\ws-java-base\JavaMaster
File file2 = new File("D:\\xgj.txt");

// A file with parent and child
File parent = new File("D:\\temp");
File child = new File(parent, "xgj.txt");

// A directory
File dir = new File("D:\\temp");

如果你打算将应用封装成jar包,应该使用URL类来引用资源,因为它可以引用磁盘文件以及JAR文件,例如,

java.net.URL url = this.getClass().getResource("icon.png");

Verifying Properties of a File/Directory

public boolean exists()       // Tests if this file/directory exists.
public long length()          // Returns the length of this file.
public boolean isDirectory()  // Tests if this instance is a directory.
public boolean isFile()       // Tests if this instance is a file.
public boolean canRead()      // Tests if this file is readable.
public boolean canWrite()     // Tests if this file is writable.
public boolean delete()       // Deletes this file/directory.
public void deleteOnExit()    // Deletes this file/directory when the program terminates.
public boolean renameTo(File dest) // Renames this file.
public boolean mkdir()        // Makes (Creates) this directory.

List Directory

如果是目录的话,我们可以通过下面的方法列出内容

public String[] list()     // List the contents of this directory in a String-array
public File[] listFiles()  // List the contents of this directory in a File-array

示例:

package com.xgj.master.java.io.fileDemo;

import java.io.File;

/**
 * 
 * 
 * @ClassName: ListDirectoryRecusive
 * 
 * @Description: Recursively list the contents of a directory
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月6日 下午6:27:01
 */
public class ListDirectoryRecusive {

    public static void main(String[] args) {
        File dir = new File("D:\\OneDrive");
        listDirectoryRecusive(dir);
    }

    /**
     * 
     * 
     * @Title: listDirectoryRecusive
     * 
     * @Description: 遍历目录下的内容(包括子孙目录)
     * 
     * @param dir
     * 
     * @return: void
     */
    public static void listDirectoryRecusive(File dir) {
        if (dir.isDirectory()) {
            File[] items = dir.listFiles();
            for (File item : items) {
                System.out.println(item.getAbsolutePath());
                // Recursive call 如果目录下还有目录
                if (item.isDirectory()) {
                    listDirectoryRecusive(item);
                }
            }
        }
    }
}

输出结果:

D:\OneDrive\desktop.ini
D:\OneDrive\公开
D:\OneDrive\公开\2.txt
D:\OneDrive\图片
D:\OneDrive\图片\1.txt
D:\OneDrive\文档
D:\OneDrive\文\MasterJava.pptx
D:\OneDrive\文\新建
D:\OneDrive\文\新建\23让34.mpp

List Directory with Filter

当然了我们也可以将过滤器应用于list()和listFiles(),以仅列出满足特定条件的文件。

public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
public File[] listFiles(FileFilter filter)

java.io.FilenameFilter接口定义了一个抽象方法

public boolean accept(File dirName, String fileName)

list()和listFiles()方法对所生成的每个文件/子目录执行回调accept()。 我们可以在accept()中编写过滤条件。 不符合的文件/子目录将被排除。

示例:

Java-Java I/O流解读之基于字节的I / O和字节流

package com.xgj.master.java.io.fileDemo;

import java.io.File;
import java.io.FilenameFilter;

import org.junit.Test;

/**
 * 
 * 
 * @ClassName: ListDirectoryWithFilter
 * 
 * @Description: List files that end with "pptx"
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月6日 下午7:50:13
 */
public class ListDirectoryWithFilter {
    /**
     * 
     * 
     * @Title: listDirectoryWithFilter
     * 
     * @Description: 遍历某个目录下所有以pptx结尾的文件或者目录
     * 
     * @param dir
     * 
     * @return: void
     */
    public void listDirectoryWithFilter(File dir) {
        if (dir.isDirectory()) {
            // List only files that meet the filtering criteria
            // programmed in accept() method of FilenameFilter.
            String[] files = dir.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String file) {
                    return file.endsWith("pptx");
                }
            });// an anonymous inner class as FilenameFilter

            for (String file : files) {
                System.out.println(file);
            }
        }
    }

    @Test
    public void test() {
        listDirectoryWithFilter(new File("D:\\OneDrive\\文档"));
    }

}

输出:

MasterJava.pptx
pptx

Class java.nio.file.Path (JDK 7)

另外开篇阐述


Stream I/O in Standard I/O (java.io Package) 标准I / O中的流I / O(java.io包)

程序从数据源(例如键盘,文件,网络,存储器缓冲器或另一程序)读取输入,并向数据宿(例如,显示控制台,文件,网络,存储器缓冲器或另一程序)写入输出。

在Java标准I / O中,输入和输出由所谓的流(Stream)处理。

流是连续的单向数据流(就像水或油流过管道)。重要的是要提到Java不区分流I / O中的各种类型的数据源或汇(例如文件或网络)。它们都被视为一个顺序的数据流。输入和输出流可以从任何数据源/汇点(如文件,网络,键盘/控制台或其他程序)建立。

Java程序通过打开输入流从源接收数据,并通过打开输出流将数据发送到宿。

所有Java I / O流都是单向的(除了RandomAccessFile,稍后将讨论)

如果你的程序需要执行输入和输出,则必须打开两个流 - 输入流和输出流。

操作I / O流,分为3步:

  1. 通过构建适当的I / O流实例来打开与物理设备(例如,文件,网络,控制台/键盘)相关联的输入/输出流。
  2. 从已打开的输入流读取,直到遇到“end-of-stream”,或写入打开的输出流(并可选地刷新缓冲输出)。
  3. 关闭输入/输出流。

Java的I / O操作比C / C ++更复杂以支持国际化(i18n)。 Java内部将字符(字符类型)存储在16位UCS-2字符集中。 但外部数据源/接收器可以将字符存储在其他字符集(例如US-ASCII,ISO-8859-x,UTF-8,UTF-16等等)中,固定长度为8位或16位, 位或以1到4字节的可变长度。 [读取“字符集和编码方案”]。

因此,Java需要区分用于处理原始字节或二进制数据的基于字节的I / O以及用于处理由字符组成的文本的基于字符的I / O。

Java-Java I/O流解读之基于字节的I / O和字节流


Byte-Based I/O & Byte Streams 基于字节的I / O和字节流

Java-Java I/O流解读之基于字节的I / O和字节流

字节流用于从外部设备串行读取/写入原始字节。 所有字节流都是从抽象超类InputStream和OutputStream派生的,如类图所示。


Reading from an InputStream

抽象类 InputStream中声明了一个抽象方法read() 从输入源读取一个数据字节

public abstract int read() throws IOException;

read()方法:

  • 返回输入字节读取为int,范围为0到255,或

  • 如果检测到“流结束”条件,则返回-1

  • 如果遇到I / O错误,则抛出IOException。

read()方法返回一个int而不是一个字节,因为它使用-1表示流末尾。
read()方法将会block,直到一个字节可用或者发生I / O错误或检测到“end-of-stream”。
术语“block”表示方法(和程序)将被暂停。 只有当方法返回时,程序才会恢复。

在InputStream中实现了两种read()方法,用于将字节块读入字节数组。
它返回读取的字节数. 如果“end-of-stream”遇到,则返回-1。

// Read "length" number of bytes, store in bytes array starting from offset of index.
public int read(byte[] bytes, int offset, int length) throws IOException

// Same as read(bytes, 0, bytes.length)
public int read(byte[] bytes) throws IOException

Writing to an OutputStream

与输入对应类似,抽象超类OutputStream声明一个抽象方法write()来将数据字节写入输出接收器。 write()入参为一个int。 int参数的最低有效字节被写出; 高于3字节被丢弃。 如果发生I / O错误(例如,输出流已关闭),则会抛出IOException。

public void abstract void write(int unsignedByte) throws IOException

和 InputStream中的read()相似,OutputStream中实现了两种write()方法从字节数组中写入一个字节块

// Write "length" number of bytes, from the bytes array starting from offset of index.
public void write(byte[] bytes, int offset, int length) throws IOException

// Same as write(bytes, 0, bytes.length)
public void write(byte[] bytes) throws IOException

Opening & Closing I/O Streams

我们通过构建流的实例来打开I / O流。 InputStream和OutputStream都提供了一个close()方法来关闭流,该流执行必要的清理操作以释放系统资源。

public void close() throws IOException  // close this Stream

通过在try-catch-finally的finally子句中运行close()来明确地关闭I / O流是一个很好的做法,以便在不再需要流时立即释放系统资源。 这可以防止严重的资源泄漏。

不幸的是,close()方法也抛出一个IOException,并且需要包含在一个嵌套的try-catch语句中,如下所示。 这使得代码变得很丑陋。

InputStream in = null;

        try {
            in = new FileInputStream(...);
            ....
            ....
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

JDK 1.7引入了一种新的try-with-resources语法,它会在try或catch之后自动关闭所有打开的资源,如下所示。 这样的代码看起来更加的优雅。

// Automatically closes all opened resource in try (...).
try (FileInputStream in = new FileInputStream(...)) {
    ...
    ....
} catch (Exception e) {
    e.printStackTrace();
}

Flushing the OutputStream

OutputStream提供了一个flush()方法来从输出缓冲区中刷新剩余的字节。

public void flush() throws IOException  // Flush the output

Implementations of abstract InputStream/OutputStream

InputStream和OutputStream是不能被实例化的抽象类。 我们需要选择一个适当的具体子类来建立与物理设备的连接。 例如,可以实例化FileInputStream或FileOutputStream以建立到物理磁盘文件的流。


Layered (or Chained) I/O Streams 分层(或链接)I / O流

I / O流通常与其他I / O流分层或链接,用于缓冲,过滤或数据格式转换(原始字节和原始类型之间)的目的。 例如,我们可以将BufferedInputStream分层到一个FileInputStream进行缓冲输入,并在前面堆叠一个DataInputStream进行格式化数据输入(使用诸如int,double等原语),如下图所示。

Java-Java I/O流解读之基于字节的I / O和字节流


File I/O Byte-Streams - FileInputStream & FileOutputStream

FileInputStream和FileOutputStream是抽象类InputStream和OutputStream的具体实现,用于从磁盘文件支持I / O。


Buffered I/O Byte-Streams - BufferedInputStream & BufferedOutputStream

InputStream / OutputStream中的read()/ write()方法旨在读/写每个调用的单字节数据。 这是非常低效的,因为每个调用由底层操作系统处理(可能会触发磁盘访问或其他昂贵的操作)。 在单个I / O操作中,从外部设备读取/写入内存缓冲区的缓冲区通常用于加速I / O。

FileInputStream / FileOutputStream没有缓冲。 它通常链接到BufferedInputStream或BufferedOutputStream,它提供缓冲。 要将流链接在一起,只需将一个流的实例传递到另一个流的构造函数中。

例如,以下代码将FileInputStream链接到BufferedInputStream,最后是一个DataInputStream

FileInputStream in = new FileInputStream(new File("D:\\xgj.txt"));

BufferedInputStream bis = new BufferedInputStream(in);

DataInputStream dis = new DataInputStream(bis);
// or 
DataInputStream dis2 = new DataInputStream(
                    new BufferedInputStream(new FileInputStream(new File("D:\\xgj.txt"))));

示例:

Example 1: Copying a file byte-by-byte without Buffering.

maven工程

Java-Java I/O流解读之基于字节的I / O和字节流

package com.xgj.master.java.io.fileDemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * 
 * 
 * @ClassName: FileCopyNoBuffer
 * 
 * @Description: FileCopyNoBuffer
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月6日 下午10:53:17
 */
public class FileCopyNoBufferPreJDK7 { // Pre-JDK 7

    @Test
    public void test() {
        // 利用Spring提供的Resource/ResourceLoader接口操作资源文件
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource resource = resourcePatternResolver
                .getResource("classpath:com/xgj/master/java/io/fileDemo/SQL.Cookbook.pdf");

        FileInputStream in = null;
        FileOutputStream out = null;
        long startTime, elapsedTime; // for speed benchmarking

        try {
            // Print file length
            File fileIn = resource.getFile();
            System.out.println("File size is " + fileIn.length() / 1024 / 1024 + " MB");

            in = new FileInputStream(fileIn);
            out = new FileOutputStream(new File("D:\\NoBufferPreJDK7.pdf"));

            startTime = System.nanoTime();

            int byteRead;
            // Read a raw byte, returns an int of 0 to 255.
            while ((byteRead = in.read()) != -1) {
                // Write the least-significant byte of int, drop the upper 3
                // bytes
                out.write(byteRead);
            }
            // cost time
            elapsedTime = System.nanoTime() - startTime;
            System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // JDK7之前的写法,在finally中关闭流
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

此示例通过从输入文件读取一个字节并将其写入输出文件来复制文件。 它直接使用FileInputStream和FileOutputStream进行缓冲。

请注意,大多数I / O方法“抛出”IOException,它必须被捕获或声明为抛出。

close()方法在finally子句中,确保try 或者catch后能够关闭流。 然而,close()方法也会抛出一个IOException,因此必须封装在一个嵌套的try-catch块中,这样使代码有点难看。

在这里使用了 JDK 1.5中引入的System.nanoTime()来更准确地测量经过的时间,而不是传统的不精确的System.currentTimeMillis()。

如前所述,JDK 1.7引入了一种新的try-with-resources语法,它会在try或catch之后自动关闭所有打开的资源。 例如,上述示例可以按照如下方式重新编写:

package com.xgj.master.java.io.fileDemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * 
 * 
 * @ClassName: FileCopyNoBufferJDK7
 * 
 * @Description: FileCopyNoBufferJDK7
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月6日 下午11:02:50
 */
public class FileCopyNoBufferJDK7 {

    @Test
    public void test() {
        // 利用Spring提供的ResourcePatternResolver操作资源文件 同时支持Ant风格的资源路径表达式
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource resource = resourcePatternResolver.getResource("classpath:com/xgj/master/java/io/fileDemo/281015.jpg");

        long startTime, elapsedTime; // for speed benchmarking

        // DK 1.7引入了一种新的try-with-resources语法,
        // 它会在try或catch之后自动关闭所有打开的资源,如下所示。
        // 这样的代码看起来更加的优雅。
        try (FileInputStream in = new FileInputStream(resource.getFile());
                FileOutputStream out = new FileOutputStream(new File("D:\\test.jpg"))) {
            // Print file length
            File fileIn = resource.getFile();
            System.out.println("File size is " + fileIn.length() / 1024 / 1024 + " MB");

            startTime = System.nanoTime();

            int byteRead;
            // Read a raw byte, returns an int of 0 to 255.
            while ((byteRead = in.read()) != -1) {
                // Write the least-significant byte of int, drop the upper 3
                // bytes
                out.write(byteRead);
            }
            // cost time
            elapsedTime = System.nanoTime() - startTime;
            System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行结果:

File size is 26 MB
Elapsed Time is 333595.85495 msec

耗费 333595.85495 毫秒 大约是 333S ,也就是5分半钟……


Example 2: Copying a file with a Programmer-Managed Buffer. 使用自定义的Buf

package com.xgj.master.java.io.fileDemo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class FileCopyUserBufferPreJDK7 {

    @Test
    public void test() {
        // 利用Spring提供的ResourcePatternResolver操作资源文件 同时支持Ant风格的资源路径表达式
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("com/xgj/master/java/io/fileDemo/SQL.Cookbook.pdf");

        FileInputStream in = null;
        FileOutputStream out = null;

        long startTime, elapsedTime; // for speed benchmarking

        try {

            // Print file length
            File fileIn = resource.getFile();
            System.out.println("File size is " + fileIn.length() / 1024 / 1024 + " MB");

            in = new FileInputStream(fileIn);
            out = new FileOutputStream(new File("D:\\FileCopyUserBufferPreJDK7.pdf"));

            startTime = System.nanoTime();

            // 4K byte-buffer 每次读取4K
            byte[] byteBuf = new byte[4096];

            int numByteRead;
            while ((numByteRead = in.read(byteBuf)) != -1) {
                out.write(byteBuf, 0, numByteRead);
            }
            // cost time
            elapsedTime = System.nanoTime() - startTime;
            System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流 切记
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

运行结果

File size is 26 MB
Elapsed Time is 198.8157 msec

此示例再次直接使用FileInputStream和FileOutputStream。 但是,它不是一次读取/写入一个字节,而是读/写一个4KB块。 这个程序只花了198毫秒 , 和第一个不使用缓冲 ,速度相比提高了1600多倍,效率不言而喻。

较大的缓冲区大小,达到一定限度,通常会提高I / O性能。 然而,在加速和内存使用之间存在权衡。 对于文件复制,肯定建议使用大型缓冲区。 但是,从文件读取几个字节,大型缓冲区只会浪费内存。
我使用JDK 1.7重新编写程序,并尝试在26MB的更大的文件上的各种缓冲区大小,如下

package com.xgj.master.java.io.fileDemo;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class FileCopyUserBufferLoopJDK7 {

    @Test
    public void test() {

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("com/xgj/master/java/io/fileDemo/SQL.Cookbook.pdf");

        long startTime = 0, elapsedTime; // for speed benchmarking

        int[] bufSizeKB = { 1, 2, 4, 8, 16, 32, 64, 256, 1024 }; // in KB
        int bufSize; // in bytes

        for (int run = 0; run < bufSizeKB.length; ++run) {
            bufSize = bufSizeKB[run] * 1024;
            try (FileInputStream in = new FileInputStream(resource.getFile());
                    FileOutputStream out = new FileOutputStream("D:\\FileCopyUserBufferLoopJDK7.pdf")) {
                startTime = System.nanoTime();
                byte[] byteBuf = new byte[bufSize];
                int numBytesRead;
                while ((numBytesRead = in.read(byteBuf)) != -1) {
                    out.write(byteBuf, 0, numBytesRead);
                }
                elapsedTime = System.nanoTime() - startTime;
                System.out.printf("%4dKB: %6.2fmsec%n", bufSizeKB[run], (elapsedTime / 1000000.0));

            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}

输出结果:

   1KB: 560.22msec
   2KB: 265.76msec
   4KB: 130.43msec
   8KB:  77.81msec
  16KB:  52.49msec
  32KB:  38.99msec
  64KB:  30.66msec
 256KB:  31.00msec
1024KB:  73.45msec

从结果中可以看出一些端倪,并不是缓冲区越大效率要高,存在某个平衡。


Example 3: Copying a file with Buffered Streams.

package com.xgj.master.java.io.fileDemo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class FileCopyBufferedStreamPreJDK7 {

    @Test
    public void test() {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("com/xgj/master/java/io/fileDemo/SQL.Cookbook.pdf");

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        long startTime = 0, elapsedTime; // for speed benchmarking

        try {
            bis = new BufferedInputStream(new FileInputStream(resource.getFile()));
            bos = new BufferedOutputStream(new FileOutputStream("D:\\artisan.pdf"));

            startTime = System.nanoTime();

            int byteRead;
            // Read byte-by-byte from buffer
            while ((byteRead = bis.read()) != -1) {
                bos.write(byteRead);
            }
            elapsedTime = System.nanoTime() - startTime;
            System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally { // always close the streams
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

输出结果:

Elapsed Time is 1432.864941 msec

在这个例子中,我用BufferedInputStream连接FileInputStream,使用BufferedOutputStream连接FileOutputStream,并逐个字节读/写。 JRE决定缓冲区大小。 该程序花费了1432.864941 毫秒,比例1高出,但慢于我们自定义管理的缓冲区。

JDK 1.7版本的例子如下:

package com.xgj.master.java.io.fileDemo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class FileCopyBufferedStreamJDK7 {

    @Test
    public void test() {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("com/xgj/master/java/io/fileDemo/SQL.Cookbook.pdf");

        long startTime = 0, elapsedTime; // for speed benchmarking

        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(resource.getFile()));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("D:\\xgj_2.pdf")))) {

            // Check file length
            File fileIn = resource.getFile();
            System.out.println("File size is " + fileIn.length() + " bytes");

            startTime = System.nanoTime();
            int byteRead;
            while ((byteRead = bis.read()) != -1) {
                bos.write(byteRead);
            }
            elapsedTime = System.nanoTime() - startTime;
            System.out.println("Elapsed Time is " + (elapsedTime / 1000000.0) + " msec");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Formatted Data-Streams: DataInputStream & DataOutputStream

DataInputStream和DataOutputStream可以堆叠在任何InputStream和OutputStream之上,以解析原始字节,以便以所需的数据格式(如int和double)执行I / O操作。

DataInputStream 是数据输入流。它继承于FilterInputStream。
DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。

要使用DataInputStream进行格式化输入,可以按如下方式链接输入流:

DataInputStream in = new DataInputStream(
                        new BufferedInputStream(
                           new FileInputStream("in.dat")));

DataInputStream实现了DataInput接口,它提供了读取格式化的原始数据和字符串的方法,如:

// 8 Primitives
public final int readInt() throws IOExcpetion;       // Read 4 bytes and convert into int
public final double readDoube() throws IOExcpetion;  // Read 8 bytes and convert into double 
public final byte readByte() throws IOExcpetion;
public final char readChar() throws IOExcpetion;
public final short readShort() throws IOExcpetion;
public final long readLong() throws IOExcpetion;
public final boolean readBoolean() throws IOExcpetion;    // Read 1 byte. Convert to false if zero
public final float readFloat() throws IOExcpetion;

public final int readUnsignedByte() throws IOExcpetion;   // Read 1 byte in [0, 255] upcast to int
public final int readUnsignedShort() throws IOExcpetion;  // Read 2 bytes in [0, 65535], same as char, upcast to int
public final void readFully(byte[] b, int off, int len) throws IOException;
public final void readFully(byte[] b) throws IOException;

// Strings
public final String readLine() throws IOException;
     // Read a line (until newline), convert each byte into a char - no unicode support.
public final String readUTF() throws IOException;
    // read a UTF-encoded string with first two bytes indicating its UTF bytes length

public final int skipBytes(int n)  // Skip a number of bytes

同样,你可以按如下方式堆叠DataOutputStream:

DataOutputStream out = new DataOutputStream(
                          new BufferedOutputStream(
                             new FileOutputStream("out.dat")));

DataOutputStream实现DataOutput接口,它提供写入格式化的原始数据和String的方法。 例如,

// 8 primitive types
public final void writeInt(int i) throws IOExcpetion;      // Write the int as 4 bytes
public final void writeFloat(float f) throws IOExcpetion;
public final void writeDoube(double d) throws IOExcpetion; // Write the double as 8 bytes
public final void writeByte(int b) throws IOExcpetion;     // least-significant byte
public final void writeShort(int s) throws IOExcpetion;    // two lower bytes
public final void writeLong(long l) throws IOExcpetion;
public final void writeBoolean(boolean b) throws IOExcpetion;
public final void writeChar(int i) throws IOExcpetion;

// String
public final void writeBytes(String str) throws IOExcpetion;  
     // least-significant byte of each char
public final void writeChars(String str) throws IOExcpetion;
     // Write String as UCS-2 16-bit char, Big-endian (big byte first)
public final void writeUTF(String str) throws IOException;   
     // Write String as UTF, with first two bytes indicating UTF bytes length

public final void write(byte[] b, int off, int len) throws IOException
public final void write(byte[] b) throws IOException
public final void write(int b) throws IOException     // Write the least-significant byte

举例: 以下程序将一些原始语写入磁盘文件。 然后,它读取原始字节以检查原始语的存储方式。 最后,它读取数据作为原始语。

package com.xgj.master.java.io.fileDemo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class DataIOStream {
    public static void main(String[] args) {

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource("com/xgj/master/java/io/fileDemo/data-out.txt");
        String message = "Hi,您好!";

        // Write primitives to an output file
        try (DataOutputStream out = new DataOutputStream(
                new BufferedOutputStream(new FileOutputStream(resource.getFile())))) {
            out.writeByte(127);
            out.writeShort(0xFFFF); // -1
            out.writeInt(0xABCD);
            out.writeLong(0x1234_5678); // JDK 7 syntax
            out.writeFloat(11.22f);
            out.writeDouble(55.66);
            out.writeBoolean(true);
            out.writeBoolean(false);
            for (int i = 0; i < message.length(); ++i) {
                out.writeChar(message.charAt(i));
            }
            out.writeChars(message);
            out.writeBytes(message);
            out.flush();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // Read raw bytes and print in Hex
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(resource.getFile()))) {
            int inByte;
            while ((inByte = in.read()) != -1) {
                System.out.printf("%02X ", inByte); // Print Hex codes
            }
            System.out.printf("%n%n");
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // Read primitives
        try (DataInputStream in = new DataInputStream(
                new BufferedInputStream(new FileInputStream(resource.getFile())))) {
            System.out.println("byte:    " + in.readByte());
            System.out.println("short:   " + in.readShort());
            System.out.println("int:     " + in.readInt());
            System.out.println("long:    " + in.readLong());
            System.out.println("float:   " + in.readFloat());
            System.out.println("double:  " + in.readDouble());
            System.out.println("boolean: " + in.readBoolean());
            System.out.println("boolean: " + in.readBoolean());

            System.out.print("char:    ");
            for (int i = 0; i < message.length(); ++i) {
                System.out.print(in.readChar());
            }
            System.out.println();

            System.out.print("chars:   ");
            for (int i = 0; i < message.length(); ++i) {
                System.out.print(in.readChar());
            }
            System.out.println();

            System.out.print("bytes:   ");
            for (int i = 0; i < message.length(); ++i) {
                System.out.print((char) in.readByte());
            }
            System.out.println();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

输出结果分析:

Java-Java I/O流解读之基于字节的I / O和字节流

存储在磁盘中的数据与内部Java程序的格式完全相同(例如,用于字符的UCS-2)。 字节顺序是big-endian(大字节优先,最低地址中最高有效字节)。


代码

代码已托管到Github—> https://github.com/yangshangwei/JavaMaster