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 }

 

执行结果

filebeat memqueue 和 pool 有啥不同 filechannel与outputstream_java

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

filebeat memqueue 和 pool 有啥不同 filechannel与outputstream_sed_02

 

 

==============================================================

努力工作,用心生活

==============================================================