转载请注明出处
Hive的数据是存储在HDFS上的,在使用Hive执行操作的时候,实际上是将sql语句解释成MR程序对HDFS上的数据进行读写操作,而一般的,文件在网络传输和存储上都是以二进制(0和1组成的比特流)的方式分发的,这样,如果需要读写数据就要进行序列化和反序列化,这里,简称SerDe,即(Serializer and Deserializer)
通常,我们在HDFS上有一个JSON格式的文件,如果我们需要用Hive与它关联就需要使用以下语句并需要相对应的SerDe程序来对它进行读写
CREATE TABLE tb_name(
Field_1 Value_1 commend ,
Field_2 Value_2,
.....
Field_n Value_n
)
PARTITIONED BY(Field_1)
CLUSTERED BY(Field_4)
STORED BY(Field_1)
INTO 200 BUCKETS
ROW FORMAT SERDE'org.apache.hive.hcatalog.data.JsonSerDe'
STORED AS TEXTFILE
LOCATION 'HDFS_PATH'
这里,Hive内置了一些SerDe:
1.Avro
2.ORC
3.RegEx
4.Thrift
5.Parquet
6.CSV
7.JsonSerDe
但有时候我们需要根据自己的业务去定义自己的SerDe。
Hive的反序列化步骤:
HDFS File->InputFileFormat-><key,value>->Deserializer->Row Object
即通过inputfileformat函数读取HDFS上的文件组成KV键值对,然后反序列化为数据库表对象
Hive的序列化步骤:
Row Object->Serializer-><key,value>->OutputFileFormat->HDFS File
即将数据表对象序列化为KV键值对并通过OutputFileForma函数输出为HDFS文件
Hive序列化方法
这里我使用的是Hive3.1.1,serde类在这个版本已经弃用而使用serde2。
首先,要实现序列化和反序列化就要继承AbstractSerDe类
public abstract class AbstractSerDe implements Deserializer, Serializer
这里,AbstractSerDe类实现Serializer和Deserializer接口,用于序列化和反序列化文件对象。
public void initialize(Configuration configuration, Properties tableProperties,
Properties partitionProperties) throws SerDeException {
};
它有一个initialize方法,用于初始化我们创建的表
序列化方法
public abstract Writable serialize(Object obj, ObjectInspector objInspector)
throws SerDeException;
这里序列化方法有两个参数obj和objInspector,通过我们的序列化步骤,在序列化传过来的是Row对象即数据库表,obj实际就是数据库表数据,objInspector则是表的结构包括表的字段名和表字段对应的数据类型。
反序列化方法
public abstract Object deserialize(Writable blob) throws SerDeException;
这里的反序列化方法传入的是Writable类型的参数,写过MR程序的都知道这是用于读写HDFS文件的数据类型,这里blob参数即是读取的HDFS文件。
接下来,看一段官方源码。
JSONSerDe源码分析
public class JsonSerDe extends AbstractSerDe{
private static final Logger LOG = LoggerFactory.getLogger(TestJsonSerDe.class);
List<String> columnNames;
private StructTypeInfo schema;
private JsonFactory jsonFactory = null;
private TimestampParser tsParser;
private StandardStructObjectInspector cachedObjectInspector;
/**
* 初始化我们创建的表格并检查表格的格式和数据类型
* @param conf Hadoop
* @param tabl Hive table
* @see org.apache.hadoop.hive.serde2.AbstractSerDe#initialize(org.apache.hadoop.conf.Configuration, java.util.Properties, java.util.Properties)
*/
@Override
public void initialize(Configuration conf, Properties tabl) throws SerDeException{
List<TypeInfo> columnTypes;
StructTypeInfo rowTypeInfo;
// 获取所有表名和表中每列的数据类型
String columnName = tabl.getProperty(serdeConstants.LIST_COLUMNS);
String columnNameType = tabl.getProperty(serdeConstants.LIST_COLUMN_TYPES);
// 拆分规则,如果包含表名的话就按照表名拆分,否则按照逗号拆分
final String columnNameDelimiter = tabl.containsKey(serdeConstants.COLUMN_NAME_DELIMITER) ? tabl
.getProperty(serdeConstants.COLUMN_NAME_DELIMITER) : String.valueOf(SerDeUtils.COMMA);
LOG.debug("tabl:{}",tabl.entrySet());
if(columnName.isEmpty()) {
columnNames = Collections.emptyList();
}else {
columnNames = Arrays.asList(columnName.split(columnNameDelimiter));
}
if(columnNameType.isEmpty()) {
columnTypes = Collections.emptyList();
}else {
columnTypes = TypeInfoUtils.getTypeInfosFromTypeString(columnNameType);
}
LOG.debug("column:{},{}",columnName,columnNames);
LOG.debug("columnTypes:{},{}",columnNameType,columnTypes);
// 表名和类型一一对应就继续,否则中断执行
assert(columnNames.size() == columnTypes.size());
/*
* getStructTypeInfo方法诠释:
* 首先将List<String> names即表名和List<TypeInfo>即类型作为参数传进来
* 然后声明一个包含两个List的List signature(即 List signature<List<String> names, List<TypeInfo> typeinfos>)
* 并将names和TypeInfos添加进去
* 通过Hash表cachedStructTypeInfo将第一个List names作为key,第二个List TypeInfos作为value
* 通过name列表里的值获取TypeInfos列表相对应的值,
* 即检查columnName和TypeInfo是否以一一对应有无缺失
* 返回的是TypeInfo对象,即表的数据类型
*
* static ConcurrentHashMap<ArrayList<List<?>>, TypeInfo> cachedStructTypeInfo =
new ConcurrentHashMap<ArrayList<List<?>>, TypeInfo>();
* public static TypeInfo getStructTypeInfo(List<String> names, List<TypeInfo> typeInfos) {
ArrayList<List<?>> signature = new ArrayList<List<?>>(2);
signature.add(names);
signature.add(typeInfos);
TypeInfo result = cachedStructTypeInfo.get(signature);
if (result == null) {
result = new StructTypeInfo(names, typeInfos);
TypeInfo prev = cachedStructTypeInfo.putIfAbsent(signature, result);
if (prev != null) {
result = prev;
}
}
return result;
}
*/
rowTypeInfo = (StructTypeInfo) TypeInfoFactory.getStructTypeInfo(columnNames, columnTypes);
schema = rowTypeInfo;
LOG.debug("schema : {}", schema);
// 检查数据类型是否符合标准
cachedObjectInspector = (StandardStructObjectInspector) TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(rowTypeInfo);
jsonFactory = new JsonFactory();
/* serdeConstants.TIMESTAMP_FORMATS初始化时间轴
* HiveStringUtils.splitAndUnEscape获取时间并拆分成String[]
* TimestampParser将时间轴时间毫秒转换年月日
*/
tsParser = new TimestampParser(
HiveStringUtils.splitAndUnEscape(tabl.getProperty(serdeConstants.TIMESTAMP_FORMATS)));
}
/**
* 反序列化
* HDFS文件->InputFileFormat-><key,value>->Deserializer->Row对象
*
* 在initialize方法中我们已经初始化创建的表了,接下来需要将HDFS上的数据
* 序列化并与创建的表的栏目和数据类型相一致
* @param blob
* @return r
* @see org.apache.hadoop.hive.serde2.AbstractSerDe#deserialize(org.apache.hadoop.io.Writable)
*/
@Override
public Object deserialize(Writable blob) throws SerDeException{
Text t = (Text) blob;
JsonParser p;
// 创建一个长度为columnNames.size,值为null的list r
List<Object> r = new ArrayList<>(Collections.nCopies(columnNames.size(), null));
try {
// 读取HDFS上的JSON文件
p = jsonFactory.createJsonParser(new ByteArrayInputStream((t.getBytes())));
// 判断是不是JSON文件,没有捕获到"{"开始的标志
if (p.nextToken() != JsonToken.START_OBJECT) {
throw new IOException("Start token not found where expected");
}
JsonToken token;
while (((token = p.nextToken()) != JsonToken.END_OBJECT) && (token != null)) {
populateRecord(r, token, p, schema);
}
} catch (JsonParseException e) {
LOG.warn("Error [{}] parsing json text [{}].", e, t);
throw new SerDeException(e);
} catch (IOException e) {
LOG.warn("Error [{}] parsing json text [{}].", e, t);
throw new SerDeException(e);
}
return r;
}
/**
* @function:
* @param r
* @param token
* @param p
* @param s
* @throws IOException
*/
private void populateRecord(List<Object> r, JsonToken token, JsonParser p, StructTypeInfo s) throws IOException {
//
if (token != JsonToken.FIELD_NAME) {
throw new IOException("Field name expected");
}
// 将所有字母转换成小写
String fieldName = p.getText(