Java IO 之 FileInputStream & FileOutputStream源码分析

简介:

一、引子

文件,作为常见的数据源。关于操作文件的字节流就是 — 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(重写)了抽象类InputStreamread方法。

int read() 方法,即

?
1
public int read() throws IOException

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

 

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

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

代码实现也是比较简单的,也是一个try中调用本地nativereadBytes()方法,直接从文件输入流中读取最多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(重写)了抽象类OutputStreamwrite方法。

void write(int b) 方法,即

?
1
public void write( int b) throws IOException

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

 

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

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

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

 

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. 代码调用的流程如图所示:

iostream

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

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

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

 

五、思考与小结

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

本文小结:

1、FileInputStream 源码分析

2、FileOutputStream 资源分析

3、FileInputStream & FileOutputStream 使用案例

4、其源码调用过程

相关文章
|
4月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
133 0
|
29天前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
125 1
|
3月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
3月前
|
SQL Java 数据库连接
Java IO流(一):字节流与字符流基础
本文全面解析Java IO流,涵盖字节流、字符流及其使用场景,帮助开发者理解IO流分类与用途,掌握文件读写、编码转换、异常处理等核心技术,通过实战案例提升IO编程能力。
|
4月前
|
存储 Java Linux
操作系统层面视角下 Java IO 的演进路径及核心技术变革解析
本文从操作系统层面深入解析Java IO的演进历程,涵盖BIO、NIO、多路复用器及Netty等核心技术。分析各阶段IO模型的原理、优缺点及系统调用机制,探讨Java如何通过底层优化提升并发性能与数据处理效率,全面呈现IO技术的变革路径与发展趋势。
84 2
|
8月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
298 23
|
Java
Java 中 IO 流的分类详解
【10月更文挑战第10天】不同类型的 IO 流具有不同的特点和适用场景,我们可以根据具体的需求选择合适的流来进行数据的输入和输出操作。在实际应用中,还可以通过组合使用多种流来实现更复杂的功能。
356 57
|
9月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
264 0
|
12月前
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
208 10
|
存储 Java 数据安全/隐私保护
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)