以文件流作爲一個切面,閱讀Hadoop源代碼org.apache.hadoop.fs包中源代碼。關於流,分爲輸入流和輸出流兩種,下面也這樣簡單劃分爲兩類進行閱讀分析。
輸入流類
與輸入流相關的接口和類的繼承層次關係如下所示:
- ◦java.io.InputStream(java.io.Closeable)
- ◦java.io.FilterInputStream
- ◦java.io.DataInputStream(implements java.io.DataInput)
- ◦org.apache.hadoop.fs.FSDataInputStream(implements org.apache.hadoop.fs.Seekable, org.apache.hadoop.fs.PositionedReadable)
- ◦org.apache.hadoop.fs.HarFileSystem.HarFSDataInputStream
FSDataInputStream類實現了Seekable與PositionedReadable接口,賦予了Hadoop文件系統中的文件輸入流分別能夠進行流式搜索與定位流式讀取的語義。
Seekable接口定義如下:
- package org.apache.hadoop.fs;
- import java.io.*;
- /** Stream that permits seeking. */
- public interface Seekable {
- /**
- * 從指定文件中的位置pos,對文件流進行前向搜索。
- */
- void seek(long pos) throws IOException;
- /**
- * 返回文件流中當前偏移位置。
- */
- long getPos() throws IOException;
- /**
- * 從targetPos位置搜索文件數據的一個不同拷貝,搜索到則返回true,否則返回false。
- */
- boolean seekToNewSource(long targetPos) throws IOException;
- }
Seekable 接口中定義的方法,都是基於文件流的位置進行操作的方法,使得在文件系統中或文件系統之間進行流式操作更加方便。
PositionedReadable接口定義如下:
- package org.apache.hadoop.fs;
- import java.io.*;
- import org.apache.hadoop.fs.*;
- public interface PositionedReadable {
- /**
- * 讀取文件流中最多到length大小的字節,到字節緩衝區buffer中,它是從給定的position位置開始讀取的。
- * 該讀取方式不改變文件的當前偏移位置offset,並且該方法是線程安全的。
- */
- public int read(long position, byte[] buffer, int offset, int length) throws IOException;
- /**
- * 讀取文件流中length大小的字節,到字節緩衝區buffer中,它是從給定的position位置開始讀取的。
- * 該讀取方式不改變文件的當前偏移位置offset,並且該方法是線程安全的。
- */
- public void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
- /**
- * 讀取文件流中buffer長度的字節,到字節緩衝區buffer中,它是從給定的position位置開始讀取的
- * 該讀取方式不改變文件的當前偏移位置offset,並且該方法是線程安全的。
- */
- public void readFully(long position, byte[] buffer) throws IOException;
- }
PositionedReadable接口中定義了三個基於位置來進行流式讀取的操作。
接着,FSDataInputStream類繼承自DataInputStream類,並實現上述這兩個接口,必須實現接口中定義的操作:
- package org.apache.hadoop.fs;
- import java.io.*;
- public class FSDataInputStream extends DataInputStream implements Seekable, PositionedReadable {
- public FSDataInputStream(InputStream in)
- throws IOException {
- super(in); // 調用基類的構造方法,初始化一個基本流類屬性InputStream in
- if( !(in instanceof Seekable) || !(in instanceof PositionedReadable) ) { // 強制保證InputStream in必須實現Seekable與PositionedReadable這兩個接口。
- throw new IllegalArgumentException(
- "In is not an instance of Seekable or PositionedReadable");
- }
- }
- public synchronized void seek(long desired) throws IOException {
- ((Seekable)in).seek(desired); // 設置從in的desired位置開始搜索輸入流流in
- }
- public long getPos() throws IOException {
- return ((Seekable)in).getPos();
- }
- public int read(long position, byte[] buffer, int offset, int length)
- throws IOException {
- return ((PositionedReadable)in).read(position, buffer, offset, length);
- }
- public void readFully(long position, byte[] buffer, int offset, int length)
- throws IOException {
- ((PositionedReadable)in).readFully(position, buffer, offset, length);
- }
- public void readFully(long position, byte[] buffer)
- throws IOException {
- ((PositionedReadable)in).readFully(position, buffer, 0, buffer.length);
- }
- public boolean seekToNewSource(long targetPos) throws IOException {
- return ((Seekable)in).seekToNewSource(targetPos);
- }
- }
FSDataInputStream輸入流類最顯著的特徵就是,能夠基於流的位置而進行流式操作。
另外,在org.apache.hadoop.fs包中還定義了基於RAF(Random Access File)風格的輸入流類,可以隨機讀取該流對象。繼承關係如下所示:
- ◦java.io.InputStream(implements java.io.Closeable)
- ◦org.apache.hadoop.fs.FSInputStream(implements org.apache.hadoop.fs.Seekable, org.apache.hadoop.fs.PositionedReadable)
- ◦org.apache.hadoop.fs.FSInputChecker
- ◦org.apache.hadoop.fs.ChecksumFileSystem.ChecksumFSInputChecker
首先看抽象的輸入流類,實現的源代碼如下所示:
- package org.apache.hadoop.fs;
- import java.io.*;
- public abstract class FSInputStream extends InputStream implements Seekable, PositionedReadable {
- /**
- * 從給定的偏移位置pos開始搜索,下一次讀取就從該位置開始讀取。
- */
- public abstract void seek(long pos) throws IOException;
- /**
- * 返回文件的當前前向偏移位置
- */
- public abstract long getPos() throws IOException;
- /**
- * 搜索不同的文件數據的拷貝,如果搜索到則返回true,否則返回false
- */
- public abstract boolean seekToNewSource(long targetPos) throws IOException;
- public int read(long position, byte[] buffer, int offset, int length)
- throws IOException {
- synchronized (this) {
- long oldPos = getPos();
- int nread = -1;
- try {
- seek(position);
- nread = read(buffer, offset, length);
- } finally {
- seek(oldPos);
- }
- return nread;
- }
- }
- public void readFully(long position, byte[] buffer, int offset, int length)
- throws IOException {
- int nread = 0;
- while (nread < length) {
- int nbytes = read(position+nread, buffer, offset+nread, length-nread);
- if (nbytes < 0) {
- throw new EOFException("End of file reached before reading fully.");
- }
- nread += nbytes;
- }
- }
- public void readFully(long position, byte[] buffer)
- throws IOException {
- readFully(position, buffer, 0, buffer.length);
- }
- }
輸出流類
與輸出流相關的接口和類的繼承層次關係如下所示:
- ◦java.io.OutputStream(implements java.io.Closeable, java.io.Flushable)
- ◦java.io.FilterOutputStream
- ◦java.io.DataOutputStream
- ◦org.apache.hadoop.fs.FSDataOutputStream(implements org.apache.hadoop.fs.Syncable)
FSDataOutputStream輸出流類內部實現了一個基於位置的緩衝輸出流類PositionCache,該類的實現如下所示:
- /**
- * 該PositionCache類是一個緩衝流類,對輸出流的位置進行緩存。
- */
- rivate static class PositionCache extends FilterOutputStream {
- private FileSystem.Statistics statistics;
- long position; // 緩存中輸出流對象out的偏移位置
- public PositionCache(OutputStream out, FileSystem.Statistics stats, long pos) throws IOException {
- super(out); // 初始化從基類繼承下來的OutputStream out對象
- statistics = stats;
- position = pos;
- }
- public void write(int b) throws IOException {
- out.write(b); // 向輸出流對象out中寫入一個字節b
- position++; // 緩存中輸出流的偏移位置加1
- if (statistics != null) {
- statistics.incrementBytesWritten(1); // 更新文件系統的統計數據對象
- }
- }
- public void write(byte b[], int off, int len) throws IOException {
- out.write(b, off, len); //
- position += len; // 更新緩存
- if (statistics != null) {
- statistics.incrementBytesWritten(len); // 更新文件統計數據
- }
- }
- public long getPos() throws IOException {
- return position; // 返回輸出流中當前待寫入位置
- }
- public void close() throws IOException {
- out.close(); // 關閉輸出流
- }
創建一個PositionCache緩衝流對象以後,可以向該文件輸出緩衝流中寫入相關的數據,作爲緩衝使用,其中相關數據包括:文件系統(FileSystem)統計信息FileSystem.Statistics、當前待寫入流的位置。每當需要向文件系統中寫入數據,都會從PositionCache緩衝流中獲取到一個寫入位置(也就是,要從流中的該位置開始寫入)。
FSDataOutputStream輸出流類的通過一個PositionCache緩衝流來構造一個FSDataOutputStream輸出流對象:
- public FSDataOutputStream(OutputStream out, FileSystem.Statistics stats, long startPosition) throws IOException {
- super(new PositionCache(out, stats, startPosition)); // 緩衝了out流,緩存的數據對象包括stats、startPosition
- wrappedStream = out;
- }
實例化FSDataOutputStream類,能夠獲取到當前用於要寫入數據的流對象,也就是該類包裝的輸出流類OutputStream類型屬性wrappedStream,其中wrappedStream就是一個OutputStream。
基於上面構造方法,缺省設置一些參數,得到如下兩個重載的構造方法:
- @Deprecated
- public FSDataOutputStream(OutputStream out) throws IOException {
- this(out, null);
- }
- public FSDataOutputStream(OutputStream out, FileSystem.Statistics stats)
- throws IOException {
- this(out, stats, 0);
- }
該FSDataOutputStream類實現的方法如下所示:
- public long getPos() throws IOException {
- return ((PositionCache)out).getPos();
- }
- public void close() throws IOException {
- out.close();
- }
- public OutputStream getWrappedStream() {
- return wrappedStream;
- }
- /** wrappedStream是必須實現Syncable接口的流類,強制同步全部緩衝區 */
- public void sync() throws IOException {
- if (wrappedStream instanceof Syncable) {
- ((Syncable)wrappedStream).sync();
- }
- }
其中,sync方法表示實現同步流緩衝區的操作,使得緩衝的流對象與原始輸出流對象同步,保證寫入數據的正確性。