用JAVA处理文本与二进制数据混合大文件

来源:互联网 发布:javascript 关闭页面 编辑:程序博客网 时间:2024/04/30 12:26
我们一般常见的文件主要有三种形式:文本文件、二进制数据文件、混合文件。作为混合文档的处理,特别是大型混合文档的处理,开发人员面临着特殊的挑战:
 
其一,需要对二进制数据进行定位,并取得二进制数据内容而进行下一步处理;
 
其二,要根据二进制数据的上下文,取得二进制数据的相关背景信息,才可能进行下一步处理;
 
其三,面对大型文件,一次将所有数据统统读入内存进行处理往往不现实。而在分次读入文件时,如何控制二进制数据的连续性和正确性,是一个不能忽视的问题。
 
本文即对如上三个问题进行一些探讨,而提出用JAVA对大型二进制数据和文本混合文件进行有效处理的一种可行方法。
 
关于XML混合文件的处理方法,已有文章进行论述。而且,针对XML文档处理,我们可以使用JAXP等等进行处理,本文对这些问题不再赘述。有兴趣读者请参阅http://www-128.ibm.com/developerworks/cn/xml/x-wxxm33/。这里,我们主要以PDF文档为例,讨论一下大型混合文档的处理。
 
1、问题描述:
 
在pdf-1.3以后,pdf文档中可以嵌入附件注释,如下图所示。
在对pdf文档进行全文检索的时候,如果不能对附件注释的内容进行提取,则有可能不能检索出带有某个关键词的pdf文档。基于这种情况,我们有必要提取出pdf文档中的附件注释内容,根据实际需求,以决定对附件注释内容如何进行全文检索。pdf-1.3的源文件的一个案例如下图所示。
 
2、关于JAVA类的选择。
 
对于混合文档处理,我们要考虑到两个方面:其一为对文本内容的信息提取,其二为二进制数据的数据提取。针对这两个方面,我们则需要使用不同的JAVA类进行处理。
 
2.1 处理文本内容。
 
处理文本内容我们可以使用JAVA的BufferedReader类,使用案例如下:
 
File f = new File();
BufferedReader  br = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
 
针对附件存在的位置,要结合pdf文档的文档定义结构进行分析。通过分析pdf文档的每一行,找出含有附件的位置,以及附件名称等等。从而为下一步二进制数据的提取做好准备。
 
*关于pdf文档结构,请参见Acrobat主页。
 
2.2 处理二进制数据
 
处理二进制数据不可以使用BufferedReader,否则提取的数据将不会保持原样。所以,我们此时需要FileInputStream类。该类的使用案例如下:
 
File f = new File();
FileInputStream fin = new FileInputStream(f);
 
然而,我们仍然需要对要提取的二进制数据进行定位,否则将错误的提取数据。此时,则需要对获得的文件输入流进行字符比较,而找到要提取的二进制数据的开始和结束部分。进行字符比较,我们需要构造一个类似于下面的方法:
 
 int findStringInBuffer (byte[] buffer, char[] search, int buffersize, int offset)
 {
  int len = search.length;
  int pos = 0, i;
  boolean fnd = false;
  while (!fnd)
  {
   fnd = true;
   for (i=0; i<len; i++)
   {
    if ((char)buffer[offset + pos + i]!=search[i])
    {
     fnd = false;
     break;
    }
   }
   if (fnd) {
    return pos;
   }
   pos++;
   if (pos >= buffersize - len - offset) return -1;
  }
  return -1;
 }
 
3. 在处理大型混合文件时的挑战和对策。
 
3.1 大型混合文件的挑战
 
如果该混合文件较小,则可以通过把整个文档读入内存的方式,来迅速定位附件注释位置并抽取出二进制数据。但是,面对大型混合文件,我们则会有新的挑战。挑战主要包括如下几个方面:
 
其一,如何选择buffer的大小。如果buffer过小,可能会因为指示文本数据过长无法放入buffer中,而使得数据提取算法失效;如果buffer过大,则可能会使得内存溢出;
 
其二,如何对目标文档进行分段,而正确的确定二进制文件的位置,从而进行数据提取,也是一个不能忽略的问题。指示文本数据可能分属不同的分段中,这样如何恢复指示文本数据便成为关键;
 
其三,一个二进制数据流可能属于连续的文档段中,如何将二进制数据流合适的拼接成原来的数据,也是一个关键的问题。
 
针对这三个问题,我们可以有如下选择对策。
 
3.2 合理选择buffer大小。
 
在选择buffer的时候,我们要确定对每一个二进制数据流的指示文本数据的长度。理论上讲,只要buffer的长度大于指示文本数据的长度,则是适合的buffer数据长度。将输入文件流的数据读入buffer中的一个案例如下:
 
byte[] buffer = new byte[buffer_size]; 
int size = fin.read(buffer);
 
size中存放的是buffer中实际读取的字符数。
 
3.3 指示文本数据的拼接。
 
按照指示文本数据的长度,我们有必要设计首尾重叠的buffer来读取文档中的数据,并且每次只写入固定长度的二进制数据到输出文件中。首尾重叠的buffer的设计目的是正确实现指示文本数据的正确拼接,因为每一次读入的输入文件流可能将指示文本数据截为两半。首尾重叠的buffer的一个使用案例如下:
 
      for(int j = tmp_start; j < size; j++){
       buffer[j - tmp_start] = tmp[j];
      }
      size = 0;
      int bt;
      for(int j = 0; (j < tmp_start) && ((bt = fin.read()) != -1); j++){
       size++;
       buffer[buffer_size - tmp_start + j] = (byte)bt;
      }
      size = size + buffer_size - tmp_start;
 
3.4 将连续buffer中的二进制数据拼接成完全的二进制数据。首先确定二进制数据的起始位置。由于二进制数据长度可能远大于选择的buffer长度,所以在读取二进制数据时候,有必要确定二进制数据的结束位置。这涉及到二级制数据结束位置的指示文本数据位置确定,其中可能需要事先指示文本数据的拼接。另外还要确定不要读入错误的结束数据。这就需要确定结束指示文本数据的长度,在每一次将二进制数据写入到输出文件中的时候,都要预留出这个结束指示文本数据的长度,而留待下次读取。一个二进制数据读取的案例如下:
     for(int j = 0; j < endstream_length; j++){
      tmp[j] = buffer[size - endstream_length + j];
     }
     for(int j = 0; j < endstream_length; j++){
      buffer[j] = tmp[j];
     }
     size = fin.read(ass_buf_4_endstream);
     for(int j = 0; j < size; j++){
      buffer[endstream_length + j] = ass_buf_4_endstream[j];
     }
     size = size + endstream_length;
 
4. 结语
 
综合使用JAVA的FileInputStream和BufferedReader类,并对大型二进制和文本混合文件进行合理的结构分析和进行算法设计,则可以完成对大型二进制和文本混合文件中的二进制数据进行处理的任务。本文提出了处理大型二进制和文本混合文件的一种可行性方案,具有重要的实践价值。
联系作者:wtcforever (a) 163.com