使用String.split()将csv文件以引号作为文本分隔符拆分

70

我有一个逗号分隔的文件,其中有许多类似下面的行。

Sachin,,M,"Maths,Science,English",Need to improve in these subjects.

引号用于转义逗号分隔的多个值的分隔符。

现在,如果可能的话,我该如何使用String.split()函数在逗号分隔符上拆分上述值?


6
为什么你坚持使用String.split?这个例子有更好的选择吗? - user949300
5个回答

213
public static void main(String[] args) {
    String s = "Sachin,,M,\"Maths,Science,English\",Need to improve in these subjects.";
    String[] splitted = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
    System.out.println(Arrays.toString(splitted));
}

输出:

[Sachin, , M, "Maths,Science,English", Need to improve in these subjects.]

10
我花了一些时间才明白这个正则表达式在做什么。如果有解释说明它匹配逗号后跟偶数引号(或没有引号)的话,那么对我会非常有帮助。所以,这个正则表达式之所以有效,是因为引号内部的逗号(即我们不想匹配/拆分的逗号)应该在它们之间和行末之间有奇数个引号。另外值得注意的是,如果数据中可能出现转义引号,我认为这种方法可能无法奏效。 - glyphx
4
如果您想保留末尾的空字符串,请执行此操作:s.split(',(?=([^"]"[^"]")[^"]$)', -1)。参考自:https://dev59.com/LWYr5IYBdhLWcg3wH2zK。 - kctang
1
非常有帮助。在JavaScript中执行此操作时,我需要将?:添加到内部组中,因此完整表达式变为s.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/); - Marty Neal
如果我的某些字段实际上也包含引号,我该如何使其工作?它在除了一些客户字段中的名称包含“”之外的所有内容上都有效。例如:Montgomery County Sheriff's Office KS“Montgomery PD,AL”,其中它在“Montgomery PD,AL”处分割并将其放在自己的行中,但不应该:/ @glyphx - Ashton
1
@Ashton 非常奇怪......您最好发一条新问题,包括完整的细节。显示您正在尝试解析的整个字符串、您正在使用的模式以及结果。就我所理解的情况而言,本答案中的模式只应匹配并分割逗号。 - Ben
1
正则表达式的解释/可视化 https://regexper.com/#(%3F%3D(%5B%5E%5C%22%5D*%5C%22%5B%5E%5C%22%5D*%5C%22)*%5B%5E%5C%22%5D*%24) - mtk

26

由于您的问题/需求并不是非常复杂,可以利用一种自定义方法来执行,它比原方法快20倍以上,并且产生相同的结果。这取决于数据大小和解析的行数,对于更复杂的问题,使用正则表达式是必须的。

import java.util.Arrays;
import java.util.ArrayList;
public class SplitTest {

public static void main(String[] args) {

    String s = "Sachin,,M,\"Maths,Science,English\",Need to improve in these subjects.";
    String[] splitted = null;

 //Measure Regular Expression
    long startTime = System.nanoTime();
    for(int i=0; i<10; i++)
    splitted = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
    long endTime =   System.nanoTime();

    System.out.println("Took: " + (endTime-startTime));
    System.out.println(Arrays.toString(splitted));
    System.out.println("");


    ArrayList<String> sw = null;        
 //Measure Custom Method
            startTime = System.nanoTime();
    for(int i=0; i<10; i++)
    sw = customSplitSpecific(s);
    endTime =   System.nanoTime();

    System.out.println("Took: " + (endTime-startTime));
    System.out.println(sw);         
}

public static ArrayList<String> customSplitSpecific(String s)
{
    ArrayList<String> words = new ArrayList<String>();
    boolean notInsideComma = true;
    int start =0, end=0;
    for(int i=0; i<s.length()-1; i++)
    {
        if(s.charAt(i)==',' && notInsideComma)
        {
            words.add(s.substring(start,i));
            start = i+1;                
        }   
        else if(s.charAt(i)=='"')
        notInsideComma=!notInsideComma;
    }
    words.add(s.substring(start));
    return words;
}   

}

在我的电脑上,这将产生以下结果:

Took: 6651100
[Sachin, , M, "Maths,Science,English", Need to improve in these subjects.]

Took: 224179
[Sachin, , M, "Maths,Science,English", Need to improve in these subjects.]

1
-1 这并不回答问题,问题明确要求使用 String.split() 解决方案。顺带一提,使用 Vector 是一个对 Java 了解很少的人所编写的代码的标志之一。 - Bohemian
10
请解释为什么在这种情况下使用ArrayList比Vector更有优势(除了由于线程安全性而导致的性能损失)。此外,你的礼貌需要改进,礼貌是粗鲁之人的标志之一。 - Menelaos
2
我并没有失礼,只是客观陈述而已。这里有一个小技巧... Vector不是线程安全的。它是一个有缺陷的类,这就是为什么没有人,我真的是没有人在现实世界中使用它。只有完全的初学者才会使用它,我猜测这是因为演讲笔记已经过时十年了,特别是那些提倡使用Vector的讲师已经在学术界呆太久,老话"如果你做不到,就去教别人做"仍然成立。 - Bohemian
4
啊哈,我自己找到了有关向量是遗留问题的答案。谢谢你,我不打算再使用它了,并且相比于正则表达式+拆分,您确实帮助我更加提高了解决方案的速度。是的,原始问题要求使用拆分,但对于那些通过谷歌等途径查找此问题的人来说,也有其他替代方案是有用的。想象一下在处理100万或1000万条记录时,使用拆分与使用本特定情况下的其他方法所需的时间差异。 - Menelaos
2
速度并不是一切。我坚信“少代码就是好”(原因很多,这里无法讨论)。但是,与其编写自己的代码(如果不使用split()),我会首先查找现有的库,对于CSV解析,有很多选择。 - Bohemian
很多年前,当我在.NET中使用正则表达式时,我发现通过保留正则表达式对象的静态副本(因此预先解析为自己的解析树并存储在内存中)可以显着提高性能。不知道如何在Java中实现相同的效果,同时仍然使用String.split,但这可能是主要的性能成本。 - Lisa

10

如果你的字符串都是规范的,可以使用以下正则表达式:

String[] res = str.split(",(?=([^\"]|\"[^\"]*\")*$)");

这个表达式确保分割仅发生在逗号后面跟随偶数(或零)引号的位置(因此不在这些引号内部)。

然而,使用简单的非正则表达式解析器可能更容易。


读取CSV文件方面,它运行良好。如果您有这种格式:987663,seepzBranch,“Seepz mumbai,andheri”,“near infra,flat no 23,raghilla mall thane”,seepz, - abhishek ringsia

0
收藏的答案似乎是正确的,除非你有尾随逗号的情况。你应该在分割时指定限制参数
public static void main(String[] args) {
   String s = "Sachin,,M,\"Maths,Science,English\",Need to improve in these subjects.,,,";
   String[] splitted = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)", -1);
   System.out.println(Arrays.toString(splitted));
}

否则结果将不包括最后三列。

-1
在处理CSV字符串时,我们需要了解以下几点:
  1. 行中的每个元组都以引号或非引号开头。 a)如果以引号开头,则它必须是特定列的值。 b)如果直接以非引号开头,则必须为标题。 例如:'Header1、Header2、Header3、"value1"、"value2"、"value3"'; 这里Header1、Header2和Header3是列名,其余是值。
进行拆分时,我们需要记住的主要问题是要检查拆分是否进行正确。 a)获取拆分值并检查值中引号的数量(计数必须为偶数) b)如果计数为奇数,则附加下一个拆分值。 c)重复过程a、b,直到引号相等。

1
这并没有回答问题。一旦您拥有足够的声望,您将能够评论任何帖子;相反,提供不需要询问者澄清的答案。- 来自审核 - Marc Wrobel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接