以文件流作为一个切面,阅读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方法表示实现同步流缓冲区的操作,使得缓冲的流对象与原始输出流对象同步,保证写入数据的正确性。