Hadoop的数据类型

在学习了Hadoop的基本架构以后,就可以试着编写Map和Reduce的来处理数据
但是,编写之前我们需要来了解一下Hadoop的基本数据类型

1. 为什么Hadoop有自己的数据类型

  hadoop由各个节点构成一个集群,分布式储存就要考虑到数据在节点之间来回传递的问题。为了解决这一问题,hadoop采用了java中的序列化和反序列化概念1。在Hadoop中,位于org . apache . hadoop . io包中的Writable接口是Hadoop序列化格式的实现。Writable接口实现了很多hadoop数据类型,它们将java的基本数据类型和一些常用的类(如字符串,数组,集合等)序列化,然后在节点之间传递,然后在目的地反序列化。可以把Hadoop的数据类型看作是java基本类型的封装。

你应该看明白了,不使用java的数据类型是为了提高序列化的效率。2因此Hadoop定义了一种序列化方式并与Java的数据类型包装在一起成为Hadoop的数据类型。阅读源码可以看到,Hadoop定义数据类型的类几乎都重载Writable中的方法

2. (Hadoop的数据类型)和(Hadoop与Java的数据类型的联系)

2.1. 一些基本的封装类型

类型名

hadoop数据类型

java数据类型

布尔型:

BooleanWritable

boolean

整型:

ByteWritable

ShortWritable

IntWritable

LongWritable

byte

short

int

long

浮点型:

FloatWritable

DoubleWritable

float

double

字符串(文本):

Text

String

数组:

ArrayWritable

Array

map集合:

MapWritable

map

空引用

null

NullWirteble


2.2. Writable的继承图


2.3. 定长格式(IntWritbale和LongWritable)和变长格式(VIntWritable 和 VLongWritable)


2.3.1. 对于定长格式和变长格式:

对整数进行编码时,有两种选择,即定长格式(IntWritbale和LongWritable)和变长格式(VIntWritable 和 VLongWritable).需要编码的数值如果想当小(在127和-127之间,包括-127和127之间 ),变长格式就是只用一个字节进行编码;否则使用第一个字节便是数值的正负和后跟多少个字节。

简而言之:定长格式和变长格式的区别在于write和readFields函数(序列化和反序列化)

2.3.2. 如何选择?

定长格式编码很适合数值在整个值域中非常均匀的情况,例如使用精心设计的哈希函数。然而,大多数数值变量分布不均匀,一般而言变长格式更加节省空间。对于LongWritable来说:变长格式的另一有点就是可以在VIntWritable和LongWritable之间转化,因为他们的编码实际上是一致的。所以选择变长更加省空间,不必开始就是8字节的long表示。

简而言之:如果不要求编码后在值域上均匀分布,就尽量用变长格式,因为它们更省空间


3. Hadoop和Java的数据类型相互转换的方法


3.1. java数据类型与hadoop数据类型的互换:

构造Hadoop的数据类型一般

  1. 用无参构造器new一个方法再set()函数赋值
  2. 用有参构造器构造(本质上还是使用set()方法来构造hadoop的数据类型)
//IntWritable的Java源码(部分)
  private int value;

  public IntWritable() {}

  public IntWritable(int value) { set(value); }

  /** Set the value of this IntWritable. */
  public void set(int value) { this.value = value; }

  /** Return the value of this IntWritable. */
  public int get() { return value; }

  其他的Hadoop的数据类型也是类似的方法来转化,但不完全一样比如:Text要转化为String用的是toString()这个方法,NullWirteble没有public的构造函数等等

示例:

IntWritable intWritable = new IntWritable(0);
int getIntFromIntWritable = intWritable.get();
System.out.println(getIntFromIntWritable);


4. Hadoop自定义数据类型和输入格式


4.1. 为什么要自定义数据类型

  Hadoop已经拥有这么多的基本数据类型,已经足够写出Hadoop的程序了。是不是就可以不用自定义数据类型了呢?当然是可以,但是就处理的数据结构复杂的数据就容易混乱。举个不恰当的比喻,如同C语言不用结构体,手动捆绑数据逻辑关系,不是不可以只是不推荐。

声明:我只是将原文的内容进行翻译和添加(运用google翻译,并修改了一些翻译不合理的地方,一些学过java都知道的单词不进行翻译)
建议看原文,我明明知道什么意思但是这玩意就是不好翻译(请见谅)

4.1.1. 概述

  Hadoop提供了各种writable classes 例如 IntWritable,Text,LongWritable,ObjectWritable等,它还为开发人员提供了使用API的灵活性并扩展了一些功能以实现组织目标。在此示例中,我们将讨论如何创建自定义writable classes并用这些类来编写map reduce程序。

4.1.2. 开发环境

Hadoop: 3.1.1
Java: Oracle JDK 1.8
IDE: IntelliJ Idea 2018.3

4.1.3. 创建自定义 “值” writable 数据类型的步骤

接下来讨论创建自定义writable数据类型(可以被使用在MapReduce作为"键"和"值".)的所有步骤 。 在接下来的例子中创建了一个writable数据类型(可以被使用在MapReduce作为"值".)。

4.1.3.1. 实现 Writable Interface

创建自定义的 writable 应该实现Writable interface作为MapReduce“值”.

4.1.3.2. write方法

重写write方法并添加逻辑(代码)以写入所有字段值(就是调用基本类型的write方法)。如果是列表或集合,请先写入集合变量的大小,然后将所有值写入其中。

4.1.3.3. readFields方法

重写readFields方法从输入流读取所有字段值。我们必须遵循相同的数据成员读写顺序(与重写write方法保持相同的读写顺序)。

4.1.3.4. 添加默认构造函数

最后一部是添加一个默认构造函数来 allow 序列化和反序列化自定义类型(allow我不知道怎么翻译,这里我觉得这里是提供支持的意思)

4.1.3.5. equals and hashCode方法

当使用我们自定义的 writable 作为 a map-reduce ”键“ 我们需要重写 equals and hashcode 方法 。

4.1.3.6. compareTo方法

重写compareTo方法并编写自定义数据类型排序的逻辑(代码)。此方法非常重要,如果该方法出错,可能会在reduce中得到错误结构键重复(duplicate key)等等。

4.1.4. 示例输入(Sample Input File)

1	14.623801,75.621788,greenwich,1,2
2	14.623801,75.621788,greenwich,2,2
3	14.623801,75.621788,greenwich,3,2
4	14.623801,75.621788,greenwich,4,4
5	14.623801,75.621788,greenwich,10,3
6	9.383452,76.574059,little venice,11,2
7	9.383452,76.574059,little venice,18,1
8	28.440554,74.493011,covent garden,16,1
9	28.440554,74.493011,covent garden,23,2
10	28.440554,74.493011,covent garden,34,3
11	28.440554,74.493011,covent garden,56,1
12	24.882618,72.858894,london bridge,87,1
13	16.779877,74.556374,camden town,67,1
14	16.779877,74.556374,camden town,54,4
15	16.779877,74.556374,camden town,54,4
16	27.085251,88.700928,hampstead,34,3
17	12.715035,77.281296,city of london,23,2
18	12.715035,77.281296,city of london,22,2
19	13.432515,77.727478,notting hill,98,3

passengerRush.txt 文件采用以下格式

<Sequence>\t<Latitude>,<Longitude>,<Location>,<TaxiID>,<NoOfPassenger>
顺序、纬度、经度、位置、出租车识别号、乘客人数

4.1.5. Example

4.1.5.1. 问题引入(Problem Statement)

有一个新项目来至一家出租车公司的需求,他们想分析伦敦市区不同地点的乘客高峰。这个出租车公司需要分析数据,并可以重新安排出租车以获得更多的钱。
出租车公司将收集过去2个月的所有出租车数据,我们的工作是解析此输入数据,并提供更多乘客使用出租车的地点名称。

4.1.5.2. 解决方案

正如我们在示例输入中看到的那样,我们具有纬度,经度,位置名称,以及所有的计程车ID以及乘客出行数量。我们需要编写自定义可写的地理位置数据类,并比较在哪个位置有更多的乘客乘坐出租车。

4.1.5.3. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>HadoopCustomKeyWritableExample</groupId>
    <artifactId>HadoopCustomKeyWritableExample</artifactId>
    <version>1.0-SNAPSHOT</version>
    <description>Hadoop Custom Key Writable Data Type,
        London City Passenger Rush Analysis
    </description>
    <build>
        <finalName>HadoopCustomKeyWritableDataTypeExample</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <useSystemClassLoader>false</useSystemClassLoader>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-mapreduce-client-core -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>3.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.1.1</version>
        </dependency>

        <!--
        hadoop-mapreduce-client-jobclient dependency for local debug
        -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-jobclient</artifactId>
            <version>3.1.1</version>
        </dependency>

    </dependencies>

</project>
4.1.5.4. GeoLocationWritable
package com.javadeveloperzone;

import org.apache.hadoop.io.*;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Objects;

public class GeoLocationWritable implements WritableComparable<GeoLocationWritable> {

    private DoubleWritable longitude,latitude;
    private Text locationName;

    //default constructor for (de)serialization
    public GeoLocationWritable() {
        longitude = new DoubleWritable(0.0d);
        latitude = new DoubleWritable(0.0d);
        locationName = new Text();
    }

    public GeoLocationWritable(DoubleWritable longitude, DoubleWritable latitude, Text locationName) {
        this.longitude = longitude;
        this.latitude = latitude;
        this.locationName = locationName;
    }

    public void write(DataOutput dataOutput) throws IOException {
        longitude.write(dataOutput);
        latitude.write(dataOutput);
        locationName.write(dataOutput);
    }

    public void readFields(DataInput dataInput) throws IOException {
        longitude.readFields(dataInput);
        latitude.readFields(dataInput);
        locationName.readFields(dataInput);
    }

    public DoubleWritable getLongitude() {
        return longitude;
    }

    public void setLongitude(DoubleWritable longitude) {
        this.longitude = longitude;
    }

    public DoubleWritable getLatitude() {
        return latitude;
    }

    public void setLatitude(DoubleWritable latitude) {
        this.latitude = latitude;
    }

    public int compareTo(GeoLocationWritable o) {
        int dist = Double.compare(this.getLatitude().get(),o.getLatitude().get());
        dist= dist==0 ? Double.compare(this.getLongitude().get(),o.getLongitude().get()):dist;
        if(dist==0){
            return this.getLocationName().toString().compareTo(o.locationName.toString());
        }else{
            return dist;
        }
    }

    public void setLocationName(Text locationName) {
        this.locationName = locationName;
    }

    public Text getLocationName() {
        return locationName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GeoLocationWritable that = (GeoLocationWritable) o;
        return Objects.equals(longitude, that.longitude) &&
                Objects.equals(latitude, that.latitude) &&
                Objects.equals(locationName, that.locationName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(longitude, latitude, locationName);
    }
}
4.1.5.5. PassengerRushMapper
package com.javadeveloperzone;

import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class PassengerRushMapper extends Mapper<Text,Text,GeoLocationWritable, IntWritable> {

    IntWritable intKey;
    GeoLocationWritable geoLocationWritable;

    @Override
    protected void setup(Context context){
        intKey = new IntWritable(0);
    }

    @Override
    protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {

        String taxiData[] = value.toString().split(",");
        intKey.set(Integer.parseInt(taxiData[4]));
        geoLocationWritable = new GeoLocationWritable();
        geoLocationWritable.setLatitude(new DoubleWritable(Double.parseDouble(taxiData[0])));
        geoLocationWritable.setLongitude(new DoubleWritable(Double.parseDouble(taxiData[1])));
        geoLocationWritable.setLocationName(new Text(taxiData[2]));

        context.write(geoLocationWritable,intKey );
    }

}
4.1.5.6. PassengerRushReducer
package com.javadeveloperzone;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class PassengerRushReducer extends Reducer<GeoLocationWritable, IntWritable, Text, IntWritable> {

    IntWritable totalPassenger;
    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        totalPassenger = new IntWritable(0);
    }

    @Override
    protected void reduce(GeoLocationWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        int sum = 0;
        for(IntWritable passegder : values){
            sum+=passegder.get();
        }
        totalPassenger.set(sum);
        context.write(new Text(key.getLocationName()),totalPassenger);
    }
}
4.1.5.7. PassengerRushAnalysisDriver
package com.javadeveloperzone;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;


public class PassengerRushAnalysisDriver extends Configured implements Tool {

    public static void main(String[] args) throws Exception {

        int exitCode = ToolRunner.run(new Configuration(),
                new PassengerRushAnalysisDriver(), args);
        System.exit(exitCode);
    }

    public int run(String[] args) throws Exception {

        if (args.length != 2) {
            System.out.println("Please provide two arguments :");
            System.out.println("[ 1 ] Input dir path");
            System.out.println("[ 2 ] Output dir path");
            return -1;
        }

        Configuration c=new Configuration();
        String[] files=new GenericOptionsParser(c,args).getRemainingArgs();
        Path input=new Path(files[0]);
        Path output=new Path(files[1]);
        Configuration conf=new Configuration();

        /*
        * UnComment below three lines to enable local debugging of map reduce job
         * */
        /*conf.set("fs.defaultFS", "local");
        conf.set("mapreduce.job.maps","1");
        conf.set("mapreduce.job.reduces","1");
        */

        Job job=Job.getInstance(conf,"Hadoop Custom Key Writable Example");
        job.setJarByClass(PassengerRushAnalysisDriver.class);
        job.setMapperClass(PassengerRushMapper.class);
        job.setReducerClass(PassengerRushReducer.class);
        job.setMapOutputKeyClass(GeoLocationWritable.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setOutputKeyClass(GeoLocationWritable.class);
        job.setOutputValueClass(IntWritable.class);
        job.setInputFormatClass(KeyValueTextInputFormat.class);
        job.setOutputFormatClass(TextOutputFormat.class);
        job.setNumReduceTasks(1);
        FileInputFormat.addInputPath(job, input);
        FileOutputFormat.setOutputPath(job, output);
        job.setSpeculativeExecution(false);
        boolean success = job.waitForCompletion(true);
        return (success?0:1);
    }
}

4.1.6. Build & Run

Hadoop自定义 “值” writable 已经完成。编译并创建jar文件,将示例输入文件复制到HDFS并运行以下命令。

hadoop jar HadoopCustomKeyWritableDataTypeExample.jar com.javadeveloperzone.PassengerRushAnalysisDriver /user/data/passengerrushinput /user/data/passengerrushoutput

4.1.7. Output

little venice	3
city of london	4
notting hill	3
greenwich	13
camden town	9
london bridge	1
hampstead	3
covent garden	7


  1. 序列化(serialization)是指将结构化的对象转化为字节流,以便在网络上传输或者写入到硬盘进行永久存储;相对的反序列化(deserialization)是指将字节流转回到结构化对象的过程。 ↩︎
  2. Java序列化保存信息太多会导致大量数据,而Hadoop的序列化产生的中间数据较少,不进行序列化。因此需要更少的时间和空间。 ↩︎