hive-TextInputformat自訂分隔字元

來源:互聯網
上載者:User
前言

在一次利用sqoop將關係型資料庫Oracle中的資料匯入到hive的測試中,出現了一個分割符的問題。oracle中有欄位中含有\n分行符號,由於hive預設是以’\n’作為換行分割符的,所以用sqoop將oracle中資料匯入到hive中導致hive中的資料條目跟未經處理資料庫不一致,當時的處理方式是資料在匯入到HDFS之前,用sqoop的參數將欄位中的分行符號都替換掉。

Sqoop在將資料從關係型資料庫匯入到HDFS時,支援將\n替換成自訂分行符號(支援單字元自訂分行符號),但是在hive中建表時用語句<row format delimited lines terminated by>指定自訂分行符號會提示如下錯誤:

< linesterminated by>參數目前僅支援’\n’。不能指定自訂分行符號,這樣自訂分行符號的資料就不能匯入到hive中,基於以上考慮,本文簡單說明了如何讓hive實現自訂多個字元的換行和欄位分割符,供大家參考。如有不足請批評指正。

環境

Hadoop:2.2 Hive:0.12(星環inceptor,支援原生hive)

目標

分析hive自訂多字串分行符號; 實現hive自訂多字串欄位分隔符號; 實現hivetextinputformat自訂編碼格式的設定。

1.hive的序列化與還原序列化

Hive中,預設使用的是TextInputFormat,一行表示一條記錄。在每條記錄(一行中),預設使用^A分割各個欄位。

在有些時候,我們往往面對多行,結構化的文檔,並需要將其匯入Hive處理,此時,就需要自訂InputFormat、OutputFormat,以及SerDe了。

首先來理清這三者之間的關係,我們直接引用Hive官方說法:

SerDe is a short name for “Serializer and Deserializer.”

Hive uses SerDe (and !FileFormat) to read and write table rows.

HDFS files –> InputFileFormat –> <key, value> –> Deserializer –> Row object

Row object –> Serializer –> <key, value> –> OutputFileFormat –> HDFS files

總結一下,面對一個HDFS上的檔案,Hive將如下處理(以讀為例):

(1) 調用InputFormat,將檔案切成不同的文檔。每篇文檔即一行(Row)。

(2) 調用SerDe的Deserializer,將一行(Row),切分為各個欄位。

 

當HIVE執行INSERT操作,將Row寫入檔案時,主要調用OutputFormat、SerDe的Seriliazer,順序與讀取相反。

針對含有自訂分行符號和欄位分隔符號的HDFS檔案,本文僅介紹hive讀取的過程的修改。 2 Hive預設採用的TextInputFormat類

首先建一個簡單的表,然後用<describe  extended >命令查看該表的詳細資料。

transwarp> create table test1(id int);OKTime taken: 0.062secondstranswarp>describe extended test1;OKid                  int                   None                                 Detailed Table Information     Table(tableName:test1, dbName:default, owner:root,createTime:1409300219, lastAccessTime:0, retention:0,sd:StorageDescriptor(cols:[FieldSchema(name:id, type:int,comment:null)],location:hdfs://leezq-vm3:8020/inceptor1/user/hive/warehouse/test1,inputFormat:org.apache.hadoop.mapred.TextInputFormat,outputFormat:org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat,compressed:false,numBuckets:-1,serdeInfo:SerDeInfo(name:null,serializationLib:org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe,parameters:{serialization.format=1}),bucketCols:[], sortCols:[], parameters:{},skewedInfo:SkewedInfo(skewedColNames:[], skewedColValues:[],skewedColValueLocationMaps:{}), storedAsSubDirectories:false),partitionKeys:[],parameters:{transient_lastDdlTime=1409300219},viewOriginalText:null, viewExpandedText:null,tableType:MANAGED_TABLE)       Time taken: 0.121 seconds, Fetched: 3 row(s)

從上面可以看出,預設狀態下,hive的輸入和輸出調用的類分別為:

inputFormat:org.apache.hadoop.mapred.TextInputFormat,outputFormat:org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat,

雖然現在現在hadoop現在升級到2.X版本,hive依然採用老版的mapred介面。

我們要改寫的就是類TextInputFormat。 2.1類 TextInputFormat

類TextInputFormat在hadoop-mapreduce-client-core-2.2.0.jar中。

重點看類中getRecordReader方法,該方法返回LineRecordReader對象。並且該方法中已經實現了接收自訂字串作為分行符號的代碼,只要建表前在hive的CLI介面上輸入set textinputformat.record.delimiter=<自訂換行字串>;即可實現自訂多字元分行符號。


2.2類LineRecordReader

為了進一步查看其實現原理,我們進一步看LineRecordReader(package org.apache.hadoop.mapred. LineRecordReader)類。

查看該類的建構函式,該類調用org.apache.hadoop.util.LineReader(在包hadoop-common-2.2.0.jar中)擷取每行的資料,把參數recordDelimiter傳給類對象LineReader,類LineReader中的readLine(Text str, int maxLineLength, intmaxBytesToConsume)方法負責按照使用者自訂分隔字元返回每行的長度,如果使用者不設定 textinputformat.record.delimiter的值,recordDelimiter的值為null,這時readLine方法就會按照預設’\n’分割每行。readLine的代碼如下:


通過讀源碼可以看到,原始的hive可以通過設定參數的方法實現多字元自訂分行符號(textFile的儲存方式),通過上圖中readCustomLine方法獲得使用者自訂分行符號的字串實現自動換行,每行最大可支援2147483648大小。但是要想實現自訂多字元的欄位分隔符號和自訂編碼格式的設定,還需要對源碼進行改寫。下面就講一下改寫的步驟。

3 自訂TextInputFormat 實現自訂多字串的欄位分割符 實現自訂編碼格式的設定

首先建一個空的java工程,添加必須的五個包


然後建立兩個類SQPTextInputFormat和SQPRecordReader,將TextInputFormat和LineRecordReader的代碼分別拷貝過來。

在SQPTextInputFormat中添加對自訂編碼格式的設定。(對分行符號的參數進行了更名,將textinputformat.record.delimiter改成了textinputformat.record.linesep)

//======================================================  String delimiter = job.get("textinputformat.record.linesep");  this.encoding = job.get("textinputformat.record.encoding",defaultEncoding);  byte[] recordDelimiterBytes = null;  if (null != delimiter) {//Charsets.UTF_8    recordDelimiterBytes = delimiter.getBytes(this.encoding);  }  return new SQPRecordReader(job, (FileSplit)genericSplit, recordDelimiterBytes); 

在SQPRecordReader建構函式中添加對欄位分隔符號和編碼格式的設定。

//======================================================    this.FieldSep = job.get("textinputformat.record.fieldsep",defaultFSep);this.encoding = job.get("textinputformat.record.encoding",defaultEncoding); 

在SQPRecordReader的next()方法中添加對欄位分割符的替換和對編碼格式的設定。

//======================================================    if (encoding.compareTo(defaultEncoding) != 0) {              String str = new String(value.getBytes(), 0,value.getLength(), encoding);              value.set(str);         }      if (FieldSep.compareTo(defaultFSep) != 0) {              String replacedValue = value.toString().replace(FieldSep, defaultFSep);              value.set(replacedValue);

詳細的代碼如下:

package com.learn.util.hadoop;//import com.google.common.base.Charsets;import java.io.IOException;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.io.compress.CompressionCodec;import org.apache.hadoop.io.compress.CompressionCodecFactory;import org.apache.hadoop.io.compress.SplittableCompressionCodec;import org.apache.hadoop.mapred.*;public class SQPTextInputFormat extends FileInputFormat<LongWritable, Text>implements JobConfigurable{private CompressionCodecFactory compressionCodecs = null;private final static String defaultEncoding = "UTF-8";//"US-ASCII""ISO-8859-1""UTF-8""UTF-16BE""UTF-16LE""UTF-16"private String encoding = null;public void configure(JobConf conf) {  this.compressionCodecs = new CompressionCodecFactory(conf);}protected boolean isSplitable(FileSystem fs, Path file) {  CompressionCodec codec = this.compressionCodecs.getCodec(file);  if (null == codec) {    return true;  }  return codec instanceof SplittableCompressionCodec;}public RecordReader<LongWritable, Text> getRecordReader(InputSplit genericSplit, JobConf job, Reporter reporter)  throws IOException{  reporter.setStatus(genericSplit.toString());  String delimiter = job.get("textinputformat.record.linesep");  this.encoding = job.get("textinputformat.record.encoding",defaultEncoding);  byte[] recordDelimiterBytes = null;  if (null != delimiter) {//Charsets.UTF_8    recordDelimiterBytes = delimiter.getBytes(this.encoding);  }  return new SQPRecordReader(job, (FileSplit)genericSplit, recordDelimiterBytes);}}


package com.learn.util.hadoop;import java.io.IOException;import java.io.InputStream;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate;import org.apache.hadoop.classification.InterfaceStability.Unstable;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FSDataInputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.fs.Seekable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.io.compress.CodecPool;import org.apache.hadoop.io.compress.CompressionCodec;import org.apache.hadoop.io.compress.CompressionCodecFactory;import org.apache.hadoop.io.compress.Decompressor;import org.apache.hadoop.io.compress.SplitCompressionInputStream;import org.apache.hadoop.io.compress.SplittableCompressionCodec;import org.apache.hadoop.io.compress.SplittableCompressionCodec.READ_MODE;import org.apache.hadoop.util.LineReader;import org.apache.hadoop.mapred.RecordReader;import org.apache.hadoop.mapred.FileSplit;//@InterfaceAudience.LimitedPrivate({"MapReduce", "Pig"})//@InterfaceStability.Unstablepublic class SQPRecordReader  implements RecordReader<LongWritable, Text>{  private static final Log LOG = LogFactory.getLog(SQPRecordReader.class.getName());  private CompressionCodecFactory compressionCodecs = null;  private long start;  private long pos;  private long end;  private LineReader in;  private FSDataInputStream fileIn;  private final Seekable filePosition;  int maxLineLength;  private CompressionCodec codec;  private Decompressor decompressor;  private String FieldSep;//field separator  private static final String defaultFSep="\001";  private final static String defaultEncoding = "UTF-8";//"US-ASCII""ISO-8859-1""UTF-8""UTF-16BE""UTF-16LE""UTF-16"  private String encoding = null;  public SQPRecordReader(Configuration job, FileSplit split)    throws IOException  {    this(job, split, null);  }  public SQPRecordReader(Configuration job, FileSplit split, byte[] recordDelimiter) throws IOException  {    this.maxLineLength = job.getInt("mapreduce.input.linerecordreader.line.maxlength", 2147483647);    this.FieldSep = job.get("textinputformat.record.fieldsep",defaultFSep);    this.encoding = job.get("textinputformat.record.encoding",defaultEncoding);    this.start = split.getStart();    this.end = (this.start + split.getLength());    Path file = split.getPath();    this.compressionCodecs = new CompressionCodecFactory(job);    this.codec = this.compressionCodecs.getCodec(file);    FileSystem fs = file.getFileSystem(job);    this.fileIn = fs.open(file);    if (isCompressedInput()) {      this.decompressor = CodecPool.getDecompressor(this.codec);      if ((this.codec instanceof SplittableCompressionCodec)) {        SplitCompressionInputStream cIn = ((SplittableCompressionCodec)this.codec).createInputStream(this.fileIn, this.decompressor, this.start, this.end, SplittableCompressionCodec.READ_MODE.BYBLOCK);        this.in = new LineReader(cIn, job, recordDelimiter);        this.start = cIn.getAdjustedStart();        this.end = cIn.getAdjustedEnd();        this.filePosition = cIn;      } else {        this.in = new LineReader(this.codec.createInputStream(this.fileIn, this.decompressor), job, recordDelimiter);        this.filePosition = this.fileIn;      }    } else {      this.fileIn.seek(this.start);      this.in = new LineReader(this.fileIn, job, recordDelimiter);      this.filePosition = this.fileIn;    }    if (this.start != 0L) {      this.start += this.in.readLine(new Text(), 0, maxBytesToConsume(this.start));    }    this.pos = this.start;  }  public SQPRecordReader(InputStream in, long offset, long endOffset, int maxLineLength)  {    this(in, offset, endOffset, maxLineLength, null);  }  public SQPRecordReader(InputStream in, long offset, long endOffset, int maxLineLength, byte[] recordDelimiter)  {    this.maxLineLength = maxLineLength;    this.in = new LineReader(in, recordDelimiter);    this.start = offset;    this.pos = offset;    this.end = endOffset;    this.filePosition = null;  }  public SQPRecordReader(InputStream in, long offset, long endOffset, Configuration job)    throws IOException  {    this(in, offset, endOffset, job, null);  }  public SQPRecordReader(InputStream in, long offset, long endOffset, Configuration job, byte[] recordDelimiter)    throws IOException  {    this.maxLineLength = job.getInt("mapreduce.input.linerecordreader.line.maxlength", 2147483647);    this.in = new LineReader(in, job, recordDelimiter);    this.start = offset;    this.pos = offset;    this.end = endOffset;    this.filePosition = null;  }  public LongWritable createKey() {    return new LongWritable();  }  public Text createValue() {    return new Text();  }  private boolean isCompressedInput() {    return this.codec != null;  }  private int maxBytesToConsume(long pos) {    return isCompressedInput() ? 2147483647 : (int)Math.min(2147483647L, this.end - pos);  }  private long getFilePosition()    throws IOException  {    long retVal;    if ((isCompressedInput()) && (null != this.filePosition))      retVal = this.filePosition.getPos();    else {      retVal = this.pos;    }    return retVal;  }  public synchronized boolean next(LongWritable key, Text value)    throws IOException  {    while (getFilePosition() <= this.end) {      key.set(this.pos);      int newSize = this.in.readLine(value, this.maxLineLength, Math.max(maxBytesToConsume(this.pos), this.maxLineLength));            if (newSize == 0) {        return false;      }            if (encoding.compareTo(defaultEncoding) != 0) {String str = new String(value.getBytes(), 0, value.getLength(), encoding);value.set(str);}            if (FieldSep.compareTo(defaultFSep) != 0) {String replacedValue = value.toString().replace(FieldSep, defaultFSep);value.set(replacedValue);}            this.pos += newSize;      if (newSize < this.maxLineLength) {        return true;      }      LOG.info("Skipped line of size " + newSize + " at pos " + (this.pos - newSize));    }    return false;  }  public synchronized float getProgress()    throws IOException  {    if (this.start == this.end) {      return 0.0F;    }    return Math.min(1.0F, (float)(getFilePosition() - this.start) / (float)(this.end - this.start));  }  public synchronized long getPos() throws IOException  {    return this.pos;  }  public synchronized void close() throws IOException {    try {      if (this.in != null)        this.in.close();    }    finally {      if (this.decompressor != null)        CodecPool.returnDecompressor(this.decompressor);    }  }}

4 自訂InputFormat的使用

1.      將程式打成jar包,放在/usr/lib/hive/lib和各個節點的/usr/lib/hadoop-mapreduce目錄下。

在hvie的CLI命令列介面可以設定如下參數,分別修改編碼格式、自訂欄位分隔字元和自訂分行符號。

set textinputformat.record.encoding=UTF-8;//"US-ASCII""ISO-8859-1""UTF-8""UTF-16BE""UTF-16LE""UTF-16"set textinputformat.record.fieldsep=,;set textinputformat.record.linesep=|+|;


2.      建表,標示採用的Inputformat和OutputFormat,其中org.apach…noreKeyTextOutputFormat 是hive預設的OutputFormat分隔字元。

create table test(id string,name string)stored asINPUTFORMAT'com.learn.util.hadoop.SQPTextInputFormat'OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'

3.      Load 語句載入資料 執行個體

測試資料:


測試資料中有一個欄位中含有分行符號。欄位分隔符號和行分隔字元分別為’,’和“|+|”。

分別設定欄位分隔符號和行分割符,並建表指定Inputformat和outputformat如下圖所示,

Select  * 查詢如下:

Select count(*)如下:

結果是3行,正確。

Select  id  from test1如下:

Select  name from test1:

Select  count(name) from test1:


結果正確。

Select  name,id from test1:

Select  id,name from test1;

Id和name兩個欄位單獨查沒問題,但是調用mapreduce一起查的時候帶有‘\n’的欄位顯示上出了問題。

Select  id,name from test1 where id=13:

單獨查詢每個欄位時候和查詢總行數的時候都是沒問題的,這說明改寫的InputFormat起作用了,上面的出現的NULL問題應該是hive顯示的問題。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.