没想到Hadoop在解析XML时如此纠结,以至于新版api的mapreduce竟然放弃了XML格式的format以及reader,在老版(hadoop-0.19.*)的streaming模块提供了这样的api,由于我用的hadoop-0.20.2 3U1版本,因此需要把处理XML的几个类移植过来使用。
移植所带来的问题是各处依赖包,和各种api不兼容。没关系,我可以看一下源码,然后自己写一个。细看了一下reader的代码,发现mapreduce使用了BufferedInputStream的mark,reset来寻找XML的tag,这个tag就是我们在提交作业所设置的,比如<log>,</log>这样的标签。Java中stream流的mark和reset,允许指针回读,即在找到<log>时,mark一下指针,然后再找到</log>标签,最后通过reset方法,返回到mark的位置,把<log></log>内的数据读取出来。但在匹配的过程中,我发现mapred使用了BufferedInputStream 的 read(); 方法,该方法返回下一个可读的字节。那么整个处理过程就是读一个字节,比较一个字节,我没有在mapreduce中用这样的算法,但我测试过,向缓冲区(BufferedInputStream)中一个字节一个字节的读,性能严重不足,read(); 方法平均返回时间在331纳秒,处理一个170M的xml文档(tag比较多),竟然花了200+秒。(streaming模块还写了一个faster*方法,哎,慢死了)
周敏同学提供了pig中处理xml的reader,但pig那边的代码我还没细看,也不知道hadoop的jira中有没有新的feature来解决现有xml的问题。如果有的话,不防可以告诉我一下下。呵呵。
现在有一个构思,即主要思想仍然围绕字节比较,因为字符串匹配效率更低,另外算法源于String.indexOf(“”),即找到<log>这个后,记住位置,然后再找</log>,这样算完全匹配,中间的内容用system.arraycopy来复制到新的字节数组,目前这算法我实现了一半,即找到<log>和</log>后,把这两个签标全部替换掉,170M文档,用时2.2秒(最快1.3秒)。
算法及问题:
首先提供一个BufferedInputStream,默认大小8k,在程序中建一个字节数组,大小为4k,即每次向BufferedInputStream读4k,这个效率是很不错的,然后去寻找<log>.toArray这样的字节数组,这一步速度是很惊人的。但这里有一个小的问题,即每次读4k的大小去处理,那很有可能<log></log>位于两次读取的一尾一头,那么我的想法是做一个半循环的字节数组,即如果在4k的字节数组中的最后找到<log>,那么就把前面未匹配的仍掉,然后把<log>标签移到字节数组最前端,然后另用这个字节数组再向BufferedInputStream中去读4k-5长度的内容(5是<log>的字节长度)。关于4k这个大小,首先要对XML数据进行sampling,即确定<log></log>当中的内容长度,然后再定这个缓冲buf的大小。