MapReduce中Mapper向Reducer传递枚举类型的问题及报错咨询
解决MapReduce中枚举从Mapper传递到Reducer的问题
嘿,我来帮你搞定这个困扰你的枚举传递问题!先说说你遇到的Type mismatch in value from map: expected StatisticTypes, recieved StatisticTypes$2错误到底是怎么回事:
错误原因
每个枚举常量在底层其实是枚举类自动生成的匿名子类实例(比如StatisticTypes$2就是你枚举中第二个常量对应的子类),当你直接把StatisticTypes.class设为Map输出值类型时,Hadoop的类型校验机制会认为收到的子类实例和你声明的父枚举类不匹配,于是抛出类型错误。
下面给你两种靠谱的解决方案:
方案一:用Hadoop自带的EnumWritable包装类(推荐)
这是最简单的方式,不需要修改你的枚举类,直接用Hadoop提供的包装类处理枚举的序列化/反序列化:
- 配置Job的Map输出值类型
job.setMapOutputValueClass(EnumWritable.class);
- 在Mapper中包装枚举实例输出
@Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 假设你要传递StatisticTypes.COUNT这个枚举实例 StatisticTypes targetType = StatisticTypes.COUNT; // 用EnumWritable包装枚举 EnumWritable<StatisticTypes> writableEnum = new EnumWritable<>(targetType); // 输出包装后的对象 context.write(key, writableEnum); }
- 在Reducer中解析枚举实例
@Override protected void reduce(LongWritable key, Iterable<EnumWritable<StatisticTypes>> values, Context context) throws IOException, InterruptedException { for (EnumWritable<StatisticTypes> val : values) { // 取出包装的枚举实例 StatisticTypes statType = val.get(); // 接下来就可以正常使用枚举了 switch(statType) { case COUNT: // 处理计数逻辑 break; // 其他枚举分支... } } }
方案二:让枚举实现Writable接口
如果你不想用包装类,也可以让你的枚举类直接实现Hadoop的Writable接口,手动处理序列化逻辑:
- 修改枚举类实现Writable
public enum StatisticTypes implements Writable { COUNT, SUM, AVERAGE; @Override public void write(DataOutput out) throws IOException { // 序列化枚举的名称(也可以用ordinal,name可读性更好) out.writeUTF(this.name()); } @Override public void readFields(DataInput in) throws IOException { // 读取名称并转换为对应的枚举实例 String enumName = in.readUTF(); // 注意:这里需要通过反射修改当前实例,因为枚举实例是不可变的 try { Field field = Enum.class.getDeclaredField("name"); field.setAccessible(true); field.set(this, enumName); field = Enum.class.getDeclaredField("ordinal"); field.setAccessible(true); field.set(this, StatisticTypes.valueOf(enumName).ordinal()); } catch (Exception e) { throw new IOException("Failed to deserialize StatisticTypes", e); } } }
- 配置Job并正常输出枚举
这时候你就可以像最开始那样设置job.setMapOutputValueClass(StatisticTypes.class),Mapper直接输出枚举实例,Reducer也能正常接收了。不过这种方式需要手动处理反射,不如方案一简洁。
关于多Map输出类的问题
当然可以设置多个Map输出类型!MapReduce提供了MultipleOutputs类来实现多输出,每个输出可以有自己的键值类型:
- 在Job中注册多输出
// 注册第一个输出:键是LongWritable,值是StatisticTypes MultipleOutputs.addNamedOutput(job, "stat-output", TextOutputFormat.class, LongWritable.class, StatisticTypes.class); // 注册第二个输出:键是Text,值是IntWritable MultipleOutputs.addNamedOutput(job, "num-output", TextOutputFormat.class, Text.class, IntWritable.class);
- 在Mapper中使用多输出
private MultipleOutputs multipleOutputs; @Override protected void setup(Context context) throws IOException, InterruptedException { multipleOutputs = new MultipleOutputs(context); } @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 输出到第一个命名输出 multipleOutputs.write("stat-output", key, StatisticTypes.COUNT); // 输出到第二个命名输出 multipleOutputs.write("num-output", new Text("total"), new IntWritable(100)); } @Override protected void cleanup(Context context) throws IOException, InterruptedException { // 记得关闭MultipleOutputs multipleOutputs.close(); }
这样你就能在同一个Mapper任务中输出不同类型的键值对啦!
内容的提问来源于stack exchange,提问作者Han Yu
相关产品推荐
相关产品推荐

