1.FileInputStream和FileOutputStream简介
FileInputStream表示在文件系统中,从文件获取输入字节,FileOutputStream表示往File或者FileDescriptor写入数据,如果从文件中获取字符或者往文件中写入字符,则用FileReader和FileWriter替代。另外jdk1.4中新增了nio的相关东西,并且对io进行了重写,所以io里面也提供获取nio中FileChannel的方法
2.FileInputStream源码分析
1 package java.io;
2
3 import java.nio.channels.FileChannel;
4 import sun.nio.ch.FileChannelImpl;
5
6
7 public class FileInputStream extends InputStream
8 {
9 //代表磁盘上存在的文件描述
10 private final FileDescriptor fd;
11 //java nio中的类,代表通道
12 private FileChannel channel = null;
13 //关闭流操作时,需要用到的锁
14 private final Object closeLock = new Object();
15 //流是否已经关闭
16 private volatile boolean closed = false;
17 //线程变量,存储着当前线程是否在finalize流
18 private static final ThreadLocal<Boolean> runningFinalize =
19 new ThreadLocal<>();
20 //判断是否线程在finalize流
21 private static boolean isRunningFinalize() {
22 Boolean val;
23 if ((val = runningFinalize.get()) != null)
24 return val.booleanValue();
25 return false;
26 }
27
28 //根据传入的文件名称来初始化文件字节流
29 public FileInputStream(String name) throws FileNotFoundException {
30 this(name != null ? new File(name) : null);
31 }
32
33 //根据具体的文件来初始化文件字节流
34 public FileInputStream(File file) throws FileNotFoundException {
35 String name = (file != null ? file.getPath() : null);
36 //权限校验
37 SecurityManager security = System.getSecurityManager();
38 if (security != null) {
39 security.checkRead(name);
40 }
41 if (name == null) {
42 throw new NullPointerException();
43 }
44 fd = new FileDescriptor();//初始化一个具体的文件描述
45 fd.incrementAndGetUseCount();//使用文件描述的数量原子性递增1
46 open(name);//通过jvm打开一个对应文件的输入流
47 }
48
49 //根据传入的FileDescriptor来创建输入流
50 public FileInputStream(FileDescriptor fdObj) {
51 //权限校验
52 SecurityManager security = System.getSecurityManager();
53 if (fdObj == null) {
54 throw new NullPointerException();
55 }
56 if (security != null) {
57 security.checkRead(fdObj);
58 }
59 fd = fdObj;
60
61 /*
62 * FileDescriptor is being shared by streams.
63 * Ensure that it's GC'ed only when all the streams/channels are done
64 * using it.
65 */
66 fd.incrementAndGetUseCount();//当前使用该文件的流的数量原子性递增1
67 }
68
69
70 //根据传入的文件名来打开一个文件
71 private native void open(String name) throws FileNotFoundException;
72
73
74 //从文件输入字节流读取一个字节
75 public native int read() throws IOException;
76
77
78 //从文件输入字节流读取len的字节存储到字节数组中,从off位置开始存储
79 private native int readBytes(byte b[], int off, int len) throws IOException;
80
81 //从文件输入字节流读取字节存储到字节数组中
82 public int read(byte b[]) throws IOException {
83 return readBytes(b, 0, b.length);
84 }
85 //从文件输入字节流读取len的字节存储到字节数组中,从off位置开始存储
86 public int read(byte b[], int off, int len) throws IOException {
87 return readBytes(b, off, len);
88 }
89
90 //跳过的字节数量,如果传入的为负数,则IOException
91 public native long skip(long n) throws IOException;
92
93 //返回可以被读取的字节数量,比较耗费资源,一般不掉用这个方法
94 public native int available() throws IOException;
95
96
97 //关闭流,如果当前字节关联一个FileChannel,则channel也会被关闭
98 public void close() throws IOException {
99 synchronized (closeLock) {
100 if (closed) {
101 return;
102 }
103 closed = true;
104 }
105 if (channel != null) {
106 /*
107 * Decrement the FD use count associated with the channel
108 * The use count is incremented whenever a new channel
109 * is obtained from this stream.
110 */
111 fd.decrementAndGetUseCount();//如果当前流被channel引用,则当前文件的引用原子性递减一
112 channel.close();//channel关闭
113 }
114
115 /*
116 * Decrement the FD use count associated with this stream
117 */
118 int useCount = fd.decrementAndGetUseCount();//当前文件的引用原子性递减一
119
120 /*
121 * If FileDescriptor is still in use by another stream, the finalizer
122 * will not close it.
123 */
124 //如果当前文件被多个流使用,则不关闭流
125 if ((useCount <= 0) || !isRunningFinalize()) {
126 close0();
127 }
128 }
129
130 //返回操作系统中的文件描述
131 public final FileDescriptor getFD() throws IOException {
132 if (fd != null) return fd;
133 throw new IOException();
134 }
135
136 //获取当前文件字节输入流的channel, java中nio的类
137 public FileChannel getChannel() {
138 synchronized (this) {
139 if (channel == null) {
140 channel = FileChannelImpl.open(fd, true, false, this);
141
142 /*
143 * Increment fd's use count. Invoking the channel's close()
144 * method will result in decrementing the use count set for
145 * the channel.
146 */
147 fd.incrementAndGetUseCount();//当前文件的引用流原子性递增一
148 }
149 return channel;
150 }
151 }
152
153 private static native void initIDs();
154
155 private native void close0() throws IOException;
156
157 static {
158 initIDs();
159 }
160
161 /**
162 * Ensures that the <code>close</code> method of this file input stream is
163 * called when there are no more references to it.
164 *
165 * @exception IOException if an I/O error occurs.
166 * @see java.io.FileInputStream#close()
167 */
168 //释放文件字节输入流的时候,判断下是否有别的文件字节输出流也在操作这个文件
169 protected void finalize() throws IOException {
170 if ((fd != null) && (fd != FileDescriptor.in)) {
171
172 /*
173 * Finalizer should not release the FileDescriptor if another
174 * stream is still using it. If the user directly invokes
175 * close() then the FileDescriptor is also released.
176 */
177 runningFinalize.set(Boolean.TRUE);
178 try {
179 close();
180 } finally {
181 runningFinalize.set(Boolean.FALSE);
182 }
183 }
184 }
185 }
关于close和finalize方法的一点理解(有不对的地方,请指正)
1.第一个问题:为什么关闭的时候有一段代码要加锁
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
/*
* Decrement the FD use count associated with the channel
* The use count is incremented whenever a new channel
* is obtained from this stream.
*/
fd.decrementAndGetUseCount();
channel.close();
}
/*
* Decrement the FD use count associated with this stream
*/
int useCount = fd.decrementAndGetUseCount();
/*
* If FileDescriptor is still in use by another stream, the finalizer
* will not close it.
*/
if ((useCount <= 0) || !isRunningFinalize()) {
close0();
}
}
因为如果有多个线程都要关闭某个文件的输入流容易关闭多次,造成用户态和内核态的频繁切换,浪费资源,所以可以在关闭的那段代码做个判断,如果当前别的线程已经关闭过,就不需要在关闭了。因为可能多个线程操作所以要保证
变量的可见性,所以成员变量closed要用volatile修饰。
2.第二个问题:close方法和finalize方法的区别
如果我们直接调用close方法去关闭流,则会直接把该输入流关闭,不管该文件上是否有别的流在操作该文件。如果存在别的流,别的流会报错。
如果调用finalize方法,如果存在直接通过构造方法public FileInputStream(FileDescriptor fdObj)的其他多个流,则会先判断是否有别的流存在,通过共同引用的FileDescriptor的实例,通过代码作如下判断
if ((useCount <= 0) || !isRunningFinalize()) ,如果当前没有使用共同FileDescriptor初始化的流,也就是useCount <= 0,才会关闭流,否则finalize方法不会关闭流,最后让使用该资源的流来关闭底层资源
3.FileOutputStream源码分析
1 package java.io;
2
3 import java.nio.channels.FileChannel;
4 import sun.nio.ch.FileChannelImpl;
5
6 public class FileOutputStream extends OutputStream
7 {
8 //代表磁盘上存在的文件描述
9 private final FileDescriptor fd;
10
11 //表示是否追加数据,如果为true则会一直追加数据,否则覆盖原来的数据
12 private final boolean append;
13
14 /**
15 * The associated channel, initalized lazily.延迟加载
16 */
17 private FileChannel channel;
18
19 private final Object closeLock = new Object();//关闭的时候用到的锁
20 //表示当前流是否关闭
21 private volatile boolean closed = false;
22 //线程变量,存储在当前线程是否在finalize流
23 private static final ThreadLocal<Boolean> runningFinalize =
24 new ThreadLocal<>();
25
26 private static boolean isRunningFinalize() {
27 Boolean val;
28 if ((val = runningFinalize.get()) != null)
29 return val.booleanValue();
30 return false;
31 }
32
33 //通过String类型的文件名来初始化文件字节输出流
34 public FileOutputStream(String name) throws FileNotFoundException {
35 this(name != null ? new File(name) : null, false);
36 }
37
38 //通过文件名称和在文件开头或者文件结尾追加数据的append来初始化文件字节输出流
39 public FileOutputStream(String name, boolean append)
40 throws FileNotFoundException
41 {
42 this(name != null ? new File(name) : null, append);
43 }
44
45 //通过文件来初始化输出流
46 public FileOutputStream(File file) throws FileNotFoundException {
47 this(file, false);
48 }
49
50 //通过文件和追加数据的位置来初始化输出流
51 public FileOutputStream(File file, boolean append)
52 throws FileNotFoundException
53 {
54 String name = (file != null ? file.getPath() : null);
55 SecurityManager security = System.getSecurityManager();
56 if (security != null) {
57 security.checkWrite(name);
58 }
59 if (name == null) {
60 throw new NullPointerException();
61 }
62 this.fd = new FileDescriptor();
63 this.append = append;
64
65 fd.incrementAndGetUseCount();//使用原子性自增来表示引用文件的资源加一
66 open(name, append);
67 }
68
69 //根据传入的FileDescriptor来初始化输出流
70 public FileOutputStream(FileDescriptor fdObj) {
71 SecurityManager security = System.getSecurityManager();
72 if (fdObj == null) {
73 throw new NullPointerException();
74 }
75 if (security != null) {
76 security.checkWrite(fdObj);
77 }
78 this.fd = fdObj;
79 this.append = false;
80
81 /*
82 * FileDescriptor is being shared by streams.
83 * Ensure that it's GC'ed only when all the streams/channels are done
84 * using it.
85 */
86 fd.incrementAndGetUseCount();
87 }
88
89 /**
90 * Opens a file, with the specified name, for overwriting or appending.
91 * @param name name of file to be opened
92 * @param append whether the file is to be opened in append mode
93 */
94 private native void open(String name, boolean append)
95 throws FileNotFoundException;
96
97 /**
98 * Writes the specified byte to this file output stream.
99 *
100 * @param b the byte to be written.
101 * @param append {@code true} if the write operation first
102 * advances the position to the end of file
103 */
104 private native void write(int b, boolean append) throws IOException;
105
106 /**
107 * Writes the specified byte to this file output stream. Implements
108 * the <code>write</code> method of <code>OutputStream</code>.
109 *
110 * @param b the byte to be written.
111 * @exception IOException if an I/O error occurs.
112 */
113 public void write(int b) throws IOException {
114 write(b, append);
115 }
116
117 /**
118 * Writes a sub array as a sequence of bytes.
119 * @param b the data to be written
120 * @param off the start offset in the data
121 * @param len the number of bytes that are written
122 * @param append {@code true} to first advance the position to the
123 * end of file
124 * @exception IOException If an I/O error has occurred.
125 */
126 private native void writeBytes(byte b[], int off, int len, boolean append)
127 throws IOException;
128
129 /**
130 * Writes <code>b.length</code> bytes from the specified byte array
131 * to this file output stream.
132 *
133 * @param b the data.
134 * @exception IOException if an I/O error occurs.
135 */
136 public void write(byte b[]) throws IOException {
137 writeBytes(b, 0, b.length, append);
138 }
139
140 /**
141 * Writes <code>len</code> bytes from the specified byte array
142 * starting at offset <code>off</code> to this file output stream.
143 *
144 * @param b the data.
145 * @param off the start offset in the data.
146 * @param len the number of bytes to write.
147 * @exception IOException if an I/O error occurs.
148 */
149 public void write(byte b[], int off, int len) throws IOException {
150 writeBytes(b, off, len, append);
151 }
152
153 //参考FileInputStream的关闭方法
154
155 public void close() throws IOException {
156 synchronized (closeLock) {
157 if (closed) {
158 return;
159 }
160 closed = true;
161 }
162
163 if (channel != null) {
164 /*
165 * Decrement FD use count associated with the channel
166 * The use count is incremented whenever a new channel
167 * is obtained from this stream.
168 */
169 fd.decrementAndGetUseCount();
170 channel.close();
171 }
172
173 /*
174 * Decrement FD use count associated with this stream
175 */
176 int useCount = fd.decrementAndGetUseCount();
177
178 /*
179 * If FileDescriptor is still in use by another stream, the finalizer
180 * will not close it.
181 */
182 if ((useCount <= 0) || !isRunningFinalize()) {
183 close0();
184 }
185 }
186
187
188 //获取当前文件的描述
189 public final FileDescriptor getFD() throws IOException {
190 if (fd != null) return fd;
191 throw new IOException();
192 }
193
194 //获取关联当前文件的唯一的FileChannel
195 public FileChannel getChannel() {
196 synchronized (this) {
197 if (channel == null) {
198 channel = FileChannelImpl.open(fd, false, true, append, this);
199
200 /*
201 * Increment fd's use count. Invoking the channel's close()
202 * method will result in decrementing the use count set for
203 * the channel.
204 */
205 fd.incrementAndGetUseCount();
206 }
207 return channel;
208 }
209 }
210
211 /**
212 * Cleans up the connection to the file, and ensures that the
213 * <code>close</code> method of this file output stream is
214 * called when there are no more references to this stream.
215 *
216 * @exception IOException if an I/O error occurs.
217 * @see java.io.FileInputStream#close()
218 */
219 //释放资源,如果当前资源有其他流在用,在当前流不关闭底层资源
220 protected void finalize() throws IOException {
221 if (fd != null) {
222 //如果是系统的System.in和System.error的话,刷新流
223 if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
224 flush();
225 } else {
226
227 /*
228 * Finalizer should not release the FileDescriptor if another
229 * stream is still using it. If the user directly invokes
230 * close() then the FileDescriptor is also released.
231 */
232 runningFinalize.set(Boolean.TRUE);
233 try {
234 close();
235 } finally {
236 runningFinalize.set(Boolean.FALSE);
237 }
238 }
239 }
240 }
241
242 private native void close0() throws IOException;
243
244 private static native void initIDs();
245
246 static {
247 initIDs();
248 }
249
250 }
4.示例代码
1 package com.zwc.io.test;
2
3 import java.io.FileInputStream;
4 import java.io.FileOutputStream;
5
6 public class TestFile {
7 public static void main(String[] args) throws Exception {
8 FileInputStream fis = new FileInputStream("F:\\demo.txt");
9 System.out.println(fis.available());
10 //如果文件不存在则会创建文件,如果存在则直接往文件里面填充数据
11 FileOutputStream fosStart = new FileOutputStream("F:\\demo1.txt",false);
12 //参数为true,则会一直往文件里面追加数据,false则会直接覆盖原来的数据
13 FileOutputStream fosEnd = new FileOutputStream("F:\\demo2.txt", true);
14 //定义一个1M的缓冲
15 byte[] bytes = new byte[1024];
16 int i = 0;
17 while((i = fis.read(bytes)) != -1) {
18 fosStart.write(bytes);
19 fosEnd.write(bytes);
20 }
21 }
22 }
执行结果
如果执行两次,可以看到往文件里面追加数据
==============================================================
努力工作,用心生活
==============================================================