【问题标题】:Tokenizing a String with tab delimiter in Java while skipping some tokens在 Java 中使用制表符分隔符对字符串进行标记,同时跳过一些标记
【发布时间】:2012-10-04 07:34:53
【问题描述】:

我有一个包含数据的大文件(~8Gb / ~8000 万条记录)。每条记录有 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

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

【问题讨论】:

  • 如果您只是要求“更优雅的代码”,也许您应该查看codereview.stackexchange.com
  • 不会为此使用 Java。我可能会使用cut
  • 您想要更优雅的代码还是更快的代码?在这种情况下,更优雅可能意味着更慢。较低级别、不太优雅的代码可能意味着稍微快一些。
  • @JBNizet 你说得对,我需要速度。好吧,目前我正在处理我的数据的一个非常小的子集(大约 200 条记录),除了我的“更优雅的代码”问题之外,我还遇到了一个“错误:null”,我不明白它来自哪里,如果这敲响了任何铃声。

标签: java tokenize stringtokenizer


【解决方案1】:

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

不过,在 Java 中你也可以尝试:

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

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

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

【讨论】:

    【解决方案2】:

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

    我会使用某种类型的“拆分”将行转换为标记数组。我更喜欢 commons-lang 中的 StringUtils split 而不是 String.split,尤其是在不需要正则表达式时。由于制表符是“空白”,因此您可以使用默认拆分方法而无需指定分隔符:

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

    【讨论】:

    • 请注意:在 Java 7 中,String.split 已改进为在分隔符为单个字符的情况下不使用正则表达式。
    【解决方案3】:

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

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

    几个java库: - Java Csv library - flatpack

    【讨论】:

      【解决方案4】:

      你的问题让我对性能产生了疑问。最近我一直在尽可能地使用 Guava 的 Splitter,只是因为我挖掘了语法。我从未测量过性能,所以我对四种解析样式进行了快速测试。我很快就把这些放在一起,所以请原谅风格和边缘情况正确性方面的错误。它们基于我们只对第二项和第四项感兴趣的理解。

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

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

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

      • 国产:2271ms
      • guavaSplit:3367ms
      • 正则表达式:7302ms
      • 标记化: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;
        }
      

      请注意,通过适当的边界检查和更优雅的实现,所有这些都可能会变慢。

      【讨论】:

        【解决方案5】:

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-17
          • 1970-01-01
          • 1970-01-01
          • 2015-04-16
          相关资源
          最近更新 更多