這裏,繼續對FsShell類中一些命令進行閱讀分析,主要是看與拷貝文件有關的幾個命令。
- cp命令
該命令實現對文件的拷貝操作,並且支持在不同的文件系統之間進行文件的拷貝。拷貝文件涉及的操作比較複雜,核心拷貝操作還是調用了org.apache.hadoop.fs.FileUtil類的copy方法實現的。 先看該類中定義的其中一個copy方法的實現:
- private int copy(String argv[], Configuration conf) throws IOException {
- int i = 0;
- int exitCode = 0;
- String cmd = argv[i++];
- String dest = argv[argv.length-1]; // 命令行中最後一個參數
- // 若指定了多個輸入源文件,則最後一個參數必須是一個目錄
- if (argv.length > 3) {
- Path dst = new Path(dest);
- if (!fs.isDirectory(dst)) { // 最後一個參數必須是目錄
- throw new IOException("When copying multiple files, " + "destination " + dest + " should be a directory.");
- }
- }
- // 循環對每一個文件進行拷貝操作
- for (; i < argv.length - 1; i++) {
- try {
- copy(argv[i], dest, conf); // 將文件argv[i]拷貝到dest目錄中
- } catch (RemoteException e) {
- // 捕獲命令執行發生的異常信息
- exitCode = -1;
- try {
- String[] content;
- content = e.getLocalizedMessage().split("/n");
- System.err.println(cmd.substring(1) + ": " +
- content[0]);
- } catch (Exception ex) {
- System.err.println(cmd.substring(1) + ": " +
- ex.getLocalizedMessage());
- }
- } catch (IOException e) {
- // 捕獲IO異常信息
- exitCode = -1;
- System.err.println(cmd.substring(1) + ": " + e.getLocalizedMessage());
- }
- }
- return exitCode;
- }
該命令的實現與mv命令類似,這裏調用了一個重載的copy命令,實現對一個文件執行拷貝操作。該重載的拷貝方法如下所示:
- void copy(String srcf, String dstf, Configuration conf) throws IOException {
- Path srcPath = new Path(srcf); // 構造Path
- FileSystem srcFs = srcPath.getFileSystem(getConf()); // 獲取到srcPath所在的文件系統srcFs
- Path dstPath = new Path(dstf);
- FileSystem dstFs = dstPath.getFileSystem(getConf()); // 獲取到dstPath所在的文件系統dstFs
- Path [] srcs = FileUtil.stat2Paths(srcFs.globStatus(srcPath), srcPath); // 獲取到srcFs中滿足srcPath模式的FileStatus[]並轉換成爲Path[]
- if (srcs.length > 1 && !dstFs.isDirectory(dstPath)) {
- throw new IOException("When copying multiple files, " + "destination should be a directory.");
- }
- for(int i=0; i<srcs.length; i++) { // 循環拷貝操作
- FileUtil.copy(srcFs, srcs[i], dstFs, dstPath, false, conf); // 調用FileUtil類的拷貝方法copy完成文件在srcFs與dstFs文件系統之間拷貝文件的操作
- }
- }
現在,我們開始追蹤 org.apache.hadoop.fs.FileUtil類的copy方法,看一看拷貝到底是如何實現的。FileUtil類中定義了多個重載的拷貝方法copy,我們只從FsShell類中調用的copy方法開始追蹤其實現。上面調用的FileUtil類的copy實現如下所示:
- public static boolean copy(FileSystem srcFS, Path src,
- FileSystem dstFS, Path dst,
- boolean deleteSource,
- Configuration conf) throws IOException {
- return copy(srcFS, src, dstFS, dst, deleteSource, true, conf); // 調用了一個重載的copy方法實現文件在srcFS與dstFS之間進行復制
- }
看重載的copy方法的實現,如下所示:
- public static boolean copy(FileSystem srcFS, Path src,
- FileSystem dstFS, Path dst,
- boolean deleteSource,
- boolean overwrite,
- Configuration conf) throws IOException {
- dst = checkDest(src.getName(), dstFS, dst, overwrite); // 檢查目的文件系統dstFS中dst目錄是否合法(參照src)
- if (srcFS.getFileStatus(src).isDir()) { // 若源文件系統srcFS中src是目錄
- checkDependencies(srcFS, src, dstFS, dst); // 檢查文件依賴性:如果srcFS=dstFS,並且dst不是src的子目錄,檢查通過;如果srcFS與dstFS不是同一個文件系統,依賴性檢查通過
- if (!dstFS.mkdirs(dst)) { // 在dstFS中創建dst目錄,準備向其中拷貝數據
- return false;
- }
- FileStatus contents[] = srcFS.listStatus(src); // 獲取srcFS中src目錄下的文件列表
- for (int i = 0; i < contents.length; i++) { // 分治思想:分治後執行遞歸拷貝文件操作
- copy(srcFS, contents[i].getPath(), dstFS,
- new Path(dst, contents[i].getPath().getName()),
- deleteSource, overwrite, conf); // 遞歸調用
- }
- } else if (srcFS.isFile(src)) { // 遞歸出口(如果src是一個普通文件)
- InputStream in=null;
- OutputStream out = null;
- try {
- in = srcFS.open(src); // 打開srcFS中的src文件,並返回輸入流對象
- out = dstFS.create(dst, overwrite); // 在目的文件系統dstFS中創建dst文件,並返回輸出流,等待寫入
- IOUtils.copyBytes(in, out, conf, true); // 調用:通過調用IOUtils類的copyBytes方法實現流式拷貝
- } catch (IOException e) {
- IOUtils.closeStream(out); // 關閉out
- IOUtils.closeStream(in); // 關閉in
- throw e;
- }
- } else {
- throw new IOException(src.toString() + ": No such file or directory");
- }
- if (deleteSource) { // 如果設置了拷貝完成後刪除源文件選項
- return srcFS.delete(src, true); // 刪除源文件系統srcFS的源文件src
- } else {
- return true;
- }
- }
IOUtils類中實現了Hadoop文件系統中文件的 流式拷貝操作,我們追蹤該工具類的copyBytes方法,分析實現的過程。該方法如下所示:
- /**
- * 從一個流拷貝到另一個流中
- */
- public static void copyBytes(InputStream in, OutputStream out, Configuration conf, boolean close)
- throws IOException {
- copyBytes(in, out, conf.getInt("io.file.buffer.size", 4096), close); // 調用:重載的copyBytes方法實現流式拷貝
- }
我們看重載流式拷貝實現方法copyBytes,如下所示:
- public static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close) throws IOException {
- PrintStream ps = out instanceof PrintStream ? (PrintStream)out : null; // 使用PrintStream爲out流增加便捷功能
- byte buf[] = new byte[buffSize]; // 字節緩衝區
- try {
- int bytesRead = in.read(buf); // 從輸入流in讀取bytesRead個字節到buf緩衝區中
- while (bytesRead >= 0) { // 確實讀取到了字節
- out.write(buf, 0, bytesRead); // 將從in中讀取到的字節,通過buf緩衝區寫入到輸出流out中
- if ((ps != null) && ps.checkError()) { // 如果ps=(PrintStream)out,測試內部標誌,並自動刷新
- throw new IOException("Unable to write to output stream.");
- }
- bytesRead = in.read(buf); // 繼續從in讀取字節
- }
- } finally {
- if(close) {
- out.close(); // 關閉out
- in.close(); // 關閉in
- }
- }
- }
上面在從InputStream in拷貝到OutputStream out中的過程中,使用了更加高效的PrintStream流類,它能夠爲OutputStream增加方便打印各種數據值的表示形式,而且,它不會拋出IO異常,而是將流式拷貝過程中發生的異常,設置爲通過調用checkError方法來檢測內部的標誌。另外,它還可以實現自動刷新,在向輸出流中寫入字節(通過字節緩衝區)之後,自動刷新。
cp命令的具體實現都在上面進行分析了,應該能夠理解在Hadoop中如何在不同文件系統之間執行流式拷貝文件的過程。
- copyFromLocal命令
該命令實現了從本地文件系統拷貝文件的操作。實現方法爲,如下所示:
- /**
- * 從本地文件系統(srcs在本地文件系統中)拷貝srcs到目的文件系統(對應Path爲dstf)
- */
- void copyFromLocal(Path[] srcs, String dstf) throws IOException {
- Path dstPath = new Path(dstf);
- FileSystem dstFs = dstPath.getFileSystem(getConf()); // 獲取到目的文件系統dstFs
- if (srcs.length == 1 && srcs[0].toString().equals("-")) // 如果只指定了一個參數“-”
- copyFromStdin(dstPath, dstFs); // 調用:從標準輸入流中進行流式拷貝操作
- else // 否則
- dstFs.copyFromLocalFile(false, false, srcs, dstPath); // 調用目的文件系統dstFs的copyFromLocalFile方法執行拷貝操作
- }
我們關注一下copyFromStdin方法拷貝的實現,如下所示:
- private void copyFromStdin(Path dst, FileSystem dstFs) throws IOException {
- if (dstFs.isDirectory(dst)) { // 如果目的文件是目錄,不支持源爲標準輸入流的情況
- throw new IOException("When source is stdin, destination must be a file.");
- }
- if (dstFs.exists(dst)) { // 如果目的文件系統dstFs中存在文件dst,出錯
- throw new IOException("Target " + dst.toString() + " already exists.");
- }
- FSDataOutputStream out = dstFs.create(dst); // 滿足拷貝要求,執行流式拷貝操作
- try {
- IOUtils.copyBytes(System.in, out, getConf(), false); // 調用IOUtils類的copyBytes方法實現,前面已經分析過拷貝過程
- }
- finally {
- out.close(); // 拷貝完成,關閉輸出流out
- }
- }
再看一下,如果指定的是待拷貝的文件源不是標準輸入流的情況,文件系統FileSystem是如何實現拷貝操作的。實現的方法copyFromLocalFile如下所示:
- /**
- * 將本地的srcs,拷貝到目的文件系統中的dst
- * delSrc指示了拷貝文件完成之後,是否刪除源文件srcs
- */
- public void copyFromLocalFile(boolean delSrc, boolean overwrite, Path[] srcs, Path dst)
- throws IOException {
- Configuration conf = getConf();
- FileUtil.copy(getLocal(conf), srcs, this, dst, delSrc, overwrite, conf); // 調用FileUtil工具類實現拷貝操作
- }
關於FileUtil的copy方法,前面已經詳細分析過,不再累述。
像moveFromLocal、moveFromLocal、copyToLocal、moveToLocal、copyMergeToLocal等命令的實現都非常類似,也不做過多的解釋了。