HDFS读文件的简要代码如下:

public static void main(String[] args) throws Exception {

	InputStream input;
	OutputStream output;
		
	Configuration conf = new Configuration();
	conf.set("fs.defaultFS", "hdfs://hadoop66:8020");
	FileSystem fs = FileSystem.get(conf);
	output = new FileOutputStream("D:/1.txt");

	input = fs.open(new Path("/user/1.txt"));// new DFSInputStream
	byte[] buffer = new byte[1024];
	int len = 0;

	while ((len = input.read(buffer)) != -1) {
		output.write(buffer, 0, len);
	}

	input.close();
	output.flush();
	output.close();
}

整个过程也比较复杂,我画了一个简要的源码分析流程图,来展示整个操作的过程,如下:

Python操作hdfs写入并读取一个文件 hdfs读取文件代码_实例化

流程和源码简要分析如下:

1.FileSystem通过实例化DistributedFileSystem,DistriburedFileSystem内部有一个执行远程RPC调用的对象DFSClient dfs。通过调用dfs.open(getPathName§, bufferSize, verifyChecksum)来实例化一个DFSInputStream

2.DFSInputStream通过调用openInfo(false),然后调用dfsClient.getLocatedBlocks(src, 0),来连接NameNode获取文件的块信息。

3.NameNode的NameNodeRpcServer接收到请求后会检查是否具有读权限,文件是否存在等一系列操作,然后返回LocatedBlocks,包括了Block信息和DataNode的位置。

4.返回DFSInputStream后,然后调用read(buf)方法读取数据,第一次会调用BlockSeekTo(pos)来实例化一个BlockReader,BlockReader是一个接口,有几个实现类,这里用到的是BlockReaderRemote2,暂时还没有弄明白各个BlockReader之间的区别。

5.通过调用BlockReaderRemote2.newBlockReader来实例化BlockReader,这个过程会通过new Sender(out).readBlock来连接DataNode

6.DataNode接收到Socket的请求后,会创建一个DataXceuver来响应请求,然后通过判断op的类型来进行相应的操作。

7.通过new Sender(out).readBlock发送出去的op类型为READ_BLOCK,然后调用opReadBlock来进行相应的操作,然后实例化一个BlockSender来处理相应的请求。

8.实例化BlockSender对象后,给客户端返回成功信息

9.通过调用BlockReaderRemote2.read读取数据

10.BlockReaderRemote2.read方法会从curDataSlice读取数据,curDataSlice是一个ByteBuff缓冲对象,如果有数据,则直接冲里面取走buff缓冲区大小的数据。

11.如果没有,则会调用readNextPacket()从DataNode读取一个Packet来处理。

12.读取到buff字节大小的数据时,会返回读取数据的大小

这里还有一个疑问,就是上面只是一个块的读写过程,但一个文件会有多个块,关于怎么切换块的问题,我详细查找了一下代码原来是在这里有详细的讲解:

protected synchronized int readWithStrategy(ReaderStrategy strategy, int off, int len) throws IOException {
    dfsClient.checkOpen();
    if (closed.get()) {
      throw new IOException("Stream closed");
    }
    Map<ExtendedBlock,Set<DatanodeInfo>> corruptedBlockMap = new HashMap<>();
    failures = 0;
    if (pos < getFileLength()) {//获取文件的长度
      int retries = 2;
      while (retries > 0) {
        try {
     		//每次读取都会判断是否要切换块
          if (pos > blockEnd || currentNode == null) {
            currentNode = blockSeekTo(pos);	//新建BlockReader
          }
          int realLen = (int) Math.min(len, (blockEnd - pos + 1L));
          synchronized(infoLock) {
            if (locatedBlocks.isLastBlockComplete()) {
              realLen = (int) Math.min(realLen,
                  locatedBlocks.getFileLength() - pos);
            }
          }
          int result = readBuffer(strategy, off, realLen, corruptedBlockMap);

          if (result >= 0) {
            pos += result;
          } else {
            throw new IOException("Unexpected EOS from the reader");
          }
          if (dfsClient.stats != null) {
            dfsClient.stats.incrementBytesRead(result);
          }
          return result;
        } catch (ChecksumException ce) {
          throw ce;
        } catch (IOException e) {
          if (retries == 1) {
            DFSClient.LOG.warn("DFS Read", e);
          }
          blockEnd = -1;
          if (currentNode != null) { addToDeadNodes(currentNode); }
          if (--retries == 0) {
            throw e;
          }
        } finally {
          reportCheckSumFailure(corruptedBlockMap,
              currentLocatedBlock.getLocations().length);
        }
      }
    }
    return -1;
  }

核心代码在这里:

if (pos > blockEnd || currentNode == null) {
		currentNode = blockSeekTo(pos);	//新建BlockReader
	}

每次读取的时候,都会判断读取的位置是否超过块的位置,如果超过,则根据pos位置获取下一个块的位置开始读取,直到最后一个块结束