2

这个问题很简单。


CSV 文件如下所示:

1, "John", "John Joy"

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


如果 CSV 文件如下所示:

1, "John", "Joy, John"

所以我们在双引号对中有一个逗号。上面的拆分不再起作用,因为我想要“Joy, John”作为完整的部分。


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


编辑:

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

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

4

5 回答 5

4

最好将现有库用于此目的,而不是编写自定义实现(如果您不这样做是为了学习)。因为 CSV 具有一些您在自定义实现中可能会错过的细节,并且通常库已经过良好测试。

Here you can find some good one你能推荐一个用于读取(和可能写入)CSV文件的Java库吗?

编辑

我已经创建了可以解析您的字符串的方法,但是由于我还没有很好地测试它,所以它可能工作得并不完美。它可以作为您的起点,您可以进一步改进它。

    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"" (“Joy,John”上有两个引号)怎么办?

于 2012-11-26T11:39:44.790 回答
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;
}
于 2012-11-27T06:03:32.653 回答
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
于 2012-11-26T16:43:03.217 回答
0

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

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

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

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

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

根据您回答该问题的方式,您最终可能会得到不同的正则表达式。(实际上,使用 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 会)。如果您使用我提供的第一个正则表达式,然后没有立即用逗号跟随带引号的字符串,则可能会发生第二个。

于 2012-11-26T16:54:54.230 回答
-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;
    }
于 2015-05-15T07:28:01.193 回答