複製鏈接
請複製以下鏈接發送給好友

hdfs

鎖定
Hadoop分佈式文件系統(HDFS)是指被設計成適合運行在通用硬件(commodity hardware)上的分佈式文件系統(Distributed File System)。它和現有的分佈式文件系統有很多共同點。但同時,它和其他的分佈式文件系統的區別也是很明顯的。HDFS是一個高度容錯性的系統,適合部署在廉價的機器上。HDFS能提供高吞吐量的數據訪問,非常適合大規模數據集上的應用。HDFS放寬了一部分POSIX約束,來實現流式讀取文件系統數據的目的。HDFS在最開始是作為Apache Nutch搜索引擎項目的基礎架構而開發的。HDFS是Apache Hadoop Core項目的一部分。
HDFS有着高容錯性fault-tolerant)的特點,並且設計用來部署在低廉的(low-cost)硬件上。而且它提供高吞吐量(high throughput)來訪問應用程序的數據,適合那些有着超大數據集(large data set)的應用程序。HDFS放寬了(relax)POSIX的要求(requirements)這樣可以實現流的形式訪問(streaming access)文件系統中的數據。
全    稱
Hadoop Distributed File System
簡    稱
HDFS
實    質
分佈式文件系統
作    用
作為Apache Nutch的基礎架構
特    點
高容錯性
適    用
大規模數據集

hdfs體系結構

HDFS採用了主從(Master/Slave)結構模型,一個HDFS集羣是由一個NameNode和若干個DataNode組成的。其中NameNode作為主服務器,管理文件系統的命名空間和客户端對文件的訪問操作;集羣中的DataNode管理存儲的數據。

hdfs特點和目標

硬件故障
硬件故障是常態,而不是異常。整個HDFS系統將由數百或數千個存儲着文件數據片段的服務器組成。實際上它裏面有非常巨大的組成部分,每一個組成部分都很可能出現故障,這就意味着HDFS裏的總是有一些部件是失效的,因此,故障的檢測和自動快速恢復是HDFS一個很核心的設計目標
數據訪問
運行在HDFS之上的應用程序必須流式地訪問它們的數據集,它不是運行在普通文件系統之上的普通程序。HDFS被設計成適合批量處理的,而不是用户交互式的。重點是在數據吞吐量,而不是數據訪問的反應時間,POSIX的很多硬性需求對於HDFS應用都是非必須的,去掉POSIX一小部分關鍵語義可以獲得更好的數據吞吐率
大數據集
運行在HDFS之上的程序有很大量的數據集。典型的HDFS文件大小是GB到TB的級別。所以,HDFS被調整成支持大文件。它應該提供很高的聚合數據帶寬,一個集羣中支持數百個節點,一個集羣中還應該支持千萬級別的文件。
簡單一致性模型
大部分的HDFS程序對文件操作需要的是一次寫多次讀取的操作模式。一個文件一旦創建、寫入、關閉之後就不需要修改了。這個假定簡單化了數據一致的問題,並使高吞吐量的數據訪問變得可能。一個Map-Reduce程序或者網絡爬蟲程序都可以完美地適合這個模型。
移動計算比移動數據更經濟
在靠近計算數據所存儲的位置來進行計算是最理想的狀態,尤其是在數據集特別巨大的時候。這樣消除了網絡的擁堵,提高了系統的整體吞吐量。一個假定就是遷移計算到離數據更近的位置比將數據移動到程序運行更近的位置要更好。HDFS提供了接口,來讓程序將自己移動到離數據存儲更近的位置。
異構軟硬件平台間的可移植性
HDFS被設計成可以簡便地實現平台間的遷移,這將推動需要大數據集的應用更廣泛地採用HDFS作為平台。
名字節點和數據節點
HDFS是一個主從結構,一個HDFS集羣是由一個名字節點,它是一個管理文件命名空間和調節客户端訪問文件的主服務器,當然還有一些數據節點,通常是一個節點一個機器,它來管理對應節點的存儲。HDFS對外開放文件命名空間並允許用户數據以文件形式存儲。
內部機制是將一個文件分割成一個或多個塊,這些塊被存儲在一組數據節點中。名字節點用來操作文件命名空間的文件或目錄操作,如打開,關閉,重命名等等。它同時確定塊與數據節點的映射。數據節點負責來自文件系統客户的讀寫請求。數據節點同時還要執行塊的創建,刪除,和來自名字節點的塊複製指令。
hdfs的大數據集 hdfs的大數據集
名字節點和數據節點都是運行在普通的機器之上的軟件,機器典型的都是GNU/Linux,HDFS是用java編寫的,任何支持java的機器都可以運行名字節點或數據節點,利用java語言的超輕便性,很容易將HDFS部署到大範圍的機器上。典型的部署是由一個專門的機器來運行名字節點軟件,集羣中的其他每台機器運行一個數據節點實例。體系結構不排斥在一個機器上運行多個數據節點的實例,但是實際的部署不會有這種情況。
集羣中只有一個名字節點極大地簡單化了系統的體系結構。名字節點是仲裁者和所有HDFS元數據的倉庫,用户的實際數據不經過名字節點。

hdfs文件命名空間

HDFS支持傳統的繼承式的文件組織結構。一個用户或一個程序可以創建目錄,存儲文件到很多目錄之中。文件系統的名字空間層次和其他的文件系統相似。可以創建、移動文件,將文件從一個目錄移動到另外一個,或重命名。HDFS還沒有實現用户的配額和訪問控制。HDFS還不支持硬鏈接軟鏈接。然而,HDFS結構不排斥在將來實現這些功能。
名字節點維護文件系統的命名空間,任何文件命名空間的改變和或屬性都被名字節點記錄。應用程序可以指定文件的副本數,文件的副本數被稱作文件的複製因子,這些信息由命名空間來負責存儲。

hdfs數據複製

HDFS設計成能可靠地在集羣中大量機器之間存儲大量的文件,它以塊序列的形式存儲文件。文件中除了最後一個塊,其他塊都有相同的大小。屬於文件的塊為了故障容錯而被複制。塊的大小和複製數是以文件為單位進行配置的,應用可以在文件創建時或者之後修改複製因子。HDFS中的文件是一次寫的,並且任何時候都只有一個寫操作。
名字節點負責處理所有的塊複製相關的決策。它週期性地接受集羣中數據節點的心跳和塊報告。一個心跳的到達表示這個數據節點是正常的。一個塊報告包括該數據節點上所有塊的列表。
hdfs的數據複製 hdfs的數據複製
副本位置:第一小步
塊副本存放位置的選擇嚴重影響HDFS的可靠性和性能。副本存放位置的優化是HDFS區分於其他分佈式文件系統的的特徵,這需要精心的調節和大量的經驗。機架敏感的副本存放策略是為了提高數據的可靠性,可用性網絡帶寬利用率。副本存放策略的實現是這個方向上比較原始的方式。短期的實現目標是要把這個策略放在生產環境下驗證,瞭解更多它的行為,為以後測試研究更精緻的策略打好基礎。
HDFS運行在跨越大量機架的集羣之上。兩個不同機架上的節點是通過交換機實現通信的,在大多數情況下,相同機架上機器間的網絡帶寬優於在不同機架上的機器。
在開始的時候,每一個數據節點自檢它所屬的機架id,然後在向名字節點註冊的時候告知它的機架id。HDFS提供接口以便很容易地掛載檢測機架標示的模塊。一個簡單但不是最優的方式就是將副本放置在不同的機架上,這就防止了機架故障時數據的丟失,並且在讀數據的時候可以充分利用不同機架的帶寬。這個方式均勻地將複製分散在集羣中,這就簡單地實現了組建故障時的負載均衡。然而這種方式增加了寫的成本,因為寫的時候需要跨越多個機架傳輸文件塊。
默認的HDFS block放置策略在最小化寫開銷和最大化數據可靠性、可用性以及總體讀取帶寬之間進行了一些折中。一般情況下複製因子為3,HDFS的副本放置策略是將第一個副本放在本地節點,將第二個副本放到本地機架上的另外一個節點而將第三個副本放到不同機架上的節點。這種方式減少了機架間的寫流量,從而提高了寫的性能。機架故障的幾率遠小於節點故障。這種方式並不影響數據可靠性和可用性的限制,並且它確實減少了讀操作的網絡聚合帶寬,因為文件塊僅存在兩個不同的機架, 而不是三個。文件的副本不是均勻地分佈在機架當中,1/3在同一個節點上,1/3副本在同一個機架上,另外1/3均勻地分佈在其他機架上。這種方式提高了寫的性能,並且不影響數據的可靠性和讀性能。
副本的選擇
為了儘量減小全局的帶寬消耗讀延遲,HDFS嘗試返回給一個讀操作離它最近的副本。假如在讀節點的同一個機架上就有這個副本,就直接讀這個,如果HDFS集羣是跨越多個數據中心,那麼本地數據中心的副本優先於遠程的副本。

hdfs安全模式

在啓動的時候,名字節點進入一個叫做安全模式的特殊狀態。安全模式中不允許發生文件塊的複製。名字節點接受來自數據節點的心跳和塊報告。一個塊報告包含數據節點所擁有的數據塊的列表。
每一個塊有一個特定的最小複製數。當名字節點檢查這個塊已經大於最小的複製數就被認為是安全地複製了,當達到配置的塊安全複製比例時(加上額外的30秒),名字節點就退出安全模式。它將檢測數據塊的列表,將小於特定複製數的塊複製到其他的數據節點。
文件系統的元數據的持久化
HDFS的命名空間是由名字節點來存儲的。名字節點使用叫做EditLog的事務日誌來持久記錄每一個對文件系統元數據的改變,如在HDFS中創建一個新的文件,名字節點將會在EditLog中插入一條記錄來記錄這個改變。類似地,改變文件的複製因子也會向EditLog中插入一條記錄。名字節點在本地文件系統中用一個文件來存儲這個EditLog。整個文件系統命名空間,包括文件塊的映射表和文件系統的配置都存在一個叫FsImage的文件中,FsImage也存放在名字節點的本地文件系統中。
名字節點在內存中保留一個完整的文件系統命名空間和文件塊的映射表的鏡像。這個元數據被設計成緊湊的,這樣4GB內存的名字節點就足以處理非常大的文件數和目錄。名字節點啓動時,它將從磁盤中讀取FsImage和EditLog,將EditLog中的所有事務應用到FsImage的仿內存空間,然後將新的FsImage刷新到本地磁盤中,因為事務已經被處理並已經持久化的FsImage中,然後就可以截去舊的EditLog。這個過程叫做檢查點。當前實現中,檢查點僅在名字節點啓動的時候發生,正在支持週期性的檢查點。
數據節點將HDFS數據存儲到本地的文件系統中。數據節點並不知道HDFS文件的存在,它在本地文件系統中以單獨的文件存儲每一個HDFS文件的數據塊。數據節點不會將所有的數據塊文件存放到同一個目錄中,而是啓發式的檢測每一個目錄的最優文件數,並在適當的時候創建子目錄。在本地同一個目錄下創建所有的數據塊文件不是最優的,因為本地文件系統可能不支持單個目錄下鉅額文件的高效操作。當數據節點啓動的時候,它將掃描它的本地文件系統,根據本地的文件產生一個所有HDFS數據塊的列表並報告給名字節點,這個報告稱作塊報告。

hdfs通信協議

所有的通信協議都是在TCP/IP協議之上構建的。一個客户端和指定TCP配置端口的名字節點建立連接之後,它和名字節點之間通信的協議是Client Protocol。數據節點和名字節點之間通過Datanode Protocol通信。
RPC(Remote Procedure Call)抽象地封裝了Client Protocol和DataNode Protocol協議。按照設計,名字節點不會主動發起一個RPC,它只是被動地對數據節點和客户端發起的RPC作出反饋。

hdfs異常處理

hdfs可靠性

HDFS的主要目標就是在存在故障的情況下也能可靠地存儲數據。三個最常見的故障是名字節點故障,數據節點故障和網絡斷開。

hdfs重新複製

一個數據節點週期性發送一個心跳包到名字節點。網絡斷開會造成一組數據節點子集和名字節點失去聯繫。名字節點根據缺失的心跳信息判斷故障情況。名字節點將這些數據節點標記為死亡狀態,不再將新的IO請求轉發到這些數據節點上,這些數據節點上的數據將對HDFS不再可用,可能會導致一些塊的複製因子降低到指定的值。
名字節點檢查所有的需要複製的塊,並開始複製他們到其他的數據節點上。重新複製在有些情況下是不可或缺的,例如:數據節點失效,副本損壞,數據節點磁盤損壞或者文件的複製因子增大。

hdfs數據正確性

從數據節點上取一個文件塊有可能是壞塊,壞塊的出現可能是存儲設備錯誤,網絡錯誤或者軟件的漏洞。HDFS客户端實現了HDFS文件內容的校驗。當一個客户端創建一個HDFS文件時,它會為每一個文件塊計算一個校驗碼並將校驗碼存儲在同一個HDFS命名空間下一個單獨的隱藏文件中。當客户端訪問這個文件時,它根據對應的校驗文件來驗證從數據節點接收到的數據。如果校驗失敗,客户端可以選擇從其他擁有該塊副本的數據節點獲取這個塊。

hdfs元數據失效

FsImage和Editlog是HDFS的核心數據結構。這些文件的損壞會導致整個集羣的失效。因此,名字節點可以配置成支持多個FsImage和EditLog的副本。任何FsImage和EditLog的更新都會同步到每一份副本中。
同步更新多個EditLog副本會降低名字節點的命名空間事務交易速率。但是這種降低是可以接受的,因為HDFS程序中產生大量的數據請求,而不是元數據請求。名字節點重新啓動時,選擇最新一致的FsImage和EditLog。
名字節點對於一個HDFS集羣是單點失效的。假如名字節點失效,就需要人工的干預。還不支持自動重啓和到其它名字節點的切換。

hdfs數據特點

hdfs快照

快照支持在一個特定時間存儲一個數據拷貝,快照可以將失效的集羣回滾到之前一個正常的時間點上。HDFS已經支持元數據快照。

hdfs數據組織

HDFS的設計是用於支持大文件的。運行在HDFS上的程序也是用於處理大數據集的。這些程序僅寫一次數據,一次或多次讀數據請求,並且這些讀操作要求滿足流式傳輸速度。HDFS支持文件的一次寫多次讀操作。HDFS中典型的塊大小是64MB,一個HDFS文件可以被切分成多個64MB大小的塊,如果需要,每一個塊可以分佈在不同的數據節點上。

hdfs階段狀態

一個客户端創建一個文件的請求並不會立即轉發到名字節點。實際上,一開始HDFS客户端將文件數據緩存在本地的臨時文件中。應用程序的寫操作被透明地重定向到這個臨時本地文件。當本地文件堆積到一個HDFS塊大小的時候,客户端才會通知名字節點。名字節點將文件名插入到文件系統層次中,然後為它分配一個數據塊。名字節點構造包括數據節點ID(可能是多個,副本數據塊存放的節點也有)和目標數據塊標識的報文,用它回覆客户端的請求。客户端收到後將本地的臨時文件刷新到指定的數據節點數據塊中。
當文件關閉時,本地臨時文件中未上傳的殘留數據就會被轉送到數據節點。然後客户端就可以通知名字節點文件已經關閉。此時,名字節點將文件的創建操作添加到到持久化存儲中。假如名字節點在文件關閉之前死掉,文件就丟掉了。
上述流程是在認真考慮了運行在HDFS上的目標程序之後被採用。這些應用程序需要流式地寫文件。如果客户端對遠程文件系統進行直接寫入而沒有任何本地的緩存,這就會對網速網絡吞吐量產生很大的影響。這方面早有前車之鑑,早期的分佈式文件系統如AFS,也用客户端緩衝來提高性能,POSIX接口的限制也被放寬以達到更高的數據上傳速率

hdfs流水式複製

當客户端寫數據到HDFS文件中時,如上所述,數據首先被寫入本地文件中,假設HDFS文件的複製因子是3,當本地文件堆積到一塊大小的數據,客户端從名字節點獲得一個數據節點的列表。這個列表也包含存放數據塊副本的數據節點。當客户端刷新數據塊到第一個數據節點。第一個數據節點開始以4kb為單元接收數據,將每一小塊都寫到本地庫中,同時將每一小塊都傳送到列表中的第二個數據節點。同理,第二個數據節點將小塊數據寫入本地庫中同時傳給第三個數據節點,第三個數據節點直接寫到本地庫中。一個數據節點在接前一個節點數據的同時,還可以將數據流水式傳遞給下一個節點,所以,數據是流水式地從一個數據節點傳遞到下一個。

hdfs可訪問性

HDFS提供多種方式由應用程序訪問,自然地,HDFS提供為程序提供java api,為c語言包裝的java api也是可用的,還有一個HTTP瀏覽器可以瀏覽HDFS中的文件,通過WebDAV協議訪問HDFS庫的方式也正在構建中。

hdfsDFSShell

HDFS允許用户數據組織文件和文件夾的方式,它提供一個叫DFSShell的接口,使用户可以和HDFS中的數據交互。命令集的語法跟其他用户熟悉的shells(bash,csh)相似。以下是一些例子:
Action
Command
創建目錄 /foodir
hadoop dfs -mkdir /foodir
查看文件 /foodir/myfile.txt
hadoop dfs -cat /foodir/myfile.txt
刪除文件/foodir/myfile.txt
hadoop dfs -rm /foodir myfile.txt
DFSAdmin
DFSAdmin命令集是用於管理dfs集羣的,這些命令只由HDFS管理員使用。示例:
Action
Command
將集羣設置成安全模式
bin/hadoop dfsadmin -safemode enter
產生一個數據節點的列表
bin/hadoop dfsadmin -report
去掉一個數據節點
bin/hadoop dfsadmin -decommission datanodename

hdfs瀏覽器接口

典型的HDFS初始化配置了一個web 服務,通過一個可配的TCP端口可以訪問HDFS的命名空間。這就使得用户可以通過web瀏覽器去查看HDFS命名空間的內容。

hdfs存儲空間回收

文件刪除和恢復刪除
當一個文件被用户或程序刪除時,它並沒有立即從HDFS中刪除。HDFS將它重新命名後轉存到/trash目錄下,這個文件只要還在/trash目錄下保留就可以重新快速恢復。文件在/trash中存放的時間是可配置的。存儲時間超時後,名字節點就將目標文件名字空間中刪除,同時此文件關聯的所有文件塊都將被釋放。注意,用户刪除文件的時間和HDF系統回收空閒存儲之間的時間間隔是可以估計的。
刪除一個文件之後,只要它還在/trash目錄下,用户就可以恢復刪除一個文件。如果一個用户希望恢復刪除他已經刪除的文件,可以查找/trash目錄獲得這個文件。/trash目錄僅保存最新版本的刪除文件。/trash目錄也像其他目錄一樣,只有一個特殊的功能,HDFS採用一個特定的策略去自動地刪除這個目錄裏的文件,當前默認的策略是刪除在此目錄存放超過6小時的文件。以後這個策略將由一個定義好的接口來配置。
當文件的複製因子減少了,名字節點選擇刪除多餘的副本,下一次的心跳包的回覆就會將此信息傳遞給數據節點。然後,數據節點移除相應的塊,對應的空閒空間將回歸到集羣中,需要注意的就是,在setReplication函數調用後和集羣空閒空間更新之間會有一段時間延遲

hdfs文件讀取解析

文件內容讀取的代碼可以分為三個大步驟 [1] 
1、獲取文件系統
2、通過文件系統打開文件
3、將文件內容輸出
public static void read(Path path) throws IOException{
    FileSystem hdfs = HdfsUtils.getFilesystem();  //步驟 1
    FSDataInputStream fsDataInputStream =  hdfs.open(path); //步驟 2
    IOUtils.copyBytes(fsDataInputStream, System.out, 4096,false);  //步驟 3
}

接下來,我們來看一下每個步驟的詳細過程

hdfs獲取文件系統對象

要從HDFS上讀取文件,必須先得到一個FileSystem。HDFS本身就是一個文件系統,所以,我們得到一個文件系統後就可以對HDFS進行相關操作。獲取文件系統的步驟可以分為以下2步。
1、讀取配置文件
2、獲取文件系統。
讀取配置文件:Configuration類有三個構造器,無參數的構造器表示直接加載默認資源,也可以指定一個boolean參數來關閉加載默認值,或直接使用另外一個Configuration對象來初始化。
package com.yq.common;
 
import java.net.URI;
 
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
 
public class HdfsUtils {
    public static FileSystem getFilesystem(){
        FileSystem hdfs=null;
        Configuration conf=new Configuration(); 
        try{
            URI uri = new URI("hdfs://localhost:9000");
            hdfs = FileSystem.get(uri,conf);
        }
        catch(Exception ex){
        //
        }
    return hdfs;
    }
}

hdfs打開文件

FSDataInputStream fsDataInputStream =  hdfs.open(path);
打開文件其實就是創建一個文件輸入流,跟蹤文件系統的open方法,可以找到源碼
  public FSDataInputStream open(Path f) throws IOException {
    return open(f, getConf().getInt("io.file.buffer.size", 4096));
  }

再跟蹤open方法,找到以下抽象方法
  public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException;
      //這個方法在DistributedFileSystem類有實現,如下
  @Override
  public FSDataInputStream open(Path f, final int bufferSize) throws IOException {
    statistics.incrementReadOps(1);
    Path absF = fixRelativePart(f);
    return new FileSystemLinkResolver<FSDataInputStream>() {
      @Override
      public FSDataInputStream doCall(final Path p)
          throws IOException, UnresolvedLinkException {
        return new HdfsDataInputStream(
            dfs.open(getPathName(p), bufferSize, verifyChecksum));
      }
      @Override
      public FSDataInputStream next(final FileSystem fs, final Path p)
          throws IOException {
        return fs.open(p, bufferSize);
      }
    }.resolve(this, absF);
  }

在返回結果的時候,創建了一個FileSystemLinkResolver對象,並實現了此類的兩個抽象方法。doCall方法和next方法都在resolve方法裏用到了,而next方法只是在resolve方法異常捕獲時才調用。
跟蹤doCall方法,doCall方法裏的open()方法有3個參數,src表示要打開的文件路徑,buffersize表示緩衝大小,verifyChecksum表示是否校驗和,的源代碼如下。
  public DFSInputStream open(String src, int buffersize, boolean verifyChecksum)
      throws IOException, UnresolvedLinkException {
    checkOpen();
    //    Get block info from namenode
    return new DFSInputStream(this, src, buffersize, verifyChecksum);
  }

checkOpen方法表示檢查文件系統是否已經打開,如果沒有打開,則拋出異常(FileSystemclosed)。
然後返回一個分佈式文件系統輸入流DFSInputStream),此處調用的構造方法源代碼如下。
  DFSInputStream(DFSClient dfsClient, String src, int buffersize, boolean verifyChecksum
                 ) throws IOException, UnresolvedLinkException {
    this.dfsClient = dfsClient;
    this.verifyChecksum = verifyChecksum;
    this.buffersize = buffersize;
    this.src = src;
    this.cachingStrategy =
        dfsClient.getDefaultReadCachingStrategy();
    openInfo();
  }

這個方法先是做了一些準備工作,然後調用openInfo()方法,openInfo()方法是一個線程安全的方法,作用是從namenode獲取已打開的文件信息。其源代碼如下。
 /**
   * Grab the open-file info from namenode
   */
  synchronized void openInfo() throws IOException, UnresolvedLinkException {
    lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();
    int retriesForLastBlockLength = dfsClient.getConf().retryTimesForGetLastBlockLength;
    while (retriesForLastBlockLength > 0) {
      // Getting last block length as -1 is a special case. When cluster
      // restarts, DNs may not report immediately. At this time partial block
      // locations will not be available with NN for getting the length. Lets
      // retry for 3 times to get the length.
      if (lastBlockBeingWrittenLength == -1) {
        DFSClient.LOG.warn("Last block locations not available. "
            + "Datanodes might not have reported blocks completely."
            + " Will retry for " + retriesForLastBlockLength + " times");
        waitFor(dfsClient.getConf().retryIntervalForGetLastBlockLength);
        lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();
      } else {
        break;
      }
      retriesForLastBlockLength--;
    }
    if (retriesForLastBlockLength == 0) {
      throw new IOException("Could not obtain the last block locations.");
    }
  }

此方法有調用fetchLocatedBlocksAndGetLastBlockLength()方法獲取塊的位置信息
  private long fetchLocatedBlocksAndGetLastBlockLength() throws IOException {
    final LocatedBlocks newInfo = dfsClient.getLocatedBlocks(src, 0);
    if (DFSClient.LOG.isDebugEnabled()) {
      DFSClient.LOG.debug("newInfo = " + newInfo);
    }
    if (newInfo == null) {
      throw new IOException("Cannot open filename " + src);
    }
 
    if (locatedBlocks != null) {
      Iterator<LocatedBlock> oldIter = locatedBlocks.getLocatedBlocks().iterator();
      Iterator<LocatedBlock> newIter = newInfo.getLocatedBlocks().iterator();
      while (oldIter.hasNext() && newIter.hasNext()) {
        if (! oldIter.next().getBlock().equals(newIter.next().getBlock())) {
          throw new IOException("Blocklist for " + src + " has changed!");
        }
      }
    }
    locatedBlocks = newInfo;
    long lastBlockBeingWrittenLength = 0;
    if (!locatedBlocks.isLastBlockComplete()) {
      final LocatedBlock last = locatedBlocks.getLastLocatedBlock();
      if (last != null) {
        if (last.getLocations().length == 0) {
          if (last.getBlockSize() == 0) {
            // if the length is zero, then no data has been written to
            // datanode. So no need to wait for the locations.
            return 0;
          }
          return -1;
        }
        final long len = readBlockLength(last);
        last.getBlock().setNumBytes(len);
        lastBlockBeingWrittenLength = len; 
      }
    }
 
    currentNode = null;
    return lastBlockBeingWrittenLength;
  }

getLocatedBlocks方法可以獲取塊的位置信息。LocatedBlocks類是許多塊的位置信息的集合。因為從此類的源碼可以發現有這個一個私有屬性:
 private final List<LocatedBlock> blocks; // array of blocks with prioritized locations
通過文件名,FSDataInputStream類可以獲取相文件內容,也可以充當namenode與datanode橋樑。

hdfs將文件內容在標準輸出顯示

因為之前已經獲得了一個FSDataInputStream,所以,我們可以調用方法copyBytes將FSDataInputStream拷貝到標準輸出流System.out顯示。
 public static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close) 
    throws IOException {
    try {
      copyBytes(in, out, buffSize);
      if(close) {
        out.close();
        out = null;
        in.close();
        in = null;
      }
    } finally {
      if(close) {
        closeStream(out);
        closeStream(in);
      }
    }
  }

此方法裏又調用了另外一個copyBytes方法,作用同樣是從一個流拷貝到另外一個流。
public static void copyBytes(InputStream in, OutputStream out, int buffSize) 
    throws IOException {
    PrintStream ps = out instanceof PrintStream ? (PrintStream)out : null;
    byte buf[] = new byte[buffSize];
    int bytesRead = in.read(buf);
    while (bytesRead >= 0) {
      out.write(buf, 0, bytesRead);
      if ((ps != null) && ps.checkError()) {
        throw new IOException("Unable to write to output stream.");
      }
      bytesRead = in.read(buf);
    }
  }

先從輸入流中讀取buffSize大小的數據到緩衝裏面,然後將緩衝裏的數據寫入到輸出流out裏。一直循環,直到從輸入流中讀到緩衝裏的字節長度為0,表示輸入流裏的數據已經讀取完畢。
參考資料