HiveSerDe2及JsonSerDe源码分析(超详细)

本文详细分析了Hive中的SerDe2机制,特别是针对HiveSerDe2和JsonSerDe的源码进行了深入探讨。内容涵盖Hive如何将SQL语句转化为MapReduce任务处理HDFS上的JSON格式文件,以及Hive内置的各种SerDe,如Avro、ORC、Parquet等。同时,介绍了自定义SerDe的需求,并重点解析了序列化和反序列化的过程,包括Hive3.1.1中废弃serde类改用serde2的情况。文章还提供了部分官方源码以供读者理解。
摘要由CSDN通过智能技术生成

转载请注明出处

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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值