优雅的算法,用Java中的逗号或双引号对分割字符串

时间:2012-11-26 11:33:34

标签: java algorithm

问题很简单。


CSV文件如下所示:

1, "John", "John Joy"

如果我想获得每一栏,我只使用String[] splits = line.split(",");


如果CSV文件如下所示:

1, "John", "Joy, John"

所以我们在双引号对中有一个逗号。上述分裂将不再适用,因为我希望“欢乐,约翰”作为一个完整的部分。


那么有一个优雅/简单的算法来处理这种情况吗?


修改

请不要将其视为正式的CSV解析内容。我只是将CSV用作需要拆分的用例。

我真正想要的不是一个合适的CSV解析器,相反,我只想要一个算法,它可以用逗号来正确地分割一行,考虑到双引号。

5 个答案:

答案 0 :(得分:4)

最好将现有的库用于此purpuse,而不是编写自定义实现(如果不这样做,则需要进行学习)。 因为CSV具有一些您可以在自定义实现中遗漏的细节,并且通常库已经过充分测试。

在这里你可以找到一些好的Can you recommend a Java library for reading (and possibly writing) CSV files?

修改

我已经创建了解析你的字符串的方法,但是它再次起作用并不完美,因为我还没有很好地测试它。 它可以作为您的起点,您可以进一步改进它。

    String inputString = "1, \"John\",\"Joy, John\"";
    char quote = '"';
    List<String> csvList = new ArrayList<String>();
    boolean inQuote = false;
    int lastStart = 0;
    for (int i = 0; i < inputString.length(); i++) {
        if ((i + 1) == inputString.length()) {
            //if this is the last character
            csvList.add(inputString.substring(lastStart, i + 1));
        }
        if (inputString.charAt(i) == quote) {
            //if the character is quote
            if (inQuote) {
                inQuote = false;
                continue; //escape
            }
            inQuote = true;
            continue;
        }
        if (inputString.charAt(i) == ',') {
            if (inQuote) continue;
            csvList.add(inputString.substring(lastStart, i));
            lastStart = i + 1;
        }
    }
    System.out.println(csvList);

问题

如果你得到类似1, "John", ""Joy, John""的字符串怎么办? (关于“乔伊,约翰”的两个引语)?

答案 1 :(得分:1)

// use regxep with matcher

String string1 = "\"John\", \"John Joy\"";
String string2 = "\"John\", \"Joy, John\"";
Pattern pattern = Pattern.compile("\"[^\"]+\"");

Matcher matcher = pattern.matcher(string1);
System.out.println("string1: " + string1);
int start = 0;
while(matcher.find(start)){
    System.out.println(matcher.group());
    start = matcher.end() + 1;
    if(start > string1.length())
    break;
}

matcher = pattern.matcher(string2);
System.out.println("string2: " + string2);
start = 0;
while(matcher.find(start)){
    System.out.println(matcher.group());
    start = matcher.end() + 1;
    if(start > string2.length())
    break;
}

答案 2 :(得分:0)

使用正则表达式非常优雅 对不起,我不熟悉Java正则表达式,所以我的例子是Lua:
(这个例子没有考虑到引用文本中可能有换行符,并且原始引用字符在引用文本中会加倍)

--- file.csv
1, "John", "John Joy"
2, "John", "Joy, John"

--- Lua code
for line in io.lines 'file.csv' do
   print '==='
   for _, s in (line..','):gmatch '%s*("?)(.-)%1%s*,' do
      print(s)
   end
end

--- Output
===
1
John
John Joy
===
2
John
Joy, John

答案 3 :(得分:0)

您可以从正则表达式开始:

[^",]*|"[^"]*"

匹配不包含逗号或带引号的字符串的非引用字符串。但是,有很多问题,包括:

  1. 输入中的逗号之后是否真的有空格?或者,更一般地说,您是否允许不完全位于字段第一个字符的引号?

  2. 如何在包含引号的字段周围加上引号?

  3. 根据您回答该问题的方式,您最终可能会使用不同的正则表达式。 (实际上,使用CSV解析库的习惯性建议并不是处理极端情况;它不需要考虑它们,因为您假设“标准CSV”处理,无论根据作者的说法。解析库.CSV很乱。)

    我已经成功使用的一个正则表达式(尽管 CSV兼容)是:

    (?:[^",]|"[^"]*")*
    

    与第一个非常相似,只是它允许任意数量的连接字段,因此以下两个都被识别为单个字段:

    "John"", Mary"
    John", "Mary
    

    CSV标准会将第一个视为代表:

    John", Mary    -- internal quote
    

    并将第二个中的引号视为普通字符,从而产生两个字段。所以YMMV。

    无论如何,一旦你决定了一个合适的正则表达式,算法就很简单了。在伪代码中,因为我远非Java专家。

    repeat:
       match the regex at the current position
         and append the result to the result;
       if the match fails:
         report error
       if the match goes to the end of the string:
         done
       if the next character is a ',':
         advance the position by one
       otherwise:
         report error
    

    根据正则表达式,可能无法报告错误的两个条件。通常,如果引用的字段未终止,则第一个将触发(并且您需要决定是否允许引用字段中的换行 - CSV确实如此)。如果您使用我提供的第一个正则表达式,然后没有立即使用逗号跟随带引号的字符串,则可能会发生第二个。

答案 4 :(得分:-1)

首先将字符串拆分为引号。奇数段会引用内容;即使是那些也必须再用逗号分开。我在日志上使用它,其中引用的文本没有转义引号,就像在这个问题中一样。

    boolean quoted = false;
    for(String q : str.split("\"")) {
        if(quoted)
            System.out.println(q.trim());
        else
            for(String s : q.split(","))
                if(!s.trim().isEmpty())
                    System.out.println(s.trim());
        quoted = !quoted;
    }