使用Java中的制表符分隔符对字符串进行标记,同时跳过一些标记

时间:2012-10-13 20:41:04

标签: java tokenize stringtokenizer

我有一个包含数据的大文件(~8Gb / ~80万条记录)。每条记录都有6-8个属性,这些属性由单个选项卡分割。我想让初学者在另一个文件中复制一些给定的属性。所以我想要一个比上面更优雅的代码,例如,如果我只想要总共4个中的第二个和最后一个令牌:

StringTokenizer st = new StringTokenizer(line, "\t");
st.nextToken(); //get rid of the first token
System.out.println(st.nextToken()); //show me the second token
st.nextToken(); //get rid of the third token
System.out.println(st.nextToken()); //show me the fourth token

我提醒说这是一个巨大的文件,所以我必须避免任何多余的检查。

5 个答案:

答案 0 :(得分:3)

你的问题让我想知道表现。最近我一直在使用Guava的Splitter,因为我挖掘了语法。我从未测量过性能,因此我对四种解析样式进行了快速测试。我把这些放在一起非常快,所以请原谅错误的风格和边缘情况的正确性。他们的理解是我们只对第二和第四项感兴趣。

我发现有趣的是,在解析350MB制表符分隔的文本文件(包含四列)时,“homeGrown”(非常粗略的代码)解决方案是最快的,例如:

head test.txt 
0   0   0   0
1   2   3   4
2   4   6   8
3   6   9   12

在我的笔记本电脑上运行超过350MB的数据时,我得到了以下结果:

  • 本土:2271ms
  • guavaSplit:3367ms
  • 正则表达式:7302ms
  • tokenize:3466ms

考虑到这一点,我认为我会坚持使用Guava的分配器进行大多数工作,并考虑使用更大数据集的自定义代码。

  public static List<String> tokenize(String line){
    List<String> result = Lists.newArrayList();
    StringTokenizer st = new StringTokenizer(line, "\t");
    st.nextToken(); //get rid of the first token
    result.add(st.nextToken()); //show me the second token
    st.nextToken(); //get rid of the third token
    result.add(st.nextToken()); //show me the fourth token
    return result;
  }

  static final Splitter splitter = Splitter.on('\t');
  public static List<String> guavaSplit(String line){
    List<String> result = Lists.newArrayList();
    int i=0;
    for(String str : splitter.split(line)){
      if(i==1 || i==3){
        result.add(str);
      }
      i++;
    }
    return result;
  }

  static final Pattern p = Pattern.compile("^(.*?)\\t(.*?)\\t(.*?)\\t(.*)$");
  public static List<String> regex(String line){
    List<String> result = null;
    Matcher m = p.matcher(line);
    if(m.find()){
      if(m.groupCount()>=4){
        result= Lists.newArrayList(m.group(2),m.group(4));
      }
    }
    return result;
  }

  public static List<String> homeGrown(String line){
    List<String> result = Lists.newArrayList();
    String subStr = line;
    int cnt = -1;
    int indx = subStr.indexOf('\t');
    while(++cnt < 4 && indx != -1){
      if(cnt==1||cnt==3){
        result.add(subStr.substring(0,indx));
      }
      subStr = subStr.substring(indx+1);
      indx = subStr.indexOf('\t');
    }
    if(cnt==1||cnt==3){
      result.add(subStr);
    }
    return result;
  }

请注意,通过正确的绑定检查和更优雅的实现,所有这些都可能会更慢。

答案 1 :(得分:0)

正如Paul Tomblin所说,您应该使用unix cut实用程序。

但是,在Java中你也可以尝试:

String[] fields = line.split("\t");
System.out.println(fields[1]+" "+fields[3]);

这是否更“优雅”是一个意见问题。对于大文件是否更快,我不知道 - 你需要在你的系统上对它进行基准测试。

相对性能还取决于每行有多少字段,以及您想要的字段; split()会立即处理整行,但StringTokenizer将逐步处理该行(如果您只想要20个字段中的字段2和4,则很好)。

答案 2 :(得分:0)

虽然您的数据文件很大,但听起来您的问题更多的是如何方便地访问文本行中的项目,其中项目由制表符分隔。我认为StringTokenizer对于这种简单的格式来说太过分了。

我会使用某种类型的“split”将该行转换为一个标记数组。我更喜欢在String.split上的公共语句StringUtils split,特别是当不需要正则表达式时。由于选项卡是“空格”,因此您可以使用默认拆分方法而不指定分隔符:

String [] items = StringUtils.split(line);
if (items != null && items.length > 6)
{
    System.out.println("Second: " + items[1]  + "; Fourth: " + items[3]);
}

答案 3 :(得分:0)

如果您正在执行readLines,那么您实际上是在扫描文件两次: 1)您一次搜索文件1个字符以查找行尾字符 2)然后你扫描每一行的标签。

您可以查看其中一个Csv库。从内存中,flatpack只进行一次扫描。 这些库可以提供更好的性能(我从未测试过它)。

一些java库:   - Java Csv library   - flatpack

答案 4 :(得分:0)

如果您的文件除了速度之外还很大,您还将面临内存消耗问题,因为您必须将文件加载到内存中才能操作它。

我有一个想法,但请注意,这是特定于平台的,并且违反了Java移动性。

您可以从java运行unix命令以获得大量的速度和内存消耗。例如:

    public static void main ( final String[] args)throws Exception {
         Runtime.getRuntime().exec("cat <file> | awk {print $1} >> myNewFile.txt");
    }