天天看點

Java IO 之 FileInputStream & FileOutputStream源碼分析一、引子二、FileInputStream源碼分析三、FileOutputStream 源碼分析四、使用案例五、思考與小結

檔案,作為常見的資料源。關于操作檔案的位元組流就是 — fileinputstream & fileoutputstream。它們是basic io位元組流中重要的實作類。

fileinputstream源碼如下:

<a href="http://www.bysocket.com/?p=611#">?</a>

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

<code>/**</code>

<code> </code><code>* fileinputstream 從檔案系統的檔案中擷取輸入位元組流。檔案取決于主機系統。</code>

<code> </code><code>*  比如讀取圖檔等的原始位元組流。如果讀取字元流,考慮使用 filereader。</code>

<code> </code><code>*/</code>

<code>public</code> <code>class</code> <code>sfileinputstream</code><code>extends</code> <code>inputstream</code>

<code>{</code>

<code>    </code><code>/* 檔案描述符類---此處用于打開檔案的句柄 */</code>

<code>    </code><code>private final filedescriptor fd;</code>

<code>    </code><code>/* 引用檔案的路徑 */</code>

<code>    </code><code>private final string path;</code>

<code>    </code><code>/* 檔案通道,nio部分 */</code>

<code>    </code><code>private filechannel channel = null;</code>

<code>    </code><code>private final object closelock = new object();</code>

<code>    </code><code>private volatile boolean closed = false;</code>

<code>    </code><code>private static final threadlocal&lt;boolean&gt; runningfinalize =</code>

<code>        </code><code>new threadlocal&lt;&gt;();</code>

<code>    </code><code>private static boolean isrunningfinalize() {</code>

<code>        </code><code>boolean val;</code>

<code>        </code><code>if ((val = runningfinalize.get()) != null)</code>

<code>            </code><code>return val.booleanvalue();</code>

<code>        </code><code>return false;</code>

<code>    </code><code>}</code>

<code>    </code><code>/* 通過檔案路徑名來建立fileinputstream */</code>

<code>    </code><code>public fileinputstream(string name) throws filenotfoundexception {</code>

<code>        </code><code>this(name != null ? new file(name) : null);</code>

<code>    </code><code>/* 通過檔案來建立fileinputstream */</code>

<code>    </code><code>public fileinputstream(file file) throws filenotfoundexception {</code>

<code>        </code><code>string name = (file != null ? file.getpath() : null);</code>

<code>        </code><code>securitymanager security = system.getsecuritymanager();</code>

<code>        </code><code>if (security != null) {</code>

<code>            </code><code>security.checkread(name);</code>

<code>        </code><code>}</code>

<code>        </code><code>if (name == null) {</code>

<code>            </code><code>throw new nullpointerexception();</code>

<code>        </code><code>if (file.isinvalid()) {</code>

<code>            </code><code>throw new filenotfoundexception("invalid file path");</code>

<code>        </code><code>fd = new filedescriptor();</code>

<code>        </code><code>fd.incrementandgetusecount();</code>

<code>        </code><code>this.path = name;</code>

<code>        </code><code>open(name);</code>

<code>    </code><code>/* 通過檔案描述符類來建立fileinputstream */</code>

<code>    </code><code>public fileinputstream(filedescriptor fdobj) {</code>

<code>        </code><code>if (fdobj == null) {</code>

<code>            </code><code>security.checkread(fdobj);</code>

<code>        </code><code>fd = fdobj;</code>

<code>        </code><code>path = null;</code>

<code>    </code><code>/* 打開檔案,為了下一步讀取檔案内容。native方法 */</code>

<code>    </code><code>private native void open(string name) throws filenotfoundexception;</code>

<code>    </code><code>/* 從此輸入流中讀取一個資料位元組 */</code>

<code>    </code><code>public int read() throws ioexception {</code>

<code>        </code><code>object tracecontext = iotrace.filereadbegin(path);</code>

<code>        </code><code>int b = 0;</code>

<code>        </code><code>try {</code>

<code>            </code><code>b = read0();</code>

<code>        </code><code>} finally {</code>

<code>            </code><code>iotrace.filereadend(tracecontext, b == -1 ? 0 : 1);</code>

<code>        </code><code>return b;</code>

<code>    </code><code>/* 從此輸入流中讀取一個資料位元組。native方法 */</code>

<code>    </code><code>private native int read0() throws ioexception;</code>

<code>    </code><code>/* 從此輸入流中讀取多個位元組到byte數組中。native方法 */</code>

<code>    </code><code>private native int readbytes(byte b[], int off, int len) throws ioexception;</code>

<code>    </code><code>/* 從此輸入流中讀取多個位元組到byte數組中。 */</code>

<code>    </code><code>public int read(byte b[]) throws ioexception {</code>

<code>        </code><code>int bytesread = 0;</code>

<code>            </code><code>bytesread = readbytes(b, 0, b.length);</code>

<code>            </code><code>iotrace.filereadend(tracecontext, bytesread == -1 ? 0 : bytesread);</code>

<code>        </code><code>return bytesread;</code>

<code>    </code><code>/* 從此輸入流中讀取最多len個位元組到byte數組中。 */</code>

<code>    </code><code>public int read(byte b[], int off, int len) throws ioexception {</code>

<code>            </code><code>bytesread = readbytes(b, off, len);</code>

<code>    </code> 

<code>    </code><code>public native long skip(long n) throws ioexception;</code>

<code>    </code><code>/* 傳回下一次對此輸入流調用的方法可以不受阻塞地從此輸入流讀取(或跳過)的估計剩餘位元組數。 */</code>

<code>    </code><code>public native int available() throws ioexception;</code>

<code>    </code><code>/* 關閉此檔案輸入流并釋放與此流有關的所有系統資源。 */</code>

<code>    </code><code>public void close() throws ioexception {</code>

<code>        </code><code>synchronized (closelock) {</code>

<code>            </code><code>if (closed) {</code>

<code>                </code><code>return;</code>

<code>            </code><code>}</code>

<code>            </code><code>closed = true;</code>

<code>        </code><code>if (channel != null) {</code>

<code>           </code><code>fd.decrementandgetusecount();</code>

<code>           </code><code>channel.close();</code>

<code>        </code><code>int usecount = fd.decrementandgetusecount();</code>

<code>        </code><code>if ((usecount &lt;= 0) || !isrunningfinalize()) {</code>

<code>            </code><code>close0();</code>

<code>    </code><code>public final filedescriptor getfd() throws ioexception {</code>

<code>        </code><code>if (fd != null) return fd;</code>

<code>        </code><code>throw new ioexception();</code>

<code>    </code><code>/* 擷取此檔案輸入流的唯一filechannel對象 */</code>

<code>    </code><code>public</code> <code>filechannel getchannel() {</code>

<code>        </code><code>synchronized</code> <code>(</code><code>this</code><code>) {</code>

<code>            </code><code>if</code> <code>(channel ==</code><code>null</code><code>) {</code>

<code>                </code><code>channel = filechannelimpl.open(fd, path,</code><code>true</code><code>,</code><code>false</code><code>,</code><code>this</code><code>);</code>

<code>                </code><code>fd.incrementandgetusecount();</code>

<code>            </code><code>return</code> <code>channel;</code>

<code>    </code><code>private</code> <code>static</code> <code>native</code> <code>void</code> <code>initids();</code>

<code>    </code><code>private</code> <code>native</code> <code>void</code> <code>close0()</code><code>throws</code> <code>ioexception;</code>

<code>    </code><code>static</code> <code>{</code>

<code>        </code><code>initids();</code>

<code>    </code><code>protected</code> <code>void</code> <code>finalize()</code><code>throws</code> <code>ioexception {</code>

<code>        </code><code>if</code> <code>((fd !=</code><code>null</code><code>) &amp;&amp;  (fd != filedescriptor.in)) {</code>

<code>            </code><code>runningfinalize.set(boolean.true);</code>

<code>            </code><code>try</code> <code>{</code>

<code>                </code><code>close();</code>

<code>            </code><code>}</code><code>finally</code> <code>{</code>

<code>                </code><code>runningfinalize.set(boolean.false);</code>

<code>}</code>

1. 三個核心方法

三個核心方法,也就是override(重寫)了抽象類inputstream的read方法。

int read() 方法,即

<code>public</code> <code>int</code> <code>read()</code><code>throws</code> <code>ioexception</code>

代碼實作中很簡單,一個try中調用本地native的read0()方法,直接從檔案輸入流中讀取一個位元組。iotrace.filereadend(),字面意思是防止檔案沒有關閉讀的通道,導緻讀檔案失敗,一直開着讀的通道,會造成記憶體洩露。

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

<code>public</code> <code>int</code> <code>read(</code><code>byte</code> <code>b[])</code><code>throws</code> <code>ioexception</code>

代碼實作也是比較簡單的,也是一個try中調用本地native的readbytes()方法,直接從檔案輸入流中讀取最多b.length個位元組到byte數組b中。

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

<code>public</code> <code>int</code> <code>read(</code><code>byte</code> <code>b[],</code><code>int</code> <code>off,</code><code>int</code> <code>len)</code><code>throws</code> <code>ioexception</code>

代碼實作和 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 源碼如下:

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

<code> </code><code>* 檔案輸入流是用于将資料寫入檔案或者檔案描述符類</code>

<code> </code><code>*  比如寫入圖檔等的原始位元組流。如果寫入字元流,考慮使用 filewriter。</code>

<code>public</code> <code>class</code> <code>sfileoutputstream</code><code>extends</code> <code>outputstream</code>

<code>    </code><code>/* 如果為 true,則将位元組寫入檔案末尾處,而不是寫入檔案開始處 */</code>

<code>    </code><code>private final boolean append;</code>

<code>    </code><code>/* 關聯的filechannel類,懶加載 */</code>

<code>    </code><code>private filechannel channel;</code>

<code>    </code><code>/* 通過檔案名建立檔案輸入流 */</code>

<code>    </code><code>public fileoutputstream(string name) throws filenotfoundexception {</code>

<code>        </code><code>this(name != null ? new file(name) : null, false);</code>

<code>    </code><code>/* 通過檔案名建立檔案輸入流,并确定檔案寫入起始處模式 */</code>

<code>    </code><code>public fileoutputstream(string name, boolean append)</code>

<code>        </code><code>throws filenotfoundexception</code>

<code>    </code><code>{</code>

<code>        </code><code>this(name != null ? new file(name) : null, append);</code>

<code>    </code><code>/* 通過檔案建立檔案輸入流,預設寫入檔案的開始處 */</code>

<code>    </code><code>public fileoutputstream(file file) throws filenotfoundexception {</code>

<code>        </code><code>this(file, false);</code>

<code>    </code><code>/* 通過檔案建立檔案輸入流,并确定檔案寫入起始處  */</code>

<code>    </code><code>public fileoutputstream(file file, boolean append)</code>

<code>            </code><code>security.checkwrite(name);</code>

<code>        </code><code>this.fd = new filedescriptor();</code>

<code>        </code><code>this.append = append;</code>

<code>        </code><code>open(name, append);</code>

<code>    </code><code>/* 通過檔案描述符類建立檔案輸入流 */</code>

<code>    </code><code>public fileoutputstream(filedescriptor fdobj) {</code>

<code>            </code><code>security.checkwrite(fdobj);</code>

<code>        </code><code>this.fd = fdobj;</code>

<code>        </code><code>this.path = null;</code>

<code>        </code><code>this.append = false;</code>

<code>    </code><code>/* 打開檔案,并确定檔案寫入起始處模式 */</code>

<code>    </code><code>private native void open(string name, boolean append)</code>

<code>        </code><code>throws filenotfoundexception;</code>

<code>    </code><code>/* 将指定的位元組b寫入到該檔案輸入流,并指定檔案寫入起始處模式 */</code>

<code>    </code><code>private native void write(int b, boolean append) throws ioexception;</code>

<code>    </code><code>/* 将指定的位元組b寫入到該檔案輸入流 */</code>

<code>    </code><code>public void write(int b) throws ioexception {</code>

<code>        </code><code>object tracecontext = iotrace.filewritebegin(path);</code>

<code>        </code><code>int byteswritten = 0;</code>

<code>            </code><code>write(b, append);</code>

<code>            </code><code>byteswritten = 1;</code>

<code>            </code><code>iotrace.filewriteend(tracecontext, byteswritten);</code>

<code>    </code><code>/* 将指定的位元組數組寫入該檔案輸入流,并指定檔案寫入起始處模式 */</code>

<code>    </code><code>private native void writebytes(byte b[], int off, int len, boolean append)</code>

<code>        </code><code>throws ioexception;</code>

<code>    </code><code>/* 将指定的位元組數組b寫入該檔案輸入流 */</code>

<code>    </code><code>public void write(byte b[]) throws ioexception {</code>

<code>            </code><code>writebytes(b, 0, b.length, append);</code>

<code>            </code><code>byteswritten = b.length;</code>

<code>    </code><code>/* 将指定len長度的位元組數組b寫入該檔案輸入流 */</code>

<code>    </code><code>public void write(byte b[], int off, int len) throws ioexception {</code>

<code>            </code><code>writebytes(b, off, len, append);</code>

<code>            </code><code>byteswritten = len;</code>

<code>    </code><code>/* 關閉此檔案輸出流并釋放與此流有關的所有系統資源 */</code>

<code>    </code><code>public</code> <code>void</code> <code>close()</code><code>throws</code> <code>ioexception {</code>

<code>        </code><code>synchronized</code> <code>(closelock) {</code>

<code>            </code><code>if</code> <code>(closed) {</code>

<code>                </code><code>return</code><code>;</code>

<code>            </code><code>closed =</code><code>true</code><code>;</code>

<code>        </code><code>if</code> <code>(channel !=</code><code>null</code><code>) {</code>

<code>            </code><code>fd.decrementandgetusecount();</code>

<code>            </code><code>channel.close();</code>

<code>        </code><code>int</code> <code>usecount = fd.decrementandgetusecount();</code>

<code>        </code><code>if</code> <code>((usecount &lt;=</code><code>0</code><code>) || !isrunningfinalize()) {</code>

<code>     </code><code>public</code> <code>final</code> <code>filedescriptor getfd() </code><code>throws</code> <code>ioexception {</code>

<code>        </code><code>if</code> <code>(fd !=</code><code>null</code><code>)</code><code>return</code> <code>fd;</code>

<code>        </code><code>throw</code> <code>new</code> <code>ioexception();</code>

<code>     </code><code>}</code>

<code>                </code><code>channel = filechannelimpl.open(fd, path,</code><code>false</code><code>,</code><code>true</code><code>, append,</code><code>this</code><code>);</code>

<code>        </code><code>if</code> <code>(fd !=</code><code>null</code><code>) {</code>

<code>            </code><code>if</code> <code>(fd == filedescriptor.out || fd == filedescriptor.err) {</code>

<code>                </code><code>flush();</code>

<code>            </code><code>}</code><code>else</code> <code>{</code>

<code>                </code><code>runningfinalize.set(boolean.true);</code>

<code>                </code><code>try</code> <code>{</code>

<code>                    </code><code>close();</code>

<code>                </code><code>}</code><code>finally</code> <code>{</code>

<code>                    </code><code>runningfinalize.set(boolean.false);</code>

<code>                </code><code>}</code>

三個核心方法,也就是override(重寫)了抽象類outputstream的write方法。

void write(int b) 方法,即

<code>public</code> <code>void</code> <code>write(</code><code>int</code> <code>b)</code><code>throws</code> <code>ioexception</code>

代碼實作中很簡單,一個try中調用本地native的write()方法,直接将指定的位元組b寫入檔案輸出流。iotrace.filereadend()的意思和上面fileinputstream意思一緻。

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

<code>public</code> <code>void</code> <code>write(</code><code>byte</code> <code>b[])</code><code>throws</code> <code>ioexception</code>

代碼實作也是比較簡單的,也是一個try中調用本地native的writebytes()方法,直接将指定的位元組數組寫入該檔案輸入流。

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

<code>public</code> <code>void</code> <code>write(</code><code>byte</code> <code>b[],</code><code>int</code> <code>off,</code><code>int</code> <code>len)</code><code>throws</code> <code>ioexception</code>

代碼實作和 void write(byte b[]) 方法 一樣,直接将指定的位元組數組寫入該檔案輸入流。

native void write(int b, boolean append) // 直接将指定的位元組b寫入檔案輸出流 native native void writebytes(byte b[], int off, int len, boolean append) // 直接将指定的位元組數組寫入該檔案輸入流。

相似之處:

其實到這裡,該想一想。兩個源碼實作很相似,而且native方法也很相似。其實不能說“相似”,應該以“對應”來概括它們。 它們是一組,是一根吸管的兩個孔的關系:“一個input一個output”。 休息一下吧~ 看看小廣告:

下面先看代碼:

<code>package</code> <code>org.javacore.io;</code>

<code>import</code> <code>java.io.file;</code>

<code>import</code> <code>java.io.fileinputstream;</code>

<code>import</code> <code>java.io.fileoutputstream;</code>

<code>import</code> <code>java.io.ioexception;</code>

<code>/*</code>

<code> </code><code>* copyright [2015] [jeff lee]</code>

<code> </code><code>*</code>

<code> </code><code>* licensed under the apache license, version 2.0 (the "license");</code>

<code> </code><code>* you may not use this file except in compliance with the license.</code>

<code> </code><code>* you may obtain a copy of the license at</code>

<code> </code><code>* unless required by applicable law or agreed to in writing, software</code>

<code> </code><code>* distributed under the license is distributed on an "as is" basis,</code>

<code> </code><code>* without warranties or conditions of any kind, either express or implied.</code>

<code> </code><code>* see the license for the specific language governing permissions and</code>

<code> </code><code>* limitations under the license.</code>

<code> </code><code>* @author jeff lee</code>

<code> </code><code>* @since 2015-10-8 20:06:03</code>

<code> </code><code>* fileinputstream&amp;fileoutputstream使用案例</code>

<code>public</code> <code>class</code> <code>fileiostreamt {</code>

<code>    </code><code>private</code> <code>static</code> <code>final</code> <code>string thisfilepath =</code>

<code>            </code><code>"src"</code> <code>+ file.separator +</code>

<code>            </code><code>"org"</code> <code>+ file.separator +</code>

<code>            </code><code>"javacore"</code> <code>+ file.separator +</code>

<code>            </code><code>"io"</code> <code>+ file.separator +</code>

<code>            </code><code>"fileiostreamt.java"</code><code>;</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args)</code><code>throws</code> <code>ioexception {</code>

<code>        </code><code>// 建立檔案輸入流</code>

<code>        </code><code>fileinputstream fileinputstream =</code><code>new</code> <code>fileinputstream(thisfilepath);</code>

<code>        </code><code>// 建立檔案輸出流</code>

<code>        </code><code>fileoutputstream fileoutputstream = </code><code>new</code> <code>fileoutputstream(</code><code>"data.txt"</code><code>);</code>

<code>        </code> 

<code>        </code><code>// 建立流的最大位元組數組</code>

<code>        </code><code>byte</code><code>[] inoutbytes =</code><code>new</code> <code>byte</code><code>[fileinputstream.available()];</code>

<code>        </code><code>// 将檔案輸入流讀取,儲存至inoutbytes數組</code>

<code>        </code><code>fileinputstream.read(inoutbytes);</code>

<code>        </code><code>// 将inoutbytes數組,寫出到data.txt檔案中</code>

<code>        </code><code>fileoutputstream.write(inoutbytes);</code>

<code>        </code><code>fileoutputstream.close();</code>

<code>        </code><code>fileinputstream.close();</code>

運作後,會發現根目錄中出現了一個“data.txt”檔案,内容為上面的代碼。

1. 簡單地分析下源碼:

1、建立了fileinputstream,讀取該代碼檔案為檔案輸入流。 2、建立了fileoutputstream,作為檔案輸出流,輸出至data.txt檔案。 3、針對流的位元組數組,一個 read ,一個write,完成讀取和寫入。 4、關閉流

2. 代碼調用的流程如圖所示:

Java IO 之 FileInputStream &amp; FileOutputStream源碼分析一、引子二、FileInputStream源碼分析三、FileOutputStream 源碼分析四、使用案例五、思考與小結

3. 代碼雖簡單,但是有點小問題:

fileinputstream.available() 是傳回流中的估計剩餘位元組數。是以一般不會用此方法。 一般做法,比如建立一個 byte數組,大小1k。然後read至其傳回值不為-1,一直讀取即可。邊讀邊寫。

fileinputstream &amp; fileoutputstream 是一對來自 inputstream和outputstream的實作類。用于本地檔案讀寫(二進制格式按順序讀寫)。

本文小結:

1、fileinputstream 源碼分析 2、fileoutputstream 資源分析 3、fileinputstream &amp; fileoutputstream 使用案例 4、其源碼調用過程