Java split 导致字段丢失的一个“坑”

最近在排查异常数据的时候,有童鞋发现 hadoop 中 String.split() 方法会导致字段丢失,而测试 java 不会。

其实仔细想想这是不应该发生的事情,因为 hadoop 压根就没重写 string 的 split 方法,不会导致如此差异。

还是那句话:Talk is cheap, show you the code.

我们的测试数据如下:

^I 表示 该字段是 TAB 字符,awk 处理下来都是 7 个字段。

june@deepin 13:24:29 ~ >
hadoop fs -cat /tmp/table.txt|awk -F"\t" '{print NF}'
7
7
june@deepin 13:24:46 ~ >
hadoop fs -cat /tmp/table.txt|cat -A
^I^I^IA^IB^IC^ID$
A^I^I^IB^IC^ID^I$
june@deepin 13:24:52 ~ >
下面来看看hadoop处理结果:
public class Test {


	public static class RelevanceMapper extends
			Mapper<LongWritable, Text, Text, Text> {
		public void map(LongWritable key, Text value, Context context)
				throws IOException, InterruptedException {
			System.out.println("---->> map: " + value.toString().split("\t").length);
			context.write(new Text("1"), value);
		}
	}

	public static class RelevanceReducer extends
			Reducer<Text, Text, NullWritable, Text> {
		public void reduce(Text key, Iterable<Text> values, Context context)
				throws IOException, InterruptedException {
			for(Text va : values){
				System.out.println("---->> reduce: " + va.toString().split("\t").length);
			}
		}
	}

	public static void main(String[] args) throws IOException,
			InterruptedException, ClassNotFoundException {
		Configuration configuration = new Configuration();
		String[] otherArgs = new GenericOptionsParser(configuration, args)
				.getRemainingArgs();
		Job job = new Job(configuration, "test");
		job.setJarByClass(Test.class);
		job.setNumReduceTasks(1);
		job.setMapperClass(RelevanceMapper.class);
		job.setReducerClass(RelevanceReducer.class);
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(Text.class);
		job.setOutputKeyClass(NullWritable.class);
		job.setOutputValueClass(Text.class);
		FileInputFormat.addInputPath(job, new Path(args[0]));
		org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.setOutputPath(
				job, new Path("/tmp/test2"));

		System.exit(job.waitForCompletion(true) ? 0 : 1);

	}
}
结果是:

---->> map: 7   

---->> map: 6

---->> reduce: 7

---->> reduce: 6

这说明第2行最后的 \t 被忽略了,而前导 \t 是在hadoop中正常识别了。

那是不是hadoop才这样,而java不这样呢?

我们还是看代码:

public class test {
	public static void main(String[] args) {
		System.out.println("\t\t\tA\tB\tC\tD".split("\t").length);
		System.out.println("A\t\tB\t\tC\tD\t".split("\t").length);
		System.out.println("A\t\tB\t\tC\tD\t".split("\t", -1).length);
	}
}
结果是

7

6

7

可以看到 java中呈现的结果和hadoop一样,因为 hadoop 没有重写该方法,所以没有差别的。

那为什么第2行的末尾的 \t 没有被识别呢?

翻翻 jdk 官方文档就知道了:

public String[] split(String regex)
根据给定 正则表达式 的匹配拆分此字符串。

该方法的作用就像是使用给定的表达式和限制参数 0 来调用两参数 split 方法。因此,所得数组中不包括结尾空字符串。

官方说的很清楚了:split 还有个重载的方法:
public String[] split(String regex, int limit)
我们不带limit其实就是默认limit为0,那自然结尾的空字符串也被忽略了。

要想 split 尽可能多的匹配:启用limit即可,就像例子中的最后一行,limit 取值 -1 即可。

结尾:

不仔细检查,这个还真容易被我们忽略,因为我们习惯或者经验告诉我们 java 中split也应该

和awk、python等其他语言一样,但是恰恰理所当然的我们掉进了 java 的坑里,这样数据统计

的时候就出错了。

—————————————————————————————————————————

下面再举一个最近有童鞋遇到的一个java split 的坑:

有童鞋写了这么两行代码,一直跑的好好的,今天半夜程序却在这里挂掉了:

String strTrackUrl = oo.xx() // strTrackUrl 不可能为 null

if (strTrackUrl.isEmpty())
return (long) 0;
strTrackUrl = strTrackUrl.split("x\\.shtml")[0];

然后这里就报:java.lang.ArrayIndexOutOfBoundsException: 0

为了模拟当时的场景,我们用下面的代码试试:

public class Test1 {

	public static void main(String[] args) {
		String strTrackUrl = " ";
		if (strTrackUrl.isEmpty())
			System.out.println("空串!"); ;
		System.out.println("---------->> 1: [" + strTrackUrl.split(" ", -1)[0]+"]");
		System.out.println("---------->> 2: [" + strTrackUrl.split("1")[0]+"]");
		System.out.println("---------->> 3: [" + strTrackUrl.split(" ")[0]+"]");
	}

}

这个抛异常:

---------->> 1: []
---------->> 2: [ ]
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at tmp.Test1.main(Test1.java:11)

想想这是为什么呢?

我想上面分析的应该会给你答案:

当用split(" ")的时候碰上了原串是" ",这样字符串变成了模式,而split(" ")[0]取到了空,

但是恰好此时空是要被忽略的,所以 split(" ", -1) 能确保安全无侧漏~ 哈哈~

PS:

总觉得java是一种臃肿不堪的语言。。。

你可能感兴趣的:(Java split 导致字段丢失的一个“坑”)