Hadoop-0.20.0源代碼分析(08)

這裏,繼續對FsShell類中一些命令進行閱讀分析,主要是看與拷貝文件有關的幾個命令。

  • cp命令

該命令實現對文件的拷貝操作,並且支持在不同的文件系統之間進行文件的拷貝。拷貝文件涉及的操作比較複雜,核心拷貝操作還是調用了org.apache.hadoop.fs.FileUtil類的copy方法實現的。 先看該類中定義的其中一個copy方法的實現:

[java] view plaincopy
  1. private int copy(String argv[], Configuration conf) throws IOException {  
  2.   int i = 0;  
  3.   int exitCode = 0;  
  4.   String cmd = argv[i++];    
  5.   String dest = argv[argv.length-1]; // 命令行中最後一個參數  
  6.   // 若指定了多個輸入源文件,則最後一個參數必須是一個目錄  
  7.   if (argv.length > 3) {  
  8.     Path dst = new Path(dest);  
  9.     if (!fs.isDirectory(dst)) { // 最後一個參數必須是目錄  
  10.       throw new IOException("When copying multiple files, " + "destination " + dest + " should be a directory.");  
  11.     }  
  12.   }  
  13.   // 循環對每一個文件進行拷貝操作  
  14.   for (; i < argv.length - 1; i++) {  
  15.     try {  
  16.       copy(argv[i], dest, conf); // 將文件argv[i]拷貝到dest目錄中  
  17.     } catch (RemoteException e) {  
  18.       // 捕獲命令執行發生的異常信息  
  19.       exitCode = -1;  
  20.       try {  
  21.         String[] content;  
  22.         content = e.getLocalizedMessage().split("/n");  
  23.         System.err.println(cmd.substring(1) + ": " +  
  24.                            content[0]);  
  25.       } catch (Exception ex) {  
  26.         System.err.println(cmd.substring(1) + ": " +  
  27.                            ex.getLocalizedMessage());  
  28.       }  
  29.     } catch (IOException e) {  
  30.       // 捕獲IO異常信息  
  31.       exitCode = -1;  
  32.       System.err.println(cmd.substring(1) + ": " + e.getLocalizedMessage());  
  33.     }  
  34.   }  
  35.   return exitCode;  
  36. }  

該命令的實現與mv命令類似,這裏調用了一個重載的copy命令,實現對一個文件執行拷貝操作。該重載的拷貝方法如下所示:

[java] view plaincopy
  1. void copy(String srcf, String dstf, Configuration conf) throws IOException {  
  2.   Path srcPath = new Path(srcf); // 構造Path  
  3.   FileSystem srcFs = srcPath.getFileSystem(getConf()); // 獲取到srcPath所在的文件系統srcFs  
  4.   Path dstPath = new Path(dstf);  
  5.   FileSystem dstFs = dstPath.getFileSystem(getConf()); // 獲取到dstPath所在的文件系統dstFs  
  6.   Path [] srcs = FileUtil.stat2Paths(srcFs.globStatus(srcPath), srcPath); // 獲取到srcFs中滿足srcPath模式的FileStatus[]並轉換成爲Path[]  
  7.   if (srcs.length > 1 && !dstFs.isDirectory(dstPath)) {   
  8.     throw new IOException("When copying multiple files, " + "destination should be a directory.");  
  9.   }  
  10.   for(int i=0; i<srcs.length; i++) { // 循環拷貝操作  
  11.     FileUtil.copy(srcFs, srcs[i], dstFs, dstPath, false, conf); // 調用FileUtil類的拷貝方法copy完成文件在srcFs與dstFs文件系統之間拷貝文件的操作  
  12.   }  
  13. }  

現在,我們開始追蹤 org.apache.hadoop.fs.FileUtil類的copy方法,看一看拷貝到底是如何實現的。FileUtil類中定義了多個重載的拷貝方法copy,我們只從FsShell類中調用的copy方法開始追蹤其實現。上面調用的FileUtil類的copy實現如下所示:

[java] view plaincopy
  1. public static boolean copy(FileSystem srcFS, Path src,   
  2.                            FileSystem dstFS, Path dst,   
  3.                            boolean deleteSource,  
  4.                            Configuration conf) throws IOException {   
  5.   return copy(srcFS, src, dstFS, dst, deleteSource, true, conf); // 調用了一個重載的copy方法實現文件在srcFS與dstFS之間進行復制  
  6. }  

看重載的copy方法的實現,如下所示:

[java] view plaincopy
  1. public static boolean copy(FileSystem srcFS, Path src,   
  2.                            FileSystem dstFS, Path dst,   
  3.                            boolean deleteSource,  
  4.                            boolean overwrite,  
  5.                            Configuration conf) throws IOException {  
  6.   dst = checkDest(src.getName(), dstFS, dst, overwrite); // 檢查目的文件系統dstFS中dst目錄是否合法(參照src)  
  7.   if (srcFS.getFileStatus(src).isDir()) { // 若源文件系統srcFS中src是目錄  
  8.     checkDependencies(srcFS, src, dstFS, dst); // 檢查文件依賴性:如果srcFS=dstFS,並且dst不是src的子目錄,檢查通過;如果srcFS與dstFS不是同一個文件系統,依賴性檢查通過  
  9.     if (!dstFS.mkdirs(dst)) { // 在dstFS中創建dst目錄,準備向其中拷貝數據  
  10.       return false;  
  11.     }  
  12.     FileStatus contents[] = srcFS.listStatus(src); // 獲取srcFS中src目錄下的文件列表  
  13.     for (int i = 0; i < contents.length; i++) { // 分治思想:分治後執行遞歸拷貝文件操作  
  14.       copy(srcFS, contents[i].getPath(), dstFS,   
  15.            new Path(dst, contents[i].getPath().getName()),  
  16.            deleteSource, overwrite, conf); // 遞歸調用  
  17.     }  
  18.   } else if (srcFS.isFile(src)) { // 遞歸出口(如果src是一個普通文件)  
  19.     InputStream in=null;  
  20.     OutputStream out = null;  
  21.     try {  
  22.       in = srcFS.open(src); // 打開srcFS中的src文件,並返回輸入流對象  
  23.       out = dstFS.create(dst, overwrite); // 在目的文件系統dstFS中創建dst文件,並返回輸出流,等待寫入  
  24.       IOUtils.copyBytes(in, out, conf, true); // 調用:通過調用IOUtils類的copyBytes方法實現流式拷貝  
  25.     } catch (IOException e) {  
  26.       IOUtils.closeStream(out); // 關閉out  
  27.       IOUtils.closeStream(in); // 關閉in  
  28.       throw e;  
  29.     }  
  30.   } else {  
  31.     throw new IOException(src.toString() + ": No such file or directory");  
  32.   }  
  33.   if (deleteSource) { // 如果設置了拷貝完成後刪除源文件選項  
  34.     return srcFS.delete(src, true); // 刪除源文件系統srcFS的源文件src  
  35.   } else {  
  36.     return true;  
  37.   }  
  38.   
  39. }  

IOUtils類中實現了Hadoop文件系統中文件的 流式拷貝操作,我們追蹤該工具類的copyBytes方法,分析實現的過程。該方法如下所示:

[java] view plaincopy
  1. /** 
  2.  * 從一個流拷貝到另一個流中 
  3.  */  
  4. public static void copyBytes(InputStream in, OutputStream out, Configuration conf, boolean close)  
  5.   throws IOException {  
  6.   copyBytes(in, out, conf.getInt("io.file.buffer.size"4096),  close); // 調用:重載的copyBytes方法實現流式拷貝  
  7. }  

我們看重載流式拷貝實現方法copyBytes,如下所示:

[java] view plaincopy
  1. public static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close) throws IOException {  
  2.   PrintStream ps = out instanceof PrintStream ? (PrintStream)out : null// 使用PrintStream爲out流增加便捷功能  
  3.   byte buf[] = new byte[buffSize]; // 字節緩衝區  
  4.   try {  
  5.     int bytesRead = in.read(buf); // 從輸入流in讀取bytesRead個字節到buf緩衝區中  
  6.     while (bytesRead >= 0) { // 確實讀取到了字節  
  7.       out.write(buf, 0, bytesRead); // 將從in中讀取到的字節,通過buf緩衝區寫入到輸出流out中  
  8.       if ((ps != null) && ps.checkError()) { // 如果ps=(PrintStream)out,測試內部標誌,並自動刷新  
  9.         throw new IOException("Unable to write to output stream.");  
  10.       }  
  11.       bytesRead = in.read(buf); // 繼續從in讀取字節  
  12.     }  
  13.   } finally {  
  14.     if(close) {  
  15.       out.close(); // 關閉out  
  16.       in.close(); // 關閉in  
  17.     }  
  18.   }  
  19. }  

上面在從InputStream in拷貝到OutputStream out中的過程中,使用了更加高效的PrintStream流類,它能夠爲OutputStream增加方便打印各種數據值的表示形式,而且,它不會拋出IO異常,而是將流式拷貝過程中發生的異常,設置爲通過調用checkError方法來檢測內部的標誌。另外,它還可以實現自動刷新,在向輸出流中寫入字節(通過字節緩衝區)之後,自動刷新。

cp命令的具體實現都在上面進行分析了,應該能夠理解在Hadoop中如何在不同文件系統之間執行流式拷貝文件的過程。

  • copyFromLocal命令

該命令實現了從本地文件系統拷貝文件的操作。實現方法爲,如下所示:

[java] view plaincopy
  1. /** 
  2.  * 從本地文件系統(srcs在本地文件系統中)拷貝srcs到目的文件系統(對應Path爲dstf) 
  3.  */  
  4. void copyFromLocal(Path[] srcs, String dstf) throws IOException {  
  5.   Path dstPath = new Path(dstf);  
  6.   FileSystem dstFs = dstPath.getFileSystem(getConf()); // 獲取到目的文件系統dstFs  
  7.   if (srcs.length == 1 && srcs[0].toString().equals("-")) // 如果只指定了一個參數“-”  
  8.     copyFromStdin(dstPath, dstFs); // 調用:從標準輸入流中進行流式拷貝操作  
  9.   else // 否則  
  10.     dstFs.copyFromLocalFile(falsefalse, srcs, dstPath); // 調用目的文件系統dstFs的copyFromLocalFile方法執行拷貝操作  
  11. }  
  

我們關注一下copyFromStdin方法拷貝的實現,如下所示:

[java] view plaincopy
  1. private void copyFromStdin(Path dst, FileSystem dstFs) throws IOException {  
  2.   if (dstFs.isDirectory(dst)) { // 如果目的文件是目錄,不支持源爲標準輸入流的情況  
  3.     throw new IOException("When source is stdin, destination must be a file.");  
  4.   }  
  5.   if (dstFs.exists(dst)) { // 如果目的文件系統dstFs中存在文件dst,出錯  
  6.     throw new IOException("Target " + dst.toString() + " already exists.");  
  7.   }  
  8.   FSDataOutputStream out = dstFs.create(dst); // 滿足拷貝要求,執行流式拷貝操作  
  9.   try {  
  10.     IOUtils.copyBytes(System.in, out, getConf(), false); // 調用IOUtils類的copyBytes方法實現,前面已經分析過拷貝過程  
  11.   }   
  12.   finally {  
  13.     out.close(); // 拷貝完成,關閉輸出流out  
  14.   }  
  15. }  

再看一下,如果指定的是待拷貝的文件源不是標準輸入流的情況,文件系統FileSystem是如何實現拷貝操作的。實現的方法copyFromLocalFile如下所示:

[java] view plaincopy
  1. /** 
  2.  * 將本地的srcs,拷貝到目的文件系統中的dst 
  3.  * delSrc指示了拷貝文件完成之後,是否刪除源文件srcs 
  4.  */  
  5. public void copyFromLocalFile(boolean delSrc, boolean overwrite, Path[] srcs, Path dst)  
  6.   throws IOException {  
  7.   Configuration conf = getConf();  
  8.   FileUtil.copy(getLocal(conf), srcs, this, dst, delSrc, overwrite, conf); // 調用FileUtil工具類實現拷貝操作  
  9. }  

關於FileUtil的copy方法,前面已經詳細分析過,不再累述。

像moveFromLocal、moveFromLocal、copyToLocal、moveToLocal、copyMergeToLocal等命令的實現都非常類似,也不做過多的解釋了。


發佈了4 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章