关闭

Java IO - FileInputStream&FileOutputStream

263人阅读 评论(0) 收藏 举报
分类:

基本概念

  • FileInputStream 从文件系统中的某个文件中获得输入字节

  • FileOutputStream 文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。文件是否可用或能否可以被创建取决于基础平台。特别是某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。

  • 继承结构

这里写图片描述

这里写图片描述


实例探究

  • 写入文件的字节都会被底层按照系统默认编码转换成字符存到文件中

  • 当使用文件字节输出来的时候会被反转成字节

public class Test {

    private static final String TEMPFILE = "E:" + File.separator + "Test.txt";
    private static final String DESFILE = "E:" + File.separator + "Test2.txt";

    //英文字母 a ~ h
    private static final byte[] byteArray = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69 };

    public static void main(String[] args) throws IOException {
        write(TEMPFILE);
        read(TEMPFILE);
        copyFile(TEMPFILE, DESFILE);
    }

    public static void write(String path) {
        // 初始化,不然关闭流时编译不通过
        FileOutputStream fos = null;

        try {
            // 创建流,这里探究追加模式。假设文本内容现在为 1,2,3
            fos = new FileOutputStream(new File(path), true);

            // 写入 a
            fos.write(byteArray[0]);

            // 写入 b,c,d,e,f
            fos.write(byteArray, 1, 5);

            fos.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void read(String path) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(path));

            char temp = (char) fis.read();

            // 输出 1
            System.out.print(temp);

            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = fis.read(buffer)) != -1) {
                // 输出 234abcdefabcdefabcdef
                System.out.println(new String(buffer, 0, count));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 文件复制
    public static void copyFile(String srcPath, String desPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(new File(srcPath));
            fos = new FileOutputStream(new File(desPath));

            // 为了效率,一般采用按字节数组读取
            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, count);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }

                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

源码分析

1.FileInputStream

类结构图如下:

这里写图片描述

首先来看类中的静态代码块,它的作用是设置类中(也就是FileInputStream)的属性的内存地址偏移量,便于在必要时操作内存给它赋值。

static {
    initIDs();
}

private static native void initIDs();

成员变量

// 文件描述符类,表示用来打开文件的句柄
private FileDescriptor fd;

// 文件通道,NIO部分
private FileChannel channel = null;

private Object closeLock = new Object(); 

private volatile boolean closed = false;

private static final ThreadLocal<Boolean> runningFinalize = new ThreadLocal<Boolean>();

该类总共定义了 3 个构造函数,通过代码可以发现可以 ①③ 都是通过获取实际的文件连接,在通过 open 方法来创建流。而 是直接通过文件描述符创建流。

// ①构造函数,通过文件路径创建
public FileInputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null);
}

// ②构造函数,通过文件描述符创建
public FileInputStream(FileDescriptor fdObj) {
    SecurityManager security = System.getSecurityManager();
    if (fdObj == null) {
        throw new NullPointerException();
    }
    if (security != null) {
        security.checkRead(fdObj);
    }
    fd = fdObj;

    fd.incrementAndGetUseCount();
}

// ③构造函数,通过文件连接创建
public FileInputStream(File file) throws FileNotFoundException {

    String name = (file != null ? file.getPath() : null);

    // 操作文件之前,检查是否具有 read 权限
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }

    // 判断路径的合法性
    if (name == null) {
        throw new NullPointerException();
    }

    // 打开文件
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    open(name);
}

// 关键-->打开系统文件,native 方法
private native void open(String name) throws FileNotFoundException;

接下来看它 的 read 方法,观察下面的代码,发现在类中定义了常见的 3 种读取方式,如①②③。① 本身就是个native 方法,而 ②③则是通过 readBytes 这个 native 方法用来实现文件的读取。

//① 从此输入流中读取一个数据字节
public native int read() throws IOException;

//关键-->实际操作由它完成
private native int readBytes(byte b[], int off, int len) throws IOException;


//②从此输入流中将最多 len 个字节的数据读入一个 byte 数组中
public int read(byte b[]) throws IOException {
    return readBytes(b, 0, b.length);
}

//③从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中
public int read(byte b[], int off, int len) throws IOException {
    return readBytes(b, off, len);
}

接着来看其他流中没有的而 FileInputStream 中独有的几个方法

//返回文件描述符,表示该文件正在被 FileInputStream 使用
public final FileDescriptor getFD() throws IOException {
    if (fd != null) {
        return fd;
    }
    throw new IOException();
}

//返回文件文件通过,这里只允许单线程访问
public FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, true, false, this);

            fd.incrementAndGetUseCount();

        }
        return channel;
    }
}

//重写了 Object 的方法,确保该文件输入流的close方法被调用的时候它不用有引用
protected void finalize() throws IOException {
    if ((fd != null) && (fd != FileDescriptor.in)) {
        // 当前其他流操作该对象时,调用该方法无法释放资源。但是可以调用 colse 方法强行释放。
        runningFinalize.set(Boolean.TRUE);
        try {
            close();
        } finally {
            runningFinalize.set(Boolean.FALSE);
        }
    }
}

最后再来看看剩下的几个方法

public native long skip(long n) throws IOException;

public native int available() throws IOException;

public void close() throws IOException {

    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        // 减少与该 FD 相关联的计算器(当每获得一个新的通道时,该计算器增加)
        fd.decrementAndGetUseCount();
        channel.close();
    }

    int useCount = fd.decrementAndGetUseCount();

    if ((useCount <= 0) || !isRunningFinalize()) {
        close0();
    }
}

private native void close0() throws IOException;

2.FileOutputStream

类结构图

Alt text

首先来看静态代码块,作用同上。

static {
    initIDs();
}

private static native void initIDs();

成员变量

private FileDescriptor fd;

private FileChannel channel = null;

private boolean append = false;

private Object closeLock = new Object();

private volatile boolean closed = false;

private static final ThreadLocal<Boolean> runningFinalize = new ThreadLocal<Boolean>();

在分析构造函数之前先来看两个 native 方法

//替换原文件的内容
private native void open(String name) throws FileNotFoundException;

//文件原有内容的末尾追加新内容
private native void openAppend(String name) throws FileNotFoundException;

该类定义了 5 个构造函数,其中 ① ~ ④ 都是通过 file 类来创建流,具体的实现都在 ④ 里面。默认未指定 append 时为false,表示替换原文件的内容,当 append 为 true 时,表示在文件原有内容的末尾追加新内容。观察 ④,发现该方法分别调用了 openopenAppend 这两个 native 方法来实现操作。而 ⑤ 则是通过文件描述符完成创建

//①构造函数,根据文件路径创建,默认不追加内容
public FileOutputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null, false);
}

//②构造函数,根据文件路径创建,可以指定是否追加内容
public FileOutputStream(String name, boolean append) throws FileNotFoundException {
    this(name != null ? new File(name) : null, append);
}

//③构造函数,根据文件连接创建,默认不追加内容
public FileOutputStream(File file) throws FileNotFoundException {
    this(file, false);
}

//④构造函数,根据文件连接创建,可以指定是否追加内容
public FileOutputStream(File file, boolean append) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();

    //检查是否具有 "写" 权限
    if (security != null) {
        security.checkWrite(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    this.append = append;

    //判断是否追加内容
    if (append) {
        openAppend(name);
    } else {
        open(name);
    }
}

//⑤构造函数,根据文件描述符创建
public FileOutputStream(FileDescriptor fdObj) {
    SecurityManager security = System.getSecurityManager();
    if (fdObj == null) {
        throw new NullPointerException();
    }

    if (security != null) {
        security.checkWrite(fdObj);
    }

    fd = fdObj;

    fd.incrementAndGetUseCount();
}

接着来看 writer 方法,与 文件输入流的 read 方法类型,不再分析。

public native void write(int b) throws IOException;

private native void writeBytes(byte b[], int off, int len) throws IOException;

public void write(byte b[]) throws IOException {
    writeBytes(b, 0, b.length);
}

public void write(byte b[], int off, int len) throws IOException {
    writeBytes(b, off, len);
}

再来看看普通输出流没有,而 FileOutputStream 独有的方法。

public final FileDescriptor getFD() throws IOException {
    if (fd != null) {
        return fd;
    }

    throw new IOException();
}

public FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, false, true, this, append);

            fd.incrementAndGetUseCount();
        }
        return channel;
    }
}

protected void finalize() throws IOException {
    if (fd != null) {
        if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            flush();
        } else {

            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }
}

最后再来看看 close 方法

public void close() throws IOException {
    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        fd.decrementAndGetUseCount();
        channel.close();
    }

    int useCount = fd.decrementAndGetUseCount();

    if ((useCount <= 0) || !isRunningFinalize()) {
        close0();
    }
}

private static boolean isRunningFinalize() {
    Boolean val;
    if ((val = runningFinalize.get()) != null)
        return val.booleanValue();
    return false;
}

private native void close0() throws IOException;

0
0
查看评论

Java IO之FileInputStream&FileOutputStream

Java IO之InputStream和OutputStreamInputStream和OutputStream之间的对应关系InputStream与OutputStream及其子类间具有很强的对称性。下图很形象地展现出它们之间的对应关系,只有PrintStream是没有对应的。 1.FileIn...
  • jssg_tzw
  • jssg_tzw
  • 2017-09-18 15:49
  • 235

Java——FileInputStream&FileOutputStream字节流实现文件复制

要实现文件的复制,有很多种方法。今天介绍一种最基础的方法:使用FileInputStream和FileOutputStream实现文件的复制。思路:要实现文件的复制,其实质就是对源文件数据进行读取,再将这些数据写入目标文件,从而实现文件的复制。实现:创建IOUtils类及copyFile方法:pac...
  • u012325167
  • u012325167
  • 2016-03-11 22:00
  • 3087

深入理解Java中的IO

深入理解Java中的IO,个人学习总结。
  • qq_25184739
  • qq_25184739
  • 2016-04-21 01:15
  • 9203

java-IO操作性能对比

在软件系统中,IO速度比内存速度慢,IO读写在很多情况下会是系统的瓶颈。 在java标准IO操作中,InputStream和OutputStream提供基于流的IO操作,以字节为处理单位;Reader和Writer实现了Buffered缓存,以字符为处理单位。 从Java1.4开始,增加NIO(Ne...
  • wangpeifeng669
  • wangpeifeng669
  • 2014-06-05 08:31
  • 1566

java中的IO操作总结(一

所谓IO,也就是Input与Output的缩写。在java中,IO涉及的范围比较大,这里主要讨论针对文件内容的读写 其他知识点将放置后续章节(我想,文章太长了,谁都没耐心翻到最后)   对于文件内容的操作主要分为两大类 分别是:   字符流   字节流 ...
  • u012467492
  • u012467492
  • 2016-10-30 17:11
  • 1975

java io与线程

1.io流 一、字节流 输入流InputStream,输出流OutputStream,这两个是抽象里,分别包括read(byte[] b,int off,int len)和write(byte[] b,int off,int len) 文件流FileInputStream,FileOutput...
  • junfeng120125
  • junfeng120125
  • 2012-11-06 10:08
  • 2728

Java IO读文件和写文件

写文件Java代码:package hand.wang.test;import java.io.*;public class write { public static void main(String[] args) { write("E://123.txt"...
  • wang_zhou_jian
  • wang_zhou_jian
  • 2009-12-19 13:06
  • 16260

java io 层次结构图 io详解

Java中IO结构总图Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。 Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 ja...
  • lexang1
  • lexang1
  • 2017-08-28 13:09
  • 536

提高Java IO操作的性能

 一、性能优化的一般概念 人们普遍认为Java程序总是比C程序慢,对于这种意见,大多数人或许已经听得太多了。实际上,情况远比那些陈旧的主张要复杂。许多 Java程序确实很慢,但速度慢不是所有Java程序的固有特征。许多Java程序可以达到C或C++中类似程序的效率,但只有当设计者和程序员在...
  • redv
  • redv
  • 2005-03-31 23:08
  • 6086

Java IO:阻塞/非阻塞式IO、同步/异步IO

转载请注明出处:jiq•钦's technical Blog 本文主要内容转自这篇文章,并在此基础上理解后总结。引言同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有...
  • jiq408694711
  • jiq408694711
  • 2015-07-06 22:58
  • 2322
    个人资料
    • 访问:67972次
    • 积分:1701
    • 等级:
    • 排名:千里之外
    • 原创:107篇
    • 转载:0篇
    • 译文:0篇
    • 评论:4条
    博客专栏