【问题标题】:Issue in tokenizing the String标记字符串的问题
【发布时间】:2015-02-20 14:09:45
【问题描述】:

我需要从包含至少 100K 记录的 PSV 文件中读取数据,并将每一行映射到 DTO 对象。

例如我有一个管道分隔的字符串SampleData|1111|9130|23||1257|2014-04-01 18:00:00|2014-04-12 09:00:00||Software Developer|20|Vikas||PATIL 被解析并根据 DTO 值提取每个标记。

我从 String Tokenizer 开始,它给了我正确的结果,直到我收到上面的字符串作为输入。

这个字符串的特点是它在几个管道之间没有任何字符,例如|23||1257|Vikas||PATIL

当我尝试使用标记器拆分它时,它给我的标记比我预期的要少。它只是忽略了空字符,结果是 我将 1257 值分配给 phoneNumber 并将 InsertDaate 值分配给 regionCode。 em>

我应该分配值让我们说 sampleData 到 dto 字段 dataType , 1111 到 recordID .. 和 ''phone Number 因为输入数据没有 phone number 的数据但在 23 之后标记器读取下一个标记作为1257,所以我将错误的值1257 分配给phonenumber 字段。

感谢上帝,我只在测试环境中意识到了这个错误。

我尝试了几个选项,最后用 String.split() 方法解决了这个问题。

import java.util.StringTokenizer;

public class TestSpitingOfString {
    public static void main(String args[]) throws Exception {
        //DTO  dataType|recordID|employeeid|deptID|phoneNumber|regionCode|InsertDate|StartDate|hobby|designation|age|firstName|middleName|lastName
        String str = "SampleData|1111|9130|23||1257|2014-04-01 18:00:00|2014-04-12 09:00:00||Software Developer|20|Vikas||PATIL";

        System.out.println("Original String -> "+str);

        StringTokenizer tokenizer= new StringTokenizer(str,"|");// skips empty values between tokens
        System.out.println("Words With StringTokenizer ");
        while(tokenizer.hasMoreElements()){
            System.out.print(tokenizer.nextToken()+",");
        }
        System.out.println();

        String distributedWithPipe[] =str.split("|");// disaster :(  it splitted every character
        System.out.println("Words With String.split() distributedWithPipe character ->");
        for(String split : distributedWithPipe){
            System.out.print(split+",");
        }

        System.out.println();
        String distributedWithEscapedPipe[] =str.split("\\|"); // This worked for me
        System.out.println("Words With String.split() distributedWithEscapedPipe ->");
        for(String split : distributedWithEscapedPipe){
            System.out.print(split+",");
        }

    }
}

当我运行它时,我会得到输出(我在每个标记之间保留 , 只是为了便于理解):

Original String -> SampleData|1111|9130|23||1257|2014-04-01 18:00:00|2014-04-12 09:00:00||Software Developer|20|Vikas||PATIL

Words With StringTokenizer

SampleData,1111,9130,23,1257,2014-04-01 18:00:00,2014-04-12 09:00:00,Software Developer,20,Vikas,PATIL,

Words With String.split() distributedWithPipe character ->

,S,a,m,p,l,e,D,a,t,a,|,1,1,1,1,|,9,1,3,0,|,2,3,|,|,1,2,5,7,|,2,0,1,4,-,0,4,-,0,1, ,1,8,:,0,0,:,0,0,|,2,0,1,4,-,0,4,-,1,2, ,0,9,:,0,0,:,0,0,|,|,S,o,f,t,w,a,r,e, ,D,e,v,e,l,o,p,e,r,|,2,0,|,V,i,k,a,s,|,|,P,A,T,I,L,

Words With String.split() distributedWithEscapedPipe ->

SampleData,1111,9130,23,,1257,2014-04-01 18:00:00,2014-04-12 09:00:00,,Software Developer,20,Vikas,,PATIL,

我问这个问题的原因:

  1. 如果有人知道如何使用 StringTokenizer 我们可以解决这个问题,我会很乐意学习它。否则我们可以说它是 StringTokenizer 的一个限制。
  2. 如果有人遇到同样的问题,则可以使用替代解决方案,无需浪费时间找出解决方案。
  3. 还需要强调的是,由于习惯于使用 StringTokenizer,我们可能倾向于使用“|”管道(没有转义字符)作为分隔符和 String.split() 不会产生预期的输出。

【问题讨论】:

  • 也许你应该看看 Google Guava 的 Splitter 类。它似乎专门解决了StringTokenizer 类的一些类似问题:code.google.com/p/guava-libraries/wiki/StringsExplained
  • Split 需要一个正则表达式。这是in the documentation of String。正则表达式| 在“空字符串”或“空字符串”上拆分,即在每个可能的位置上。
  • 如果您替换所有“||”实例,则可以使用 stringtokenizr用“| |” (管道空间管道)
  • 如果问题缺失,请使用split("\\|", -1) ""
  • StringTokenizer 没有给我任何错误。只是它错过了几个令牌。所以可以说我期待 15 个令牌,但我只得到了 13 个,因为有两次出现 || 在 PIPES 之间没有任何东西。但是当我分配值时,让我们说 sampleData 到 dto 字段 dataType , 1111 到 recordID .. 和 '' 到 phone Number ,因为输入数据没有它。但是在23 之后,它将下一个标记读取为1257,所以我将错误的值1257 分配给电话号码字段。

标签: java stringtokenizer string-split


【解决方案1】:

StringTokenizer 在其 javadoc 中声明了这种行为(尽管我承认它可能更清楚,取决于您如何解释“连续字符”):

StringTokenizer 的实例以两种方式之一运行,具体取决于 关于它是否是使用具有值的 returnDelims 标志创建的 真假:

  • 如果标志为 false,则分隔符用于分隔标记。 标记是非分隔符的最大连续字符序列

  • 如果标志为真,则分隔符本身被视为标记。因此,令牌是一个分隔符 字符,或连续字符的最大序列 不是分隔符。

阅读this bug in JDK Bug Database(或this one)的cmets:

StringTokenizer 将标记定义为连续的最大序列 不是分隔符的字符。因此子字符串 ",," 中没有标记。

然后您可以使用构造函数StringTokenizer(String str, String delim, true),但请注意,这会将分隔符作为每个标记的一部分返回,因此您需要自己删除它们,这是一个相当大的负担。

出于所有这些原因,最好只使用String.split

【讨论】:

  • 我尝试使用构造函数StringTokenizer tokenizer= new StringTokenizer(str,"|",true); 但它也返回| 字符
  • 是的,请阅读 javadoc:“如果标志为真,则分隔符本身被视为标记”。我编辑了我的答案以表明这一点
  • OK,所以零字符不被视为一个序列。我认为这值得商榷。但是 Joshua Bloch 本人 (!) 在第二个链接中指出这是 "StringTokenizer is a very simple String scanner."(第一个链接对我不起作用)。
【解决方案2】:

为此使用String.split() 和正则表达式可能会更好(您需要指出| 是一个字符,而不是逻辑OR!):

String str = "SampleData|1111|9130|23||1257|2014-04-01 18:00:00|2014-04-12 09:00:00||Software Developer|20|Vikas||PATIL";
String[] tokens = str.split("[|]");
for (String token : tokens) {
    // or do something else...
    System.out.println(token);
}

或者,对于具有大量分隔符的字符串来说更复杂但更有效:

String str = "SampleData|1111|9130|23||1257|2014-04-01 18:00:00|2014-04-12 09:00:00||Software Developer|20|Vikas||PATIL";
// start or '|', then anything (reluctant) then '|' or end
Matcher m = Pattern.compile("(?<=^|[|]).*?(?=[|]|$)").matcher(str);
while (m.find()) {
    // or do something else...
    String token = m.group();
    System.out.println(token);
}

至于你的问题:

  1. StringTokenizer 是一个相对简单的类,可能不应该用于此。
  2. 我没有遇到这个问题,但有时测试我的正则表达式技能是值得的,而且这个解决方案应该可以工作。请参阅 Pattern 课程,了解 ^$、不情愿的量词,当然还有积极的后视和积极的前瞻。
  3. 认为它突出显示:)

【讨论】:

  • 感谢这个替代解决方案 +1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-11
  • 1970-01-01
  • 1970-01-01
  • 2020-04-21
  • 1970-01-01
相关资源
最近更新 更多