java(jdk1.7) IO系列03之FileInputStream和FileOutputStream解析
2017-02-28 22:24 童年飞机 阅读(1123) 评论(0) 收藏 举报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 }
执行结果

如果执行两次,可以看到往文件里面追加数据

==============================================================
努力工作,用心生活
==============================================================
浙公网安备 33010602011771号