Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

一、引子

文件,作为常见的数据源。关于操作文件的字节流就是 — FileInputStream & FileOutputStream。它们是Basic IO字节流中重要的实现类。

二、FileInputStream源码分析

FileInputStream源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * FileInputStream 从文件系统的文件中获取输入字节流。文件取决于主机系统。
 *  比如读取图片等的原始字节流。如果读取字符流,考虑使用 FiLeReader。
 */
public class SFileInputStream extends InputStream
{
    /* 文件描述符类---此处用于打开文件的句柄 */
    private final FileDescriptor fd;
 
    /* 引用文件的路径 */
    private final String path;
 
    /* 文件通道,NIO部分 */
    private FileChannel channel = null;
 
    private final Object closeLock = new Object();
    private volatile boolean closed = false;
 
    private static final ThreadLocal<Boolean> runningFinalize =
        new ThreadLocal<>();
 
    private static boolean isRunningFinalize() {
        Boolean val;
        if ((val = runningFinalize.get()) != null)
            return val.booleanValue();
        return false;
    }
 
    /* 通过文件路径名来创建FileInputStream */
    public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }
 
    /* 通过文件来创建FileInputStream */
    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.incrementAndGetUseCount();
        this.path = name;
        open(name);
    }
 
    /* 通过文件描述符类来创建FileInputStream */
    public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;
        fd.incrementAndGetUseCount();
    }
 
    /* 打开文件,为了下一步读取文件内容。native方法 */
    private native void open(String name) throws FileNotFoundException;
 
    /* 从此输入流中读取一个数据字节 */
    public int read() throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int b = 0;
        try {
            b = read0();
        } finally {
            IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1);
        }
        return b;
    }
 
    /* 从此输入流中读取一个数据字节。native方法 */
    private native int read0() throws IOException;
 
    /* 从此输入流中读取多个字节到byte数组中。native方法 */
    private native int readBytes(byte b[], int off, int len) throws IOException;
 
    /* 从此输入流中读取多个字节到byte数组中。 */
    public int read(byte b[]) throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int bytesRead = 0;
        try {
            bytesRead = readBytes(b, 0, b.length);
        } finally {
            IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
        }
        return bytesRead;
    }
 
    /* 从此输入流中读取最多len个字节到byte数组中。 */
    public int read(byte b[], int off, int len) throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int bytesRead = 0;
        try {
            bytesRead = readBytes(b, off, len);
        } finally {
            IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
        }
        return bytesRead;
    }
 
     
    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.decrementAndGetUseCount();
           channel.close();
        }
 
        int useCount = fd.decrementAndGetUseCount();
 
        if ((useCount <= 0) || !isRunningFinalize()) {
            close0();
        }
    }
 
    public final FileDescriptor getFD() throws IOException {
        if (fd != null) return fd;
        throw new IOException();
    }
 
    /* 获取此文件输入流的唯一FileChannel对象 */
    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, false, this);
                fd.incrementAndGetUseCount();
            }
            return channel;
        }
    }
 
    private static native void initIDs();
 
    private native void close0() throws IOException;
 
    static {
        initIDs();
    }
 
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }
}

1. 三个核心方法

三个核心方法,也就是Override(重写)了抽象类InputStream的read方法。

int read() 方法,即

1
public int read() throws IOException

代码实现中很简单,一个try中调用本地native的read0()方法,直接从文件输入流中读取一个字节。IoTrace.fileReadEnd(),字面意思是防止文件没有关闭读的通道,导致读文件失败,一直开着读的通道,会造成内存泄露。

int read(byte b[]) 方法,即

1
public int read(byte b[]) throws IOException

代码实现也是比较简单的,也是一个try中调用本地native的readBytes()方法,直接从文件输入流中读取最多b.length个字节到byte数组b中。

int read(byte b[], int off, int len) 方法,即

1
public int read(byte b[], int off, int len) throws IOException

代码实现和 int read(byte b[])方法 一样,直接从文件输入流中读取最多len个字节到byte数组b中。

可是这里有个问答:

Q: 为什么 int read(byte b[]) 方法需要自己独立实现呢? 直接调用 int read(byte b[], int off, int len) 方法,即read(b , 0 , b.length),等价于read(b)?

A:待完善,希望路过大神回答。。。。向下兼容?? Finally??

2. 值得一提的native方法

上面核心方法中为什么实现简单,因为工作量都在native方法里面,即JVM里面实现了。native倒是不少一一列举吧:

native void open(String name) // 打开文件,为了下一步读取文件内容

native int read0() // 从文件输入流中读取一个字节

native int readBytes(byte b[], int off, int len) // 从文件输入流中读取,从off句柄开始的len个字节,并存储至b字节数组内。

native void close0() // 关闭该文件输入流及涉及的资源,比如说如果该文件输入流的FileChannel对被获取后,需要对FileChannel进行close。

其他还有值得一提的就是,在jdk1.4中,新增了NIO包,优化了一些IO处理的速度,所以在FileInputStream和FileOutputStream中新增了FileChannel getChannel()的方法。即获取与该文件输入流相关的 java.nio.channels.FileChannel对象。

三、FileOutputStream 源码分析

FileOutputStream 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
 * 文件输入流是用于将数据写入文件或者文件描述符类
 *  比如写入图片等的原始字节流。如果写入字符流,考虑使用 FiLeWriter。
 */
public class SFileOutputStream extends OutputStream
{
    /* 文件描述符类---此处用于打开文件的句柄 */
    private final FileDescriptor fd;
 
    /* 引用文件的路径 */
    private final String path;
 
    /* 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处 */
    private final boolean append;
 
    /* 关联的FileChannel类,懒加载 */
    private FileChannel channel;
 
    private final Object closeLock = new Object();
    private volatile boolean closed = false;
    private static final ThreadLocal<Boolean> runningFinalize =
        new ThreadLocal<>();
 
    private static boolean isRunningFinalize() {
        Boolean val;
        if ((val = runningFinalize.get()) != null)
            return val.booleanValue();
        return false;
    }
 
    /* 通过文件名创建文件输入流 */
    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();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        this.append = append;
        this.path = name;
        fd.incrementAndGetUseCount();
        open(name, append);
    }
 
    /* 通过文件描述符类创建文件输入流 */
    public FileOutputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.path = null;
        this.append = false;
 
        fd.incrementAndGetUseCount();
    }
 
    /* 打开文件,并确定文件写入起始处模式 */
    private native void open(String name, boolean append)
        throws FileNotFoundException;
 
    /* 将指定的字节b写入到该文件输入流,并指定文件写入起始处模式 */
    private native void write(int b, boolean append) throws IOException;
 
    /* 将指定的字节b写入到该文件输入流 */
    public void write(int b) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            write(b, append);
            bytesWritten = 1;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }
 
    /* 将指定的字节数组写入该文件输入流,并指定文件写入起始处模式 */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;
 
    /* 将指定的字节数组b写入该文件输入流 */
    public void write(byte b[]) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, 0, b.length, append);
            bytesWritten = b.length;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }
 
    /* 将指定len长度的字节数组b写入该文件输入流 */
    public void write(byte b[], int off, int len) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, off, len, append);
            bytesWritten = len;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }
 
    /* 关闭此文件输出流并释放与此流有关的所有系统资源 */
    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();
        }
    }
 
     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, path, false, true, append, this);
 
                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);
                }
            }
        }
    }
 
    private native void close0() throws IOException;
 
    private static native void initIDs();
 
    static {
        initIDs();
    }
 
}

1. 三个核心方法

三个核心方法,也就是Override(重写)了抽象类OutputStream的write方法。

void write(int b) 方法,即

1
public void write(int b) throws IOException

代码实现中很简单,一个try中调用本地native的write()方法,直接将指定的字节b写入文件输出流。IoTrace.fileReadEnd()的意思和上面FileInputStream意思一致。

void write(byte b[]) 方法,即

1
public void write(byte b[]) throws IOException

代码实现也是比较简单的,也是一个try中调用本地native的writeBytes()方法,直接将指定的字节数组写入该文件输入流。

void write(byte b[], int off, int len) 方法,即

1
public void write(byte b[], int off, int len) throws IOException

代码实现和 void write(byte b[]) 方法 一样,直接将指定的字节数组写入该文件输入流。

2. 值得一提的native方法

上面核心方法中为什么实现简单,因为工作量都在native方法里面,即JVM里面实现了。native倒是不少一一列举吧:

native void open(String name) // 打开文件,为了下一步读取文件内容

native void write(int b, boolean append) // 直接将指定的字节b写入文件输出流

native native void writeBytes(byte b[], int off, int len, boolean append) // 直接将指定的字节数组写入该文件输入流。

native void close0() // 关闭该文件输入流及涉及的资源,比如说如果该文件输入流的FileChannel对被获取后,需要对FileChannel进行close。

 

相似之处:

其实到这里,该想一想。两个源码实现很相似,而且native方法也很相似。其实不能说“相似”,应该以“对应”来概括它们。

它们是一组,是一根吸管的两个孔的关系:“一个Input一个Output”。

休息一下吧~ 看看小广告:

开源代码都在我的gitHub上哦 — https://github.com/JeffLi1993 作者留言“请手贱,点项目star,支持支持拜托拜托”

四、使用案例

下面先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.javacore.io;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
 
/*
 * Copyright [2015] [Jeff Lee]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/**
 * @author Jeff Lee
 * @since 2015-10-8 20:06:03
 * FileInputStream&FileOutputStream使用案例
 */
public class FileIOStreamT {
    private static final String thisFilePath =
            "src" + File.separator +
            "org" + File.separator +
            "javacore" + File.separator +
            "io" + File.separator +
            "FileIOStreamT.java";
    public static void main(String[] args) throws IOException {
        // 创建文件输入流
        FileInputStream fileInputStream = new FileInputStream(thisFilePath);
        // 创建文件输出流
        FileOutputStream fileOutputStream =  new FileOutputStream("data.txt");
         
        // 创建流的最大字节数组
        byte[] inOutBytes = new byte[fileInputStream.available()];
        // 将文件输入流读取,保存至inOutBytes数组
        fileInputStream.read(inOutBytes);
        // 将inOutBytes数组,写出到data.txt文件中
        fileOutputStream.write(inOutBytes);
         
        fileOutputStream.close();
        fileInputStream.close();
    }
}

运行后,会发现根目录中出现了一个“data.txt”文件,内容为上面的代码。

1. 简单地分析下源码:

1、创建了FileInputStream,读取该代码文件为文件输入流。

2、创建了FileOutputStream,作为文件输出流,输出至data.txt文件。

3、针对流的字节数组,一个 read ,一个write,完成读取和写入。

4、关闭流

2. 代码调用的流程如图所示:

3. 代码虽简单,但是有点小问题:

FileInputStream.available() 是返回流中的估计剩余字节数。所以一般不会用此方法。

一般做法,比如创建一个 byte数组,大小1K。然后read至其返回值不为-1,一直读取即可。边读边写。

五、思考与小结

FileInputStream & FileOutputStream 是一对来自 InputStream和OutputStream的实现类。用于本地文件读写(二进制格式按顺序读写)。

本文小结:

1、FileInputStream 源码分析

2、FileOutputStream 资源分析

3、FileInputStream & FileOutputStream 使用案例

4、其源码调用过程

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

Java IO 之 FileInputStream & FileOutputStream源码分析的更多相关文章

  1. 图解 Java IO : 二、FilenameFilter源码

    Writer      :BYSocket(泥沙砖瓦浆木匠) 微         博:BYSocket 豆         瓣:BYSocket FaceBook:BYSocket Twitter   ...

  2. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  3. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  4. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  5. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  6. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

  7. java集合系列之LinkedList源码分析

    java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...

  8. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  9. JAVA设计模式-动态代理(Proxy)源码分析

    在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...

随机推荐

  1. Json.Net4.5 序列化问题

    1.子类序列化 依赖父类属性 [DataContract] public class pcc { [DataMember] public string Name { get; set; } } pub ...

  2. 将doc文件批量转为pdf文件

    需要将不少doc文件转为pdf,WPS带有这种功能,但是鼠标点击次数太多以后整个人都变得很烦躁 用了一下午去搜这方面的工具软件,找到若干.有一些免费,有一些试用的,但总归就找到一个真正能用,虽说生成的 ...

  3. 浅谈压缩感知(三十):压缩感知重构算法之L1最小二乘

    主要内容: l1_ls的算法流程 l1_ls的MATLAB实现 一维信号的实验与结果 前言 前面所介绍的算法都是在匹配追踪算法MP基础上延伸的贪心算法,从本节开始,介绍基于凸优化的压缩感知重构算法. ...

  4. Emmet语法大全手册

    这是基于官方手册整理制作的,因为那个手册网页打开很慢,所以就整理在这里了.以备不时之需. Syntax   Child: > nav>ul>li <nav> <ul ...

  5. Atitit.auto complete 自动完成控件的实现总结

    Atitit.auto complete  自动完成控件的实现总结 1. 框架选型 1 2. 自动完成控件的ioc设置 1 3. Liger  自动完成控件问题 1 4. 官网上的code有问题,不能 ...

  6. Atitit.java c++指针使用总结O7

    Atitit.java c++指针使用总结O7 1. 指针的本质 1 2. 指针的作用 1 1. 提升性能问题这常常用于遍历数组, 1 2. 计算两个指针的的距离 2 3. 避免栈溢出,创建动态数据结 ...

  7. java基础类和对象-题

    1.创建一个三角形类,成员变量三边,方法求周长,创建类主类A来测试它. public class Sanjiaoxing { //定义属性 private int a; private int b; ...

  8. 单元测试之NSNull 检测

    本文主要讲 单元测试之NSNull 检测,在现实开发中,我们最烦的往往就是服务端返回的数据中隐藏着NSNull的数据,一般我们的做法是通过[data isKindOfClass:[NSNull cla ...

  9. UWP开发-二维变换以及三维变换

    在开发中,由于某些需求,我们可能需要做一些平移,缩放,旋转甚至三维变换,所以我来讲讲在UWP中这些变换的实现方法. 一. 二维变换: UIElement.RenderTransform a.Trans ...

  10. 05管理登录名&服务器固定角色-大话数据库

    大纲:学习如何利用SSMS快速自学T-SQL,先看看都有那些服务器固定角色,并且都是干啥的,如何把windows系统用户增加为登录名,单独新建登录名,修改登录名,删除登录名,将角色&登录名进行 ...