标题：hive
Hive定义
Hive[1] 是建立在 Hadoop 上的数据仓库基础构架。它提供了一系列的工具，可以用来进行数据提取转化加载（ETL），这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。Hive 定义了简单的类 SQL 查询语言，称为 HQL，它允许熟悉 SQL 的用户查询数据。同时，这个语言也允许熟悉 MapReduce 开发者的开发自定义的 mapper 和 reducer 来处理内建的 mapper 和 reducer 无法完成的复杂的分析工作。
Hive 没有专门的数据格式。 Hive 可以很好的工作在 Thrift 之上，控制分隔符，也允许用户指定数据格式。
Hive 体系结构
主要分为以下几个部分：　　用户接口用户接口主要有三个：CLI，Client 和 WUI。其中最常用的是 CLI，Cli 启动的时候，会同时启动一个 Hive 副本。Client 是 Hive 的客户端，用户连接至 Hive Server。在启动 Client 模式的时候，需要指出 Hive Server 所在节点，并且在该节点启动 Hive Server。 WUI 是通过浏览器访问 Hive。元数据存储Hive 将元数据存储在数据库中，如 mysql、derby。Hive 中的元数据包括表的名字，表的列和分区及其属性，表的属性（是否为外部表等），表的数据所在目录等。解释器、编译器、优化器、执行器。解释器、编译器、优化器完成 HQL 查询语句从词法分析、语法分析、编译、优化以及查询计划的生成。生成的查询计划存储在 HDFS 中，并在随后有 MapReduce 调用执行。HadoopHive 的数据存储在 HDFS 中，大部分的查询由 MapReduce 完成（包含 * 的查询，比如 select * from tbl 不会生成 MapRedcue 任务）。
Hive 的数据存储
首先，Hive 没有专门的数据存储格式，也没有为数据建立索引，用户可以非常自由的组织 Hive 中的表，只需要在创建表的时候告诉 Hive 数据中的列分隔符和行分隔符，Hive 就可以解析数据。
其次，Hive 中所有的数据都存储在 HDFS 中，Hive 中包含以下数据模型：Table，External Table，Partition，Bucket。
Hive 中的 Table 和数据库中的 Table 在概念上是类似的，每一个 Table 在 Hive 中都有一个相应的目录存储数据。例如，一个表 pvs，它在 HDFS 中的路径为：/wh/pvs，其中，wh 是在 hive-site.xml 中由 ${hive.metastore.warehouse.dir} 指定的数据仓库的目录，所有的 Table 数据（不包括 External Table）都保存在这个目录中。
Partition 对应于数据库中的 Partition 列的密集索引，但是 Hive 中 Partition 的组织方式和数据库中的很不相同。在 Hive 中，表中的一个 Partition 对应于表下的一个目录，所有的 Partition 的数据都存储在对应的目录中。例如：pvs 表中包含 ds 和 city 两个 Partition，则对应于 ds = 20090801, ctry = US 的 HDFS 子目录为：/wh/pvs/ds=20090801/ctry=US；对应于 ds = 20090801, ctry = CA 的 HDFS 子目录为；/wh/pvs/ds=20090801/ctry=CA
Buckets 对指定列计算 hash，根据 hash 值切分数据，目的是为了并行，每一个 Bucket 对应一个文件。将 user 列分散至 32 个 bucket，首先对 user 列的值计算 hash，对应 hash 值为 0 的 HDFS 目录为：/wh/pvs/ds=20090801/ctry=US/part-00000；hash 值为 20 的 HDFS 目录为：/wh/pvs/ds=20090801/ctry=US/part-00020
External Table 指向已经在 HDFS 中存在的数据，可以创建 Partition。它和 Table 在元数据的组织上是相同的，而实际数据的存储则有较大的差异。
Table 的创建过程和数据加载过程（这两个过程可以在同一个语句中完成），在加载数据的过程中，实际数据会被移动到数据仓库目录中；之后对数据对访问将会直接在数据仓库目录中完成。删除表时，表中的数据和元数据将会被同时删除。
External Table 只有一个过程，加载数据和创建表同时完成（CREATE EXTERNAL TABLE ……LOCATION），实际数据是存储在 LOCATION 后面指定的 HDFS 路径中，并不会移动到数据仓库目录中。当删除一个 External Table 时，仅删除

Windows注册表HⅣE文件
相信大家对Windows系统的注册表（registry）一定都不陌生了，我们可以用系统提供的注册表编辑器（regedit）来访问和修改注册表中的数据。直观的讲，注册表呈现出来的是图1所示的形式，它由根键（rootkey）、子键（subkey）、键值（value）和数据（data）组成。数据之间有  hive类型的分别，常见的有：REG_SZ、字符串型，REG_BINARY、二进制型， REG_DWORD、双字型，REG_MULTI_SZ、多字符串值型和REG_EXPAND_SZ、长度可变的数据串型。
注册表相当于Windows系统中所有32位硬件/驱动和32位应用程序的数据文件，是一个系统信息的数据库。既然是数据文件，那在磁盘上就一定有注册表的影子的存在。Windows 2000/XP的注册表文件在系统设置和缺省用户配置数据的情况下，是存放在\系统文件夹 \SYSTEM32\CONFIG目录下的6个文件，DEFAULT、SAM、SECURITY、SOFTWARE、USERDIFF和SYSTEM中，而用户的配置信息存放在系统所在磁盘的\Documents and Setting\目录，包括ntuser.dat，ntuser.ini和ntuser.dat.log。其中每个文件的路径都由注册表项HKLM \SYSTEM\CurrentControlSet\Control\HⅣElist下的键值指出。
我们看到的注册表结构是经过注册表编辑器读取之后呈现给我们的，其磁盘形式并不是一个简单的大文件，而是一组称被为HⅣE的单独文件形式，HⅣE中文名曰“储巢”。每个HⅣE文件可以被理解为一棵单独注册表树，就像Windows的PE格式一样，它也有自己的组织形式。本文的任务就是要分析HⅣE文件的组织形式并完成一个HⅣE格式的分析程序。
注册表API工作原理简述
Windows系统提供了大量的API给用户访问和修改注册表中的数据，regedit就是基于这些API所实现的。注册表API大致分为用户空间的与内核空间的两类，一般用户调用前者，层层调用转移，由内核的注册表API再调用文件系统的驱动等，去访问磁盘上的HⅣE文件，并最终返回请求的数据结果。这个过程有点冗长，但是为了注册表里存放数据的安全考虑，损失一些性能表现还是值得的。
HⅣE结构解析
在认识真正的HⅣE文件之前，我们先列举HⅣE文件的几个主要特征。先入为主的将它们呈现出来将有助于我们对其文件组织和数据结构的理解。
注册表由多个HⅣE文件组成。一个HⅣE文件由多个巢箱（BIN）组成HⅣE文件的首部有一个文件头（基本块、base block），用于描述这个HⅣE文件的一些全局信息
一个BIN由多个巢室（CELL）组成，CELL可以分为具体的5种（后面介绍），用于存储不同的注册表数据。
本文中，我们并不统一使用HⅣE、BIN和CELL的英文单词，而是和对应的中文词汇交替出现。在中文里，它们分别对应储巢、巢箱和巢室三个名词。
一个储巢被看成是一些称为块（block）的分配单元，类似于将磁盘分为簇的形式。根据定义，每一个注册表块的大小为4096字节（4KB），当新的数据要加入到一个储巢中来时，该储巢总是按照块的粒度来增加。一个储巢的第一个块是基本块（base block），包含了有关该储巢的全局信息，包括一个特征签名“regf”，更新序列号，储巢上一次写操作发生的时间戳，储巢格式版本号、检验和，以及该储巢文件的内部文件名等等。下面的_HBASE_BLOCK就是一个基本块的数据结构还原。
typedef struct _HBASE_BLOCK
{
ULONG Signature; /* 签名ASCⅡ-"regf" = 0x66676572 （小端序）*/
ULONG Sequence1; 
ULONG Sequence2; 
LARGE_INTEGER TimeStamp; /* 最后一次写操作的时间戳 */
ULONG Major; /* 主版本号 */
ULONG Minor; /* 次版本号 */
ULONG Type; 
ULONG Format;
ULONG RootCell; /* 第一个键记录的偏移 */
ULONG Length; /* 数据块长度 */
ULONG Cluster; 
UCHAR name[64]; /* 储巢文件名 */
ULONG Reserved1[99];
ULONG CheckSum; /* 校验和 */
ULONG Reserved2[894];
ULONG BootType;
ULONG BootRecover;
} HBASE_BLOCK,*PHBASE_BLOCK;
Windows将一个储巢所存储的注册表条目组织在一种称为巢室的容器中，当一个巢室加入到一个储巢中，而且该巢室必须经过扩展才能容纳该巢室时，系统将创建一个巢箱的分配单元。巢箱是新巢室正好扩展到下一个块的边界的大小，系统将巢室的尾部和巢箱的尾部之间的任何空间都看作是空闲空间，因而可以分配其他的巢室。
巢箱也有头部的标识，包含了一个特殊的签名“hbin”，一个记录了该巢箱在储巢文件中偏移量的域，以及该巢箱的大小。下面是巢箱的数据结构。
typedef struct_HBIN
{
ULONG Signature; /* 签名 ASCⅡ-"hbin" = 0x6E696268 （小端序） */
ULONG FileOffset; /* 本巢箱相对第一个巢箱起始的偏移 */
ULONG Size; /* 本巢箱的大小*/
ULONG Reserved1[2];
LARGE_INTEGERTimeStamp;
ULONG Spare;
} HBIN,*PHBIN;
一个巢室可以容纳一个键、一个值、一个安全描述符、一列子键或者一列键值，分别有对应的巢室来存储数据。在巢室数据的开始之处，有一个数据域描述了该巢室数据的类型，具体的数据结构如下：
键巢室，包含了一个注册表键（也称为键节点）的巢室，一个键巢室包含一个特征签名（对于一个键是kn，一个符号链接是kl）、该键最近一次更新的时间戳、该键父键巢室的巢室索引、代表该键的子键的子键列表巢室的索引、该键的安全描述符巢室索引、一个代表该键类名的字符串键巢室索引，以及该键的名称。
typedef struct _CM_KEY_NODE
{
USHORT Signature; /*签名ASCⅡ-"kn" = 0x6B6E （小端序）*/
USHORT Flags; /*根键标识： 0x2C，其他为 0x20 */
LARGE_INTEGER LastWriteTime;
ULONG Spare;
ULONG Parent; /* 父键的偏移 */
ULONG SubKeyCounts[2]; /*SubKeyCounts[0]为子键的个数 */ 
union /*偏移为0x001C 联合体 */
{
struct
{
ULONG SubKeyLists[2]; /* SubKeyLists[0]为子键列表相差本BIN的偏移 */
CHILD_LIST ValueList; /* ValueList结构体 */
};
ULONG ChildHiveReference[4];
}; 
ULONG Security; /*安全描述符记录的偏移 */
ULONG Class; /*类名的偏移*/ 
ULONG MaxNameLen: 16;
ULONG UserFlags: 4;
ULONG VirtControlFlags: 4;
ULONG Debug: 8;
ULONG MaxClassLen;
ULONG MaxValueNameLen;
ULONG MaxValueDataLen;
ULONG WorkVar;
USHORT NameLength; /* 键名长度 */
USHORT ClassLength; /*类名长度 */
PBYTE Name; /*键名称 */
}CM_KEY_NODE,*PCM_KEY_NODE;
值巢室，一个巢室，包含了关于一个键的值的信息，该巢室包含一个签名kv，该值的类型，如REG_DWORD或REG_BINARY，以及该值的名称。一个值巢室也包含了另一个值巢室的索引，后者包含了对前者的数据。
typedef struct _CM_KEY_VALUE
{
WORD Signature; /* 签名ASCⅡ-"kv" = 0x6B76（小端序） */
WORD NameLength; /* 名称长度 */
ULONG DataLength; /* 数据长度 */
ULONG Data; /*数据偏移或数据，如果DataLength最高位为1，那么它就是数据，
且DataLenth&0x7FFFFFFF为数据长度；否则 */
ULONG Type; /* 值类型 */
WORD Flags; 
WORD Spare; 
PWCHAR Name; /* 值名称 */
} CM_KEY_VALUE,*PCM_KEY_VALUE;
子键列表巢室，有一系列的键巢室的巢室索引构成的巢室，这些键巢室是同一个父键下面的所有子键。
typedef struct _CM_KEY_INDEX
{
WORD Signature;
WORD Count;
ULONG List[1];
} CM_KEY_INDEX,*PCM_KEY_INDEX;
如果Signature==CM_KEY_FAST_LEAF，签名为“fl”，或者Signature==CM_KEY_HASH_LEAF，签名为 “hl”，那么List后是一个结构体：
struct
{
ULONG offset;
ULONG HashKey;
}
否则为：ULONG offset;
值列表巢室，有一系列的值巢室的巢室索引构成的巢室，这些值巢室是同一个父键下面的所有值。其数据结构即上文说到的结构。即上面 _CM_KEY_NODE的联合体中ValueList数据域。
typedef struct _CHILD_LIST
{
ULONG Count; /* ValueList.Count值的个数 */
ULONG List; /* ValueList.List值列表相差本BIN的偏移 */
} CHILD_LIST,*PCHILD_LIST;
安全描述符巢室，包含了一个安全描述符巢室，其首部的特征签名为ks，以及一个引用计数，该引用计数值记录了所有共享安全描述符的键节点数目，多个键巢室可以共享同样的安全描述符巢室。
typedef struct _CM_KEY_SECURITY
{
WORD Signature; /* 签名ASCⅡ-"sk" = 0x6B73 （小端序）*/
WORD Reserved;
ULONG Flink; /*上一个"sk"记录的偏移 */
ULONG Blink; /*下一个"sk"记录的偏移 */
ULONG ReferenceCount; /* 引用计数 */
ULONG DescriptorLength; /* 数据大小*/
SECURITY_DESCRIPTOR_RELATⅣE Descriptor; /* 数据 */
} CM_KEY_SECURITY,*PCM_KEY_SECURITY;
储巢的结构是通过一些链接建立起来的，这些链接称为巢室索引（cell index）。每个巢室索引是一个巢室在储巢文件中的偏移。因此，巢室索引就像是一个指针，从一个巢室指向另一个巢室，配置管理器将巢室索引解释为相对于储巢起始处的偏移。因此，假如你想找到子键A的键巢室，并且A的父键是B，那么就必须先利用B的巢室中的子键列表巢室索引，找到包含B的所有子键列表的那个巢室，然后再利用该子键列表巢室中的巢室索引列表，找到B的每个子键的巢室，随即找到A。
巢室，巢箱和块之间的区别很容易让人混淆，所以我们来看一个简单的注册表储巢的布局示例，如图5。该示例中包含了一个基本块和两个巢箱，第一个巢箱是空的，第二个巢箱包含了几个巢室。该巢室有两个键，一个是根键Root，另一个是Root的子键——SubKey。Root有两个值，Val1和 Val2，通过一个子键列表巢室，可以定位到根键的子键，通过一个值列表巢室，可以定位到根键的值。第二个巢箱中，空闲的空间属于空的巢室。
获得HⅣE文件
知道了HⅣE们的存放位置，自然就想到要把它们抓过来，逐个解剖。抓捕工作看似棘手，但解决起来也很简单。HⅣE是Windows的重要资源，自启动以来就只能被系统独占访问。我们换一种思路，在另外一个系统中启动，如同一台机器上的Linux，那目前系统的HⅣE文件不是就可以访问了吗？但是这里如果你非要在当前系统访问这个HⅣE文件，就只有求助于文件系统驱动了。后者已经超出了本文的讨论范围，故我们不作考虑。
不过，为了示例学习的需要，我们总希望HⅣE文件能相对简单一些，让我们把它的结构看个清楚明白。系统内部的HⅣE文件一般都不太适合，从其文件尺寸已经达到MB级别，就可看出其数据量的巨大。因此，初期我们需要自己建立一个小型的HⅣE以供学习之用。
为了以后叙述的方便，我们在HKLM\SAM下建立了子键test_root，然后在test_root下再建立两个子键1test和2test，并且在 1test下新建了五种不同的值，并填写了相应的数据.然后，用RegSaveKey函数编个小程序，把test_root保存为HⅣE文件。这样 test_root就变成这个HⅣE文件的根键。接下来，我们就可以细细剖析HⅣE文件中每一个部分的结构和功能了。
HⅣE格式实例分析
我们用一个16进制编辑器打开test_root文件，首先就可以看到基本块的签名——“regf”字符串，这是registry file的缩写，标志它是个注册表文件...
HⅣE文件读取程序
基于上面的结构解释和分析，我们可以写出一个HⅣE文件的读取程序。针对上述分析示例的读取效果，test_root下有子键1test_subkey和2test，前者有1_REG_SZ、2_REG_BINARY、3_REG_DWORD、 4_REG_MULIT_SZ和5_REG_EXPAND_SZ 5个键值。[]内的是键值的类型，（）内的是值的长度，以字节为单位，对于REG_SZ型数据是打印出其unicode的编码。