Hadoop自定义实现Writable/WritableComparable接口的类方法及应用
博主是刚刚踏入计算机领域的菜鸟,写博客更多的作用是备忘,如果有大牛们发现博客中有任何的错误缺漏,万望指正!——gdufs数蛙实验室菜鸟
使用过hadoop的bro们都知道,在hadoop的操作中很多情况下我们只讨论键和值,事实上我们还需要关注他们的类型——因为mapreduce框架并不允许键值是任意类型的类(因为mapreduce框架需要序列化键值对)。
一般情形下我们会使用的类有——Text\IntWritable(以及Double,long等实现Writable接口的类),布尔类型也有BooleanWritable类。
这些数据类型已经可以满足我们许多操作的需求了,类似计数,统计。
但在更多时候我们处理数据的时候,这些数据类型所能提供的帮助依旧很局限,比如当我们要同时统计一个key的两个或是三个value,那么你怎么把这些值都由map传入reduce呢。
有一种可行的方法是把value们连起来,用一个Text类型的变量存储他们,但这样虽然可以传入reduce,但要在reduce里做后续操作却非常困难,你需要分割这些value在做统计,甚至有时候采用这种方法并不能满足需求。
这个时候,我们需要发挥java的长处,自定义一些类作为key或者value在mapreduce的框架中传递。
Writable接口和WritableComparable接口
为了实现这些操作,首先我们来了解两个接口——Writable接口和WritableComparable接口,在mapreduce中实现Writable接口的类可以是值,而实现WritableComparable接口的类既可以是键又可以是值。刚才提到的那些基本的数据类型,基本都是实现后者。事实上我在用的时候也更偏向于实现WritableComparable接口,尽管你可能只是将这个类作为值来用,但有可能你码着码着突然发现他当键更方便。
这篇博客用的例子基本都是实现WritableComparable接口。
话不多说,来看例子
例子
public static class SmpWord implements WritableComparable<SmpWord> {
private Text first;
private Text second;
public SmpWord() {
set(new Text(), new Text());
}
public void set(Text first, Text second) {
this.first = first;
this.second = second;
}
public Text getFirst() {
return first;
}
public Text getSecond() {
return second;
}
@Override
public void readFields(DataInput in) throws IOException {
first.readFields(in);
second.readFields(in);
}
@Override
public void write(DataOutput out) throws IOException {
first.write(out);
second.write(out);
}
@Override
public int compareTo(SmpWord o) {
int cmp = first.compareTo(o.first);
if (cmp != 0) {
return cmp;
} else {
return second.compareTo(o.second);
}
}
}
这个代码算是我一开始的启蒙老师,是一位大牛在博客上po的代码,不过那篇博客只是写了一个TextPair的自定义类,并且是实现排序用的,并没有说明这些东西是怎么来的。
- 私有域
我们来看一下这个类的结构,首先是有两个私有域,这些私有域非常非常有用,因为当我们处理数据的时候,如刚才所说,对于一个对象,往往会有多个数据,如果用自带的数据类型传递的话,复杂且难以处理。现在这个问题解决了,我们写了这样的一个类,然后只需把其中一个比如this.user的私有域存储主关键字,其余的私有域分别存储该对象的各个数据属性,传递的时候我们只需要传递一整个对象,就把这个对象的各个属性传递下去了。同时,在reduce当中,我们也可以对这些数据做统计或是其他更复杂的操作。
- 构造方法
下一个需要注意的地方是这个类的构造方法,一个类可以有多种形式的构造方法,这种类同样。但有一个地方需要特别注意,在写构造方法时必须有一个无参构造方法!!!为什么要这么做我也很不明白,姑且认为是mapreduce可能需要自己先生成key再将这个key等同于你传入的那个key的引用。总之你需要给一个无参构造方法给mapreduce框架,使其可以自己生成一个你自定义的类的对象。
并且如果你实现的接口是WritableComparable,你需要给一个排序的实现父类方法,那么你用来排序的数据域,需要在这个无参构造方法中初始化。
- 实现父类WritableComparable类中的方法
然后是实现父类WritableComparable类中的方法了,主要有三个,在代码中可以看到三个带@override标记的方法,就是那三个啦——readFields(),write(),compareTo(),建议还是留着标记,方便查错。
readFields(),write()分别说明如何在mapreduce中对你自定义的类写入、写出数据,它们与java中的DataInput,DataOutput类一起用于类中内容的串行化。
compareTo()就是刚才提到的排序的方法,如果你当成键值来用,是需要排序的。
应用
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Mapper.Context;
import org.apache.hadoop.mapreduce.lib.chain.ChainMapper;
import org.apache.hadoop.mapreduce.lib.chain.ChainReducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
class tf {
public static void uploadInputFile(String localFile) throws IOException{
Configuration conf = new Configuration();
String hdfsPath = "hdfs://localhost:9000/";
String hdfsInput = "hdfs://localhost:9000/user/hadoop/input";
//在hdfs下新建文件夹
// Configuration conf = new Configuration();
FileSystem file = FileSystem.get(conf);
file.mkdirs(new Path("/user/hadoop/input"));
FileSystem fs = FileSystem.get(URI.create(hdfsPath),conf);
fs.copyFromLocalFile(new Path(localFile), new Path(hdfsInput));
fs.close();
System.out.println("已经上传到input文件夹啦");
}
public static void getOutput() throws IOException{
String remoteFile1 = "hdfs://localhost:9000/user/hadoop/output/part-r-00000";
File file=new File(remoteFile1);
Path path = new Path(remoteFile1);
Configuration conf = new Configuration();
String hdfsPath = "hdfs://localhost:9000/";
FileSystem fs = FileSystem.get(URI.create(hdfsPath),conf);
fs.copyToLocalFile(path, new Path("/home/hadoop/桌面/标题"));
System.out.println("已经将标题保留到本地文件");
String remoteFile2 = "hdfs://localhost:9000/user/hadoop/output/part-r-00001";
file=new File(remoteFile2);
path=new Path(remoteFile2);
fs.copyToLocalFile(path, new Path("/home/hadoop/桌面/正文"));
System.out.println("已经将正文保留到本地文件");
fs.close();
}
public static void rewriteFile(String path) throws Exception{
BufferedReader br = new BufferedReader(new FileReader(new File (path)));
String content = "";
String line;
line = br.readLine();
int sum=0;
int max=0;
while((line = br.readLine()) != null){
String []a=line.split("\\s*");
sum+=Integer.parseInt(a[a.length-1]) ;
// content += line+"\n";
}
br.close();
BufferedReader be = new BufferedReader(new FileReader(new File (path)));
line=be.readLine();
double tf=0;
while((line = be.readLine()) != null){
String []a=line.split("\\s*");
tf=Double.parseDouble(a[a.length-1]) /(double)sum;
// int tmp=(int) (tf*1000);
content += line+" "+"tf:"+tf+"\n";
}
be.close();
BufferedWriter bw = new BufferedWriter(new FileWriter(new File(path)));
bw.append(content);
bw.close();
}
public static class SmpWord implements WritableComparable<SmpWord> {
private Text first;
private Text second;
public SmpWord() {
set(new Text(), new Text());
}
public void set(Text first, Text second) {
this.first = first;
this.second = second;
}
public Text getFirst() {
return first;
}
public Text getSecond() {
return second;
}
@Override
public void readFields(DataInput in) throws IOException {
first.readFields(in);
second.readFields(in);
}
@Override
public void write(DataOutput out) throws IOException {
first.write(out);
second.write(out);
}
@Override
public int compareTo(SmpWord o) {
int cmp = first.compareTo(o.first);
if (cmp != 0) {
return cmp;
} else {
return second.compareTo(o.second);
}
}
}
public static class smpmap extends Mapper<Object, Text, SmpWord, IntWritable>{
final IntWritable one =new IntWritable(1);
public void map(Object key, Text value, Context context) throws IOException, InterruptedException{
// String line=value.toString();
// String line="c";
// line=new String(line1.getBytes(line1),"utf-8");
String line=new String(value.getBytes(),0,value.getLength(),"gbk");
line=line.replaceAll("\\s*|\\S*", "");
line=line.replaceAll(" ", "");
if(line.startsWith("#")&line!="")
{
line=line.substring(1);
String [] a=line.split("/[a-z]*\\d|/[a-z]*|l/nz");
for(int i=0;i<a.length;i++)
{
SmpWord b=new SmpWord();
b.set(new Text("headline"), new Text(a[i]));
context.write(b, one);
}
}
else if(line!=""){
String [] a=line.split("/[a-z]*\\d|/[a-z]*|l/nz");
for(int i=0;i<a.length;i++)
{
SmpWord b=new SmpWord();
b.set(new Text("MainPart"), new Text(a[i]));
context.write(b, one);
}
}
}
}
public static class MyPartitionerPar extends Partitioner<SmpWord, IntWritable> {
public int getPartition(SmpWord key, IntWritable value, int numPartitions) {
String str1=key.first.toString();
int result=1;
if(str1.equals("headline")){
result = 0 % numPartitions; //0%3==0
// result = 0 ; //0%3==0
}else if(str1.equals("MainPart")){
result = 1 % numPartitions; //0%3==0
// result = 1 ; //1%3==1
}
return result;
}
}
public static class IntSumReducer extends Reducer<SmpWord, IntWritable, Text, IntWritable>{
private IntWritable result = new IntWritable();
public void reduce(SmpWord key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException{
int sum = 0;
for(IntWritable val :values){
sum+= val.get();
}
result.set(sum);
context.write(key.second, result);
}
}
public static void deleteOutput() throws IOException{
Configuration conf = new Configuration();
String hdfsOutput = "hdfs://localhost:9000/user/hadoop/output";
String hdfsPath = "hdfs://localhost:9000/";
Path path = new Path(hdfsOutput);
FileSystem fs = FileSystem.get(URI.create(hdfsPath),conf);
fs.deleteOnExit(path);
fs.close();
System.out.println("output文件已删除");
}
// public class InverseMapper extends Mapper<Text,IntWritable,IntWritable,Text> {
// public void map(Text key, IntWritable value, Context context
// ) throws IOException, InterruptedException {
// context.write(value, key);
// }
// }
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if(otherArgs.length != 2){
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Job job1 = new Job(conf, "smp word count");
job1.setJarByClass(tf.class);
job1.setMapperClass(smpmap.class);
job1.setMapOutputKeyClass(SmpWord.class);
job1.setMapOutputValueClass(IntWritable.class);
job1.setPartitionerClass(MyPartitionerPar.class);
job1.setNumReduceTasks(2);
job1.setReducerClass(IntSumReducer.class);
job1.setOutputKeyClass(Text.class);
job1.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job1, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job1, new Path(otherArgs[1]));
job1.waitForCompletion(true);
getOutput();
// deleteOutput();
// ChainMapper.addMapper(job1, InverseMapper.class, Text.class, IntWritable.class, IntWritable.class, Text.class, new Configuration() );
rewriteFile("/home/hadoop/桌面/标题");
rewriteFile("/home/hadoop/桌面/正文");
System.exit(job1.waitForCompletion(true)?0:1);
}
}
懒得打字了直接po代码上去,这其中有一些代码是用来传输文件重写文件的不需要的都可以忽略啦,主要只看main类,map类和reduce类就可以了
另外一个需要注意的地方是这里
job1.setMapperClass(smpmap.class);
job1.setMapOutputKeyClass(SmpWord.class);
job1.setMapOutputValueClass(IntWritable.class);
job1.setPartitionerClass(MyPartitionerPar.class);
job1.setNumReduceTasks(2);
job1.setReducerClass(IntSumReducer.class);
job1.setOutputKeyClass(Text.class);
job1.setOutputValueClass(IntWritable.class);
如果你使用的是自定义类的话,那么你需要设置好在map输出的key和value的类型,在reduce输出的类型。