Hadoop IO基於文件的數據結構詳解【列式和行式數據結構的存儲策略】

Charles全部關於hadoop的文章參考自hadoop權威指南第四版預覽版 你們能夠去safari免費閱讀其英文預覽版。本人也上傳了PDF版本在個人資源中能夠免費下載,不須要C幣,點擊這裏下載。

對於某些應用,須要一個特殊的數據結構來存儲數據。針對運行基於MapReduce 的進程,將每一個二進制數據塊放入它本身的文件,這樣作不易擴展, 因此Hadoop 爲此開發了一系列高級容器。咱們能夠想象一下,mapreduce遇到的文件多是日誌文件,文本文件等等,mapreduce 拆分以後變成一條條數據記錄,會轉化爲IntWritable,Text等等能夠序列化的對象,而後序列化輸出到網絡或者硬盤,每一種類型的輸出都會放入本身的文件,這樣是很不經濟的,由於咱們指望的是全部的數據能夠用同一個容器就最好了,那麼hadoop就提供了一系列高級容器,用來存放這些數據。

SequenceFile和 MapFile兩個表明。


假設有一個日誌文件,色的每個日誌記錄都是一行新的文本。若是想記錄二進制 類型,純文本並非一個合適的格式。Hadoop 的SequenceFile 類很適合這個情 況,它爲二進制鍵/值對提供一個持久化的數據結構。若是想將它用做日 志文件格式,須要選擇一個鍵(例如LongWriteable 表示的時間戳)和一個值(是一個 Writable表示日誌記錄的數量)。

SequenceFile

如何編寫SequenceFile呢?怎麼使用呢?
寫入SequenceFile
要想新建一個SequenceFile 類. 使用色的其中一個createWriter() 靜態方 法,該方法會返回一個SequenceFile.Writer 實例。有幾個重載版本,可是它們 都須要指定一個要寫入的流FSDataOutputStream 或成對的文件系統和路徑,一 個Configuration 對象和鍵/值類型。可選參數包括壓縮的類型和編碼/解碼器、 一個將由寫進度來喚醒的Progressable 回調和一個將存儲在SequenceFile 類頭 部的Metadata 實例。 存儲在SequenceFile 類中的鍵和值不必定必須是Writable 。能夠被 SequenceFile 類序列化和反序列的任何類型均可以使用。 有SequenceFile.Writer 以後,就用append ()方能寫入鍵/值對。而後在結束的 時候調用close() 方法。(SequenceFile .Writer 實現了java.io.Closeable) 。
總結以下:  
1)建立Configuration 
2)獲取FileSystem 
3)建立文件輸出路徑Path 
4)調用SequenceFile.createWriter獲得SequenceFile.Writer對象 
5)調用SequenceFile.Writer.append追加寫入文件 
6)關閉流 
代碼實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public  class  SequenceFileWriteDemo {
   
   private  static  final  String[] DATA = {
     "One, two, buckle my shoe" ,
     "Three, four, shut the door" ,
     "Five, six, pick up sticks" ,
     "Seven, eight, lay them straight" ,
     "Nine, ten, a big fat hen"
   };
   
   public  static  void  main(String[] args)  throws  IOException {
     String uri = args[ 0 ];
     Configuration conf =  new  Configuration();
     FileSystem fs = FileSystem.get(URI.create(uri), conf);
     Path path =  new  Path(uri);
     IntWritable key =  new  IntWritable();
     Text value =  new  Text();
     SequenceFile.Writer writer =  null ;
     try  {
       writer = SequenceFile.createWriter(fs, conf, path,
//   SequenceFile.Writer writer = SequenceFile.createWriter(fs,conf,path,key.getClass(),value.getClass(),CompressionType.RECORD,new BZip2Codec());  採用壓縮,用BZip2壓縮算法
           key.getClass(), value.getClass());
       for  ( int  i =  0 ; i <  100 ; i++) {
         key.set( 100  - i);
         value.set(DATA[i % DATA.length]);
         System.out.printf( "[%s]\t%s\t%s\n" , writer.getLength(), key, value);//getLength獲取的是當前文件的位置
         writer.append(key, value);
       }
     finally  {
       IOUtils.closeStream(writer);
     }
   }
}
% 
[128]   100     One, two, buckle my shoe
[173]   99      Three, four, shut the door
[220]   98      Five, six, pick up sticks
[264]   97      Seven, eight, lay them straight
[314]   96      Nine, ten, a big fat hen
[359]   95      One, two, buckle my shoe
[404]   94      Three, four, shut the door
[451]   93      Five, six, pick up sticks
[495]   92      Seven, eight, lay them straight
[545]   91      Nine, ten, a big fat hen
...
[1976]  60      One, two, buckle my shoe
[2021]  59      Three, four, shut the door
[2088]  58      Five, six, pick up sticks
[2132]  57      Seven, eight, lay them straight
[2182]  56      Nine, ten, a big fat hen
...
[4557]  5       One, two, buckle my shoe
[4602]  4       Three, four, shut the door
[4649]  3       Five, six, pick up sticks
[4693]  2       Seven, eight, lay them straight
[4743]  1       Nine, ten, a big fat henhadoop SequenceFileWriteDemo numbers.seq


讀取SequenceFile
從頭至尾讀取序列文件,須要建立一個SequenceFile . Reader 實例,反覆調用next ()方法之一遍歷記錄。使用哪個方法取決於所用的序列化框架。若是使用Writable 類型,則可使用取一個鍵和一個值做爲參數的next ( )方怯,而後將數據流中的下一個鍵/值對讀入這些變量:public boolean next(Writable key , Writable val)若是讀取的是一個鍵/值對,則返回值爲true ,若是讀取的是文件末尾,返回值爲false 。
總結以下:
1)建立Configuration 
2)獲取FileSystem 
3)建立文件輸出路徑Path 
4)new一個SequenceFile.Reader進行讀取 
5)獲得keyClass和valueClass 
6)關閉流
代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public  class  SequenceFileReadDemo {
   
   public  static  void  main(String[] args)  throws  IOException {
     String uri = args[ 0 ];
     Configuration conf =  new  Configuration();
     FileSystem fs = FileSystem.get(URI.create(uri), conf);
     Path path =  new  Path(uri);
 
     SequenceFile.Reader reader =  null ;
     try  {
       reader =  new  SequenceFile.Reader(fs, path, conf);
       Writable key = (Writable)
         ReflectionUtils.newInstance(reader.getKeyClass(), conf);//獲取key的數據類型 是從reader中獲取的
       Writable value = (Writable)
         ReflectionUtils.newInstance(reader.getValueClass(), conf);
       long  position = reader.getPosition();
       while  (reader.next(key, value)) {
         String syncSeen = reader.syncSeen() ?  "*"  "" ;//同步點,那就*標記
         System.out.printf( "[%s%s]\t%s\t%s\n" , position, syncSeen, key, value);
         position = reader.getPosition();  // beginning of next record
       }
     finally  {
       IOUtils.closeStream(reader);
     }
   }
}

注意:
   Writable key = (Writable)  ReflectionUtils.newInstance(reader.getKeyClass(), conf);//獲取key的數據類型 是從reader中獲取的
   Writable value = (Writable)  ReflectionUtils.newInstance(reader.getValueClass(), conf);
    經過以上兩行代碼,咱們能夠找到任何reader的數據類型,也就是說咱們能夠處理任何數據類型,只要是writable實現的。

此程序的另外一個特徵是它顯示了序列文件中同步點的位置。 同步點是流中的一個 點,若是reader "迷失" ,同步點就可用於從新同步記錄邊界,例如在查找流中任 意一個位置以後.同步點由SequenceFile.Reader 來記錄,當序列文件被寫入的 時候, 它會每隔幾個記錄就插入一個特殊的項來標記此同步點.插入的這種項很是 小,一般開銷小於存儲大小的1 % 。同步點一般與記錄邊界重合。
% badoop SequencepileReadDemo numbers. seq
% 
[128]   100     One, two, buckle my shoe
[173]   99      Three, four, shut the door
[220]   98      Five, six, pick up sticks
[264]   97      Seven, eight, lay them straight
[314]   96      Nine, ten, a big fat hen
[359]   95      One, two, buckle my shoe
[404]   94      Three, four, shut the door
[451]   93      Five, six, pick up sticks
[495]   92      Seven, eight, lay them straight
[545]   91      Nine, ten, a big fat hen
[590]   90      One, two, buckle my shoe
...
[1976]  60      One, two, buckle my shoe
[2021*] 59      Three, four, shut the door
[2088]  58      Five, six, pick up sticks
[2132]  57      Seven, eight, lay them straight
[2182]  56      Nine, ten, a big fat hen
...
[4557]  5       One, two, buckle my shoe
[4602]  4       Three, four, shut the door
[4649]  3       Five, six, pick up sticks
[4693]  2       Seven, eight, lay them straight
[4743]  1       Nine, ten, a big fat henhadoop SequenceFileReadDemo numbers.seq


有兩種方法能夠查找序列文件中的指定位置。
第一種是seek ( )方法,它將reader 定位在文件中的指定點。例如,如預期的那樣尋找一個記錄邊界:
reader.seek(359);assertThat(reader.next(key,value),is(true));assertThat(((IntWritable)key).get(),is(95));

但若是文件中的指定位置不是記錄邊界. reader 會在調用next ( )時失敗。

reader.seek(360);reader.next(key,value);// fails with IOException

第二種查找記錄邊界的方格用到了同步點。SequenceFile.Reader 上的 sync(long position) 方能把reader 定位到下一個同步點。(若是在這以後沒有同 步點, 那麼此reader 會定位到文件末尾)。所以,咱們能夠用流中的任何位置來調 用sync () 一一如一個沒有記錄的邊界一一且reader 將本身重定位到下一個同步點 繼續讀取:

reader.sync(360);assertThat(reader.getPosition(),is(2021L));assertThat(reader.next(key,value),is(true));assertThat(((IntWritable)key).get(),is(59));

在使用序列文件做爲Map Reduce 輸入的時候,同步點開始發揮做用,由於它們允 許文件分割,因此它的不一樣部分經過獨立的map 任務得以單獨處理。這一點是很重要的。

用命令行接口顯示序列文件
Hadoop 文件系統命令有一個-text 選項顯示文本格式的序列文件。它看起來像是 文件的魔法數字,使其可以嘗試檢測文件類型並相應地將其轉換爲文本。它能夠識別  gzip 壓縮文件和序列文件,不然便假設輸入是純文本。 對於序列文件,在鍵/值對有一個有意義的字符串表示時,此命令很是有用(如 toString ()方法定義的那樣)。若是有本身的鍵/值對類,必須肯定它們在Hadoop
的類路徑上。 對前面建立的序列文件運行它,獲得以下輸出:
% 
100     One, two, buckle my shoe
99      Three, four, shut the door
98      Five, six, pick up sticks
97      Seven, eight, lay them straight
96      Nine, ten, a big fat hen
95      One, two, buckle my shoe
94      Three, four, shut the door
93      Five, six, pick up sticks
92      Seven, eight, lay them straight
91      Nine, ten, a big fat henhadoop fs -text numbers.seq | head

排序和合並序列文件
序列文件的排序和合並也是咱們必需要熟悉的內容,由於mapreduce處理序列文件的時候,不可避免要對數據進行拆分,排序,處理,合併。 排序和合並一個或多個序列文件,最強的方式是使用MapReduce . MapReduce 固 有的並行方式,容許咱們指定使用多少個reduce(此數量決定桌輸出分區的個數〉。 例如,經過指定一個reducer. 咱們會獲得一個輸出文件。經過指定輸入和輸出爲 序列文件並經過設置鍵/值類型,咱們可使用Hadoop 自帶的排序例子:
% 
% 
1       Nine, ten, a big fat hen
2       Seven, eight, lay them straight
3       Five, six, pick up sticks
4       Three, four, shut the door
5       One, two, buckle my shoe
6       Nine, ten, a big fat hen
7       Seven, eight, lay them straight
8       Five, six, pick up sticks
9       Three, four, shut the door
10      One, two, buckle my shoehadoop jar \$HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \sort -r 1 \-inFormat org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat \-outFormat org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat \-outKey org.apache.hadoop.io.IntWritable \-outValue org.apache.hadoop.io.Text \numbers.seq sortedhadoop fs -text sorted/part-r-00000 | head
以上雖然只是一個小小的demo,卻值得花時間好好研究一下,mapreduce的排序和合並功能是hadoop的靈魂之一,所以在mapreduce中對這個合併排序有詳細的講解,在此再也不贅述。咱們只須要明白,通過mapreduce的排序合併以後順序改變爲從小到大就能夠了。

做爲使用MapRedu ce 進行排序/合併的替代方案. SequenceFile . Sorter 類有 sort ( ) 和merge ( )方法。這些函數的出現早於MapReduce. 但性能低於 MapReduce(例如,爲了實現並行計算,須要對敬據進行浮動分區) .因此通常狀況 下. MapReduce 是排序、合併序列文件的首選。反正這些老舊的方法不要再用是個正確的選擇。

序列文件的格式

先給出上圖。能夠看出 序列文件由一個頭部header和一個或多個記錄record組成 。 序列文件的前三位字節是SEQ 字節,做爲幻數,後緊接一個字節表明版本號.頭 部包括其餘字段(包含鍵/值類的名稱、壓縮細節、用戶定義的元數據和罔步標 記戶。調用同步標記能夠容許一個reader 同步一個字段中的任何一個記錄邊界。每 個文件有一個隨機產生的同步記號,它的值存儲在頭部中。同步記號出如今序列文 件的記錄中, 它們設計數量不超過存儲的1 %. 因此它們並無必要出現每一對記 錄中(好比在一個很短的記錄中) 。
咱們能夠查看SequenceFile類來找到文件格式的蛛絲馬跡
序列文件的壓縮:

記錄的內部格式取決因而否啓用壓縮,若是是, 要麼記錄壓縮,要麼塊壓縮。 若是沒有啓用壓縮(默認設置) .那麼每一個記錄都由他的記錄長度(字節數)、鍵的長 度、鍵和值組成。
記錄壓縮
長度字段被做爲四字節整型值寫入,遵循java.io.DataOutput 中writelnt ()方法的規範。鍵/值的序列化是經過爲正被寫入序列文件中的類而定 義的Serialization 來完成的。 記錄壓縮格式與無壓縮基本相同,不一樣的是值字節是用定義在頭部的編碼/解碼器 來壓縮的。注意,鍵是不壓縮的。
塊壓縮
塊壓縮一次壓縮多個記錄,所以它比記錄壓縮更緊湊,且通常應優先選擇,由於它 有機會利用記錄之間的類似之處。記錄直到到達字節的最小大小纔會 被添加到塊,該最小值是由io.seqfile.compress.blocksize 中的屬性定義的, 它的默認值是1000000 字節。同步標記寫在每一個塊開始以前。塊的格式是一個字 段(指出塊中的記錄數) . 後跟四個字段(分別爲鍵長度、鍵、值長度和值)。

MapFile

MapFile 是通過排序的帶索引的SequenceFile . 能夠根據鍵進行查找. MapFile 能夠被認爲是java.util.Map 的一種持久化形式(雖然它沒有實現這個接口),它 會增加乃至超過Map 在內存中佔用的大小。

寫一個MapFil e

(1)MapFile是通過排序的索引的SequenceFile,能夠根據key進行查找
(2)MapFile的key 是WritableComparable類型的,而value是Writable類型的;
(3)可使用MapFile.fix()方法來重建索引,把SequenceFile轉換成MapFile
(4)它有兩個靜態成員變量:
static String DATA_FILE_NAME //數據文件名
static String INDEX_FILE_NAME //索引文件名
MapFile寫過程: 
(1)建立Configuration;
(2)獲取FileSystem;
(3)建立文件輸出路徑Path;
(4)new一個MapFile.Writer對象;
(5)調用MapFile.Writer.append追加寫入文件;
(6)關閉流;
下面給出代碼實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public  class  MapFileWriteDemo {
   
   private  static  final  String[] DATA = {
     "One, two, buckle my shoe" ,
     "Three, four, shut the door" ,
     "Five, six, pick up sticks" ,
     "Seven, eight, lay them straight" ,
     "Nine, ten, a big fat hen"
   };
   
   public  static  void  main(String[] args)  throws  IOException {
     String uri = args[ 0 ];
     Configuration conf =  new  Configuration();
     FileSystem fs = FileSystem.get(URI.create(uri), conf);
 
     IntWritable key =  new  IntWritable();
//MapFile的key 是WritableComparable類型的,而value是Writable類型的;用來實現key的索引就須要比較值
     Text value =  new  Text();
     MapFile.Writer writer =  null ;
     try  {
       writer =  new  MapFile.Writer(conf, fs, uri,
           key.getClass(), value.getClass());
       
       for  ( int  i =  0 ; i <  1024 ; i++) {
         key.set(i +  1 );
         value.set(DATA[i % DATA.length]);
         writer.append(key, value);
       }
     finally  {
       IOUtils.closeStream(writer);
     }
   }
}
使用此程序來建立一個MapFile :
%hadoop MapPi1eWriteDemo numbers.map
看看這個MapFile ,能夠看出它確實是一個目錄, 其中包含兩個文件, 分別名爲 data 和index:
兩個文件都是SequenceFile. 數據文件包括全部的輸入,按順序的:

index 文件包括一小部分鍵, 而且包括鍵到data 文件中鍵偏移量的映射:
% hadoop  fs -text numbers.map/index

從輸出能夠看出,默認狀況下,只有每128 個鍵被包括在索引中,不過能夠改變這 個數,具體作法是設置io.map.index.interval 屬性或調用MapFile.Writer 實 例的setlndexlnterval() . 增長索引間隔的理由之一是減小MapFil e 存儲 索引所須要的內存大小.反之,在犧牲內存的狀況下,能夠減小索引間隔以改善隨 機時間(由於平均說來,須要跳過的記錄更少)。 由於索引只是鍵的部分索引,因此MapFile 不能提供方法來枚舉或計數它包含的 全部鍵.執行這些操做的惟一方法是該取整個文件.

讀取一個MapFile

MapFile讀過程:
(1)建立Configuration;
(2)獲取FileSystem;
(3)建立文件輸出路徑Path;
(4)new一個MapFile.Reader對象;
(5)獲得keyClass和valueClass;
(6)關閉流;

雖然不想再次贅述,可是仍是值得再給出相關代碼看看:
1
2
3
4
5
6
7
8
9
10
11
12
13
  public  static  void  main(String[] args)  throws  IOException {
   Configuration conf =  new  Configuration();
   FileSystem fs = FileSystem.get(URI.create(uri),conf);
   Path path =  new  Path( "/numbers.map" );
   MapFile.Reader reader =  new  MapFile.Reader(fs, path.toString(), conf);
   WritableComparable key = (WritableComparable)ReflectionUtils.newInstance(reader.getKeyClass(), conf);
   Writable value = (Writable)ReflectionUtils.newInstance(reader.getValueClass(), conf);
   while (reader.next(key, value)){
   System.out.println( "key = " +key);
   System.out.println( "value = " +value);
   }
   IOUtils.closeStream(reader);
   }
以上是遍歷, 隨機存取查找能夠調用get()方法來執行,源碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/** Return the value for the named key, or null if none exists. */
     public  synchronized  Writable get(WritableComparable key, Writable val)
       throws  IOException {
       if  (seek(key)) {//找key 二叉搜索
         data.getCurrentValue(val);//找值 遍歷
         return  val;
       else
         return  null ;
     }
  /** Positions the reader at the named key, or if none such exists, at the
      * first entry after the named key.  Returns true iff the named key exists
      * in this map.
      */
     public  synchronized  boolean  seek(WritableComparable key)  throws  IOException {
       return  seekInternal(key) ==  0 ;
     }
  private  synchronized  int  seekInternal(WritableComparable key,
         final  boolean  before)
       throws  IOException {
       readIndex();                                 // make sure index is read讀取索引文件
 
       if  (seekIndex != - 1                          // seeked before 查找以前的預處理
           && seekIndex+ 1  < count           
           && comparator.compare(key, keys[seekIndex+ 1 ])< 0  // before next indexed
           && comparator.compare(key, nextKey)
           >=  0 ) {                                  // but after last seeked
         // do nothing
       else  {
         seekIndex = binarySearch(key);
         if  (seekIndex <  0 )                         // decode insertion point解碼插入點
           seekIndex = -seekIndex- 2 ;
 
         if  (seekIndex == - 1 )                       // belongs before first entry
           seekPosition = firstPosition;            // use beginning of file從文件開頭開始
         else
           seekPosition = positions[seekIndex];     // else use index不然使用當前索引值
       }
       data.seek(seekPosition);//文件指針指向開始的位置接下來想向後面查找
 
       if  (nextKey ==  null )
         nextKey = comparator.newKey();
 
       // If we're looking for the key before, we need to keep track
       // of the position we got the current key as well as the position
       // of the key before it.
       long  prevPosition = - 1 ;
       long  curPosition = seekPosition;
 
       while  (data.next(nextKey)) {//讀取下一個key到nextkey中去
         int  c = comparator.compare(key, nextKey);//比較以前的key和下一個key
         if  (c <=  0 ) {                              // at or beyond desired 表示nextkey比key大因此位置就已經超過了咱們要找的那個key要從頭再找
           if  (before && c !=  0 ) {//若是沒有到key
             if  (prevPosition == - 1 ) {
               // We're on the first record of this index block
               // and we've already passed the search key. Therefore
               // we must be at the beginning of the file, so seek
               // to the beginning of this block and return c
               data.seek(curPosition);//從新找
             else  {
               // We have a previous record to back up to
               data.seek(prevPosition);//返回以前的記錄點
               data.next(nextKey);
               // now that we've rewound, the search key must be greater than this key
               return  1 ;
             }
           }
           return  c;
         }
         if  (before) {//找到,獲取當前的位置
           prevPosition = curPosition;
           curPosition = data.getPosition();
         }
       }
 
       return  1 ;
     }
 /**
     * Get the 'value' corresponding to the last read 'key'.
     * @param val : The 'value' to be read.
     * @throws IOException
     */
    public synchronized void getCurrentValue(Writable val) 
      throws IOException {
      if (val instanceof Configurable) {
        ((Configurable) val).setConf(this.conf);
      }

      // Position stream to 'current' value
      seekToCurrentValue();

      if (!blockCompressed) {
        val.readFields(valIn);

        if (valIn.read() > 0) {
          LOG.info("available bytes: " + valIn.available());
          throw new IOException(val+" read "+(valBuffer.getPosition()-keyLength)
                                + " bytes, should read " +
                                (valBuffer.getLength()-keyLength));
        }
      } else {
        // Get the value
        int valLength = WritableUtils.readVInt(valLenIn);
        val.readFields(valIn);

        // Read another compressed 'value'
        --noBufferedValues;

        // Sanity check
        if ((valLength < 0) && LOG.isDebugEnabled()) {
          LOG.debug(val + " is a zero-length value");
        }
      }

    }
返回值用因而否在MapFile 中找到一個條目,若是返回值爲nul l ,指定值沒有 對應的值. 若是找到鍵.則此鍵的對應值被該取到 val ,就像從調用方法返回 同樣. 理解它的實現方式可能有所幫助。這裏是一小段代碼.從前一小節建立的MapFile 進行檢索:

對於這個操做, MapFie.Reader 將index 文件讀取到內存中(這裏是緩存的,使後 面的隨機存儲調用能使用相同的內存索引)。而後, reader 對內存內的索引執行二 叉搜索,試圖在索引文件中找到小子或等於搜索鍵496 的那些鍵.
在這個例子中, 找到的索引鍵是385 ,對應的值是18030 ,後者是在data 文件中的偏移量.接着 reader 在data 文件中找到這個偏移量,而後讀取數據直到這個鍵大於或者等於搜索 鍵496. 在這裏,找到了匹配而且從data 文件中讀取了數據。上面的代碼塊我給出了這個流程的索引,代碼很簡單易懂。
整體來講,查找選擇的是一次磁盤尋址和一次遍歷磁盤上128 項的磁盤掃描. 對於隨機存儲讀取,這實際上是很是高效的。 getClosest() 方峯和get ( )類似, 不一樣的是它返回"更靠近"指定鍵的匹配,而 不是在沒有匹配時返回null . 更精確地說, 若是MapFile 包括指定的鍵.那麼它 就是返回值,不然返回這個MapFile 中直接在該指定鍵以後的鍵(或者以前,看布 爾參數的設置)。 大型MapFile 的索引會佔很大的內存.相較於重建索引改變索引間隔,咱們能夠 經過設置io . map . index . skip 屬性將MapFile 中的小部分鍵讀入內存。這個屬性 通常爲0 ,意味着不跳過索引鍵,若是值爲1 , 意味着隔一個鍵跳過一個鍵(因此 留下一半的鍵在索引中) 若是值爲2 ,表示該取一個鍵跳過兩個鍵(因此三分之一 的鍵留在索引中) , 以此類推.更大的跳過值能夠節省更多內存空間,但消耗查找 時間,由於平均而言,須要在磁盤中掃描更多條目。

將SequenceFile 轉換爲MapFile

對於MapFile ,一種方式是將其視爲通過索引和排序的SequenceFile ,因此咱們 天然會想到把SequenceFile 轉換爲MapFile. 前面討論瞭如何對SequenceFile 進行排序,因此這裏來看如何建立SequenceFile的索引.下面的程序代碼使用了 相似MapFile 的靜態實用方法fix (),後者重建MapFile 的索引:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public  class  MapFileFixer {
 
   @SuppressWarnings ( "unchecked" )
   public  static  void  main(String[] args)  throws  Exception {
     String mapUri = args[ 0 ];
     
     Configuration conf =  new  Configuration();
     
     FileSystem fs = FileSystem.get(URI.create(mapUri), conf);
     Path map =  new  Path(mapUri);
     Path mapData =  new  Path(map, MapFile.DATA_FILE_NAME);
     
     // Get key and value types from data sequence file
     SequenceFile.Reader reader =  new  SequenceFile.Reader(fs, mapData, conf);//讀取文件
     Class keyClass = reader.getKeyClass();
     Class valueClass = reader.getValueClass();
     reader.close();
     
     // Create the map file index file
     long  entries = MapFile.fix(fs, map, keyClass, valueClass,  false , conf);
     System.out.printf( "Created MapFile %s with %d entries\n" , map, entries);
   }
}

fix () 方法常常用於重建被損壞的索引,但由於它從零開始建立新的索引,因此他 確實正是這裏須要的方法。

其餘文件和行式列式數據結構



雖然sequencefile和mapfile在hadoop系統中是最古老的文件格式,可是他們不是僅有的。許多其餘的文件結構可能比這兩個還要更好。咱們必需要考慮其餘的結構。

Avro數據文件和sequencefile和mapfile同樣,它們被設計用於大規模數據序列文件加工,緊湊和可分割,不一樣的編程語言均可以使用。存儲在Avro數據文件的數據經過一種
schema描述數據結構,而不是在一個能夠執行的Java代碼(如序列文件,就是使用的java硬代碼),使他們不在以java爲中心。Avro的數據文件在Hadoop的生態系統獲得普遍支持,對於二進制文件存儲傳輸他們是一個很好的默認選擇。

序列文件,映射文件,Avro數據文件都是面向行式的數據格式,這意味着,每行的值被連續地存儲在文件中。

在面向列的數據格式,在一個文件中的行(在Hive的表)被成行拆分,而後每一個分割以面向列的方式存儲:在每一行的第一列的值是第一個存儲的,隨後是每一行的第二列,依此類推。能夠參考下圖:

一個面向列式的數據儲存結構容許在查詢訪問的時候不須要訪問的列被跳過,這在oracle,mysql等行式數據儲存中是不能作到的,你要取數據就必須取出一行的數據。咱們能夠參考下面的圖,在邏輯表視圖中,咱們只是想取出第二列的數據,文件存儲在一個序列文件的時候,由於sequcenceFile是行式的,連續的,整個行(存儲在序列文件的一個記錄)都會被加載到存儲器中,即便咱們實際只是須要讀取第二列。雖然咱們能夠只反序列化咱們須要的那部分數據,從而節省了一些處理時間,但它不能避免從磁盤讀取全部字節時間和性能消耗成本。

在面向列的存儲結構中,只在第二列的數據(圖中描黑邊框)須要被讀入內存。在通常狀況下,查詢表中小數目的列的時候,面向列的存儲格式會更加高效。相反,須要查詢一行的大量列的數據的時候,行式數據存儲會更加有效。如何選擇取決於咱們的需求


列式數據存儲,須要更多的內存進行讀取和寫入由於它們在內存中緩衝行的分割的數據而不只僅是一個單行數據此外,沒法肯定什麼時間會寫入數據(經過刷新或同步操做列式存儲格式不適合流操做,由於讀寫過程失敗以後文件數據是不可以恢復的另外一方面,行式數據結構,如序列文件和Avro數據文件(Avro datafile),再發生IO錯誤以後,能夠讀取最後一個同步點正是出於這個緣由,Flume使用面向行式數據結構

Hadoop中的第一個列式文件結構HiveRCFile,是
Record  Columnar File的 簡稱 。如今 它已經被Hive的ORCFile, Parquet 取代 Parquet 基於谷歌 Dremel的 通用列式文件數據結構 ,獲得整個 的Hadoop 組件的 普遍支持。 Avro公司 有一個名爲 Trevni列式格式

到此爲止。明白了行式結構和列式結構對理解hive以及Hbase頗有幫助,但願個人總結和翻譯解讀可以幫助你們。
Charles 2015-12-27於Phnom Phen




版權說明:
本文由Charles Dong原創,本人支持開源以及免費有益的傳播,反對商業化謀利。
CSDN博客:http://blog.csdn.net/mrcharles
我的站:http://blog.xingbod.cn
EMAIL:charles@xingbod.cn
相關文章
相關標籤/搜索