自定义Writable
Hadoop虽然 已经实现了一些非常有用的Writable,而且你可以使用他们的组合做很多事情,但是如果你想构造一些更加复杂的结果,你可以自定义Writable来达到你的目的,我们以注释的方式对自定义Writable进行讲解
/**
* 自定义Writable通常都要实现Writable接口
* 如果有比较大小的业务,最好是实现WritableComparable接口
* @author 廖*民
* time : 2015年1月13日下午1:39:12
* @version
*/
public class EmployeeWritable implements WritableComparable<EmployeeWritable>{
//姓名
private Text name;
//角色
private Text role;
//必须提供无参构造方法(一定要创建name和role对象否则会报空指针异常)
public EmployeeWritable() {
name = new Text();
role = new Text();
}
//构造函数
public EmployeeWritable(Text name, Text role) {
this.name = name;
this.role = role;
}
public Text getName() {
return name;
}
public void setName(Text name) {
this.name = name;
}
public Text getRole() {
return role;
}
public void setRole(Text role) {
this.role = role;
}
/**
* 调用成员对象本身的readFields()方法,从输入流中反序列化每一个成员对象
*/
public void readFields(DataInput dataInput) throws IOException {
name.readFields(dataInput);
role.readFields(dataInput);
}
/**
* 通过成员对象本身的write方法,序列化每一个成员对象到输出流中
*/
public void write(DataOutput dataOutput) throws IOException {
name.write(dataOutput);
role.write(dataOutput);
}
/**
* 如果实现了WritableComparable接口必须实现compareTo方法,用于比较
*/
public int compareTo(EmployeeWritable employeeWritable) {
int cmp = name.compareTo(employeeWritable.name);
//如果不相等
if (cmp != 0){
return cmp;
}
//如果名字相等就比较角色
return role.compareTo(employeeWritable.role);
}
/**
* MapReduce需要一个分割者(Partitioner)把Map的输出作为输入分成一块块的喂给多个reduce
* 默认的是HashPatitioner,它是通过对象的hashCode函数进行分割。
* 所以hashCode的好坏决定了分割是否均匀,它是一个很关键的方法
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((role == null) ? 0 : role.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EmployeeWritable other = (EmployeeWritable) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (role == null) {
if (other.role != null)
return false;
} else if (!role.equals(other.role))
return false;
return true;
}
/**
* 自定义自己的输出类型
*/
@Override
public String toString() {
return "EmployeeWritable [姓名=" + name + ", 角色=" + role + "]";
}
}
上面的EmployeeWritable已经可以跑的很溜了,但是还是有优化的空间,当作为MapReduce里的key,需要进行比较时,因为他已经被序列化,想要比较他们,那么首先要反序列化成一个对象,然后再调用compareTo对象进行比较,但是这样效率太低了,有没有可能可以直接比较序列化后的结构呢,答案是肯定的。
我们只需要把EmployeeWritable序列化后的结果拆分成成员对象,然后比较成员对象即可,看代码:
public static class Comparator extends WritableComparator{
private static final Text.Comparator TEXT_COMPARATOR = new Text.Comparator();
protected Comparator() {
super(EmployeeWritable.class);
}
@Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
try {
int nameL1 = WritableUtils.decodeVIntSize(b1[s1]) + readVInt(b1, s1);
int nameL2 = WritableUtils.decodeVIntSize(b2[2]) + readVInt(b2, s2);
//和compareTo方法一样,先比较name
int cmp = TEXT_COMPARATOR.compare(b1, s1, nameL1, b2, s2, nameL2);
if (cmp != 0){
return cmp;
}
//再比较role
return TEXT_COMPARATOR.compare(b1, s1+nameL1, l1-nameL1, b2, s2+nameL2, l2-nameL2);
} catch (Exception e) {
throw new IllegalArgumentException();
}
}
static {
//注册raw comparator,更像是绑定,这样MapReduce使用EmployeeWritable时就会直接调用Comparator
WritableComparator.define(EmployeeWritable.class, new Comparator());
}
}
我们没有直接去实现RawComparator而是继承于WritableComparator,因为WritableComparator提供了很多便捷的方法,并且对compare有个默认的实现。写compare方法时一定要小心谨慎,因为都是在字节上操作,可以好好参考下源代码里的一些Writable中的Comparator的写法,另外多看下WritableUtils也是有必要的,它里面有很多简便的方法可以使用。
自定义Comparators
有时候,除了默认的Comparator,你可能还需要一些自定义的Comparator来生成不同的排序队列,看一下下面这个示例,只比较name,两个compare是同一个意思,都是比较name的大小:
public static class NameComparator extends WritableComparator{
private static final Text.Comparator TEXT_COMPARATOR= new Text.Comparator();
protected NameComparator() {
super(EmploeeWritable.class);
}
@Override
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
try {
int nameL1= WritableUtils.decodeVIntSize(b1[s1])+readVInt(b1,s1);
int nameL2=WritableUtils.decodeVIntSize(b2[s2])+readVInt(b2,s2);
return TEXT_COMPARATOR.compare(b1,s1,nameL1,b2,s2,nameL2);
} catch (IOException e) {
throw new IllegalArgumentException();
}
}
@Override
public int compare(WritableComparable a, WritableComparable b) {
if(a instanceof EmploeeWritable && b instanceof EmploeeWritable){
return ((EmploeeWritable)a).name.compareTo(((EmploeeWritable)b).name);
}
return super.compare(a,b);
}
}