Java IO 之 FileInputStream & FileOutputStream源码分析
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源码分析的更多相关文章
- 图解 Java IO : 二、FilenameFilter源码
Writer :BYSocket(泥沙砖瓦浆木匠) 微 博:BYSocket 豆 瓣:BYSocket FaceBook:BYSocket Twitter ...
- Java并发系列[5]----ReentrantLock源码分析
在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- Java集合系列[4]----LinkedHashMap源码分析
这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...
- Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式
在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...
- Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式
通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...
- java集合系列之LinkedList源码分析
java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- JAVA设计模式-动态代理(Proxy)源码分析
在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...
随机推荐
- 拖动div简单事例代码
事例文件下载 //拖动容器代码 var rDrag = { o: null, init: function (o) { o.onmousedown = this.start; }, start: fu ...
- 设计模式之美:Builder(生成器)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Builder 为每个构件定义一个操作. 实现方式(二):Builder 将构件返回给 Director,Director 将构 ...
- phpMyAdmin导入文件突破2M大小
一:通过phpinfo.php找到php.ini在哪个位置,注意,它并不一定在phpMyAdmin路径下: 二:修改upload_max_filesize,post_max_size,以及memory ...
- spring mvc 配置对静态资源的访问
在spring mvc的配置文件中做如下配置: 1. <?xml version="1.0" encoding="UTF-8"?> <bean ...
- ActiveReports 9实战教程(3): 图文并茂的报表形式
基于上面2节内容,我们搭建了AR9的开发环境,配置好了数据源.在本节,我们以官方提供的3个中文图文并茂的报表来展示AR9的功能,并通过实战的方式一一分享. 以往做报表相关的工作时,最害怕的是报表的UI ...
- Java关系操作符简写
eq--等于.neq--不等于.lt--小于.lte--小于等于.gt--大于.gte--大于等于.empty.null
- Mac直播服务器Nginx配置对HLS的支持
在上一篇中Mac上搭建直播服务器Nginx+rtmp,我们已经搭建了nginx+rtmp直播服务器.下面需要对Nginx服务器增加对HLS的支持.在Nginx增加对HLS种支持比较简单,只是简单的修改 ...
- 转:nginx基础概念(request)
这节我们讲request,在nginx中我们指的是http请求,具体到nginx中的数据结构是ngx_http_request_t.ngx_http_request_t是对一个http请求的封装. 我 ...
- iOS开发---集成百度地图完善版
一.成为百度的开发者.创建应用 http://developer.baidu.com/map/index.php?title=首页 (鼠标移向 然后选择你的项目需要的功能 你可以在里面了解到你想要使用 ...
- JavaWeb学习总结(二)——Tomcat服务器学习和使用(一)
一.Tomcat服务器端口的配置 Tomcat的所有配置都放在conf文件夹之中,里面的server.xml文件是配置的核心文件. 如果想修改Tomcat服务器的启动端口,则可以在server.xml ...