如何确定CSV文件中的分隔符

时间:2018-03-12 12:58:49

标签: java csv

我有一个场景,我必须解析来自不同来源的CSV文件,解析代码非常简单明了。

        String csvFile = "/Users/csv/country.csv";
        String line = "";
        String cvsSplitBy = ",";
        try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
            while ((line = br.readLine()) != null) {
                // use comma as separator
                String[] country = line.split(cvsSplitBy);
                System.out.println("Country [code= " + country[4] + " , name=" + country[5] + "]");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

我的问题来自CSV分隔符字符,我有许多不同的格式,有时它是,有时它是;

是否有任何方法可以在解析文件之前确定分隔符字符

6 个答案:

答案 0 :(得分:5)

univocity-parsers支持自动检测分隔符(也包括行结尾和引号)。只需使用它而不是与您的代码作斗争:

CsvParserSettings settings = new CsvParserSettings();
settings.detectFormatAutomatically();

CsvParser parser = new CsvParser(settings);
List<String[]> rows = parser.parseAll(new File("/path/to/your.csv"));

// if you want to see what it detected
CsvFormat format = parser.getDetectedFormat();

免责声明:我是这个图书馆的作者,我确保涵盖各种角落案例。它的开源和免费(Apache 2.0许可证)

希望这有帮助。

答案 1 :(得分:1)

是的,但仅限于不允许分隔符作为常规文本存在

最简单的答案是拥有一个包含所有可用分隔符的列表,并尝试识别正在使用的字符。即使您必须对文件或创建它们的人/人放置一些限制。请看以下两种情况:

案例1 - file.csv的内容

test,test2,test3

案例2 - file.csv的内容

test1|test2,3|test4

如果您事先了解分隔符字符,那么您将使用,分割第一个字符串,使用|分割第二个字符串,得到相同的结果。但是,如果您尝试通过解析文件来识别分隔符,则两个字符串可以使用,字符进行拆分,您最终会得到:

案例1 - 使用,

进行拆分的结果
test1
test2
test3

案例2 - 使用,

进行拆分的结果
test1|test2
3|test4

由于缺乏使用哪个分隔符的先验知识,您无法创建将解析每个文本组合的“神奇”算法;即使是正则表达式或计算一个角色的出现次数也不会拯救你。

最坏情况

test1,2|test3,4|test5

通过查看文本,可以使用|作为分隔符来标记它。但,|的出现频率是相同的。因此,从算法的角度来看,两种结果都是准确的:

更正结果

test1,2
test3,4
test5

错误的结果

test1
2|test3
4|test5

如果您提出了一套指南,或者您可以以某种方式控制CSV文件的生成,那么您可以尝试使用上述字符列表找到与String.contains()方法一起使用的分隔符。例如:

public class MyClass {

    private List<String> delimiterList = new ArrayList<>(){{
        add(",");
        add(";");
        add("\t");
        // etc...
    }};

    private static String determineDelimiter(String text) {
        for (String delimiter : delimiterList) {
            if(text.contains(delimiter)) {
                return delimiter;
            }
        }
        return "";
    }

    public static void main(String[] args) {
        String csvFile = "/Users/csv/country.csv";
        String line = "";
        String cvsSplitBy = ",";
        String delimiter = "";
        boolean firstLine = true;
        try (BufferedReader br = new BufferedReader(new FileReader(csvFile)))  {
            while ((line = br.readLine()) != null) {
                if(firstLine) {
                    delimiter = determineDelimiter(line);
                    if(delimiter.equalsIgnoreCase("")) {
                        System.out.println("Unsupported delimiter found: " + delimiter);
                        return;
                    }
                    firstLine = false;
                }
                // use comma as separator
                String[] country = line.split(delimiter);
                System.out.println("Country [code= " + country[4] + " , name=" + country[5] + "]");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

<强>更新

对于更优化的方式,在determineDelimiter()方法而不是for-each循环中,您可以使用正则表达式。

答案 2 :(得分:0)

如果分隔符可以出现在数据列中,那么您要求的是不可能的。例如,考虑CSV文件的第一行:

one,two:three

这可以是逗号分隔文件,也可以是冒号分隔文件。你不知道它是哪种类型。

如果您可以保证第一行的所有列都用引号括起来,例如,如果它始终是这种格式:

"one","two","three"

然后你可以使用这个逻辑(虽然它不是100%防弹):

if (line.contains("\",\""))
    delimiter = ',';
else if (line.contains("\";\""))
    delimiter = ';';

如果您不能保证这样的受限格式,那么最好将分隔符作为参数传递。

然后,您可以使用广为人知的开源CSV解析器(例如Apache Commons CSV)来阅读该文件。

答案 3 :(得分:0)

虽然我同意 Lefteris008 的观点,即不可能拥有正确确定所有情况的函数,但我们可以拥有一个既高效又在实践中给出大部分正确结果的函数。

def head(filename: str, n: int):
    try:
        with open(filename) as f:
            head_lines = [next(f).rstrip() for x in range(n)]
    except StopIteration:
        with open(filename) as f:
            head_lines = f.read().splitlines()
    return head_lines


def detect_delimiter(filename: str, n=2):
    sample_lines = head(filename, n)
    common_delimiters= [',',';','\t',' ','|',':']
    for d in common_delimiters:
        ref = sample_lines[0].count(d)
        if ref > 0:
            if all([ ref == sample_lines[i].count(d) for i in range(1,n)]):
                return d
    return ','

我的高效实施基于

  1. 先验知识,例如您经常使用的常用分隔符列表 ',;\t |:' ,甚至可能使用的分隔符的罩盖,因此我经常将常规的 ',' 放在顶部列表
  2. 分隔符出现在文本文件每一行的频率是相等的。这是为了解决如果我们读取一行并看到频率相等(错误检测为Lefteris008)甚至正确的分隔符出现频率较低的问题,如第一行中的错误
  3. 高效实现 read only first n lines from the file
  1. 随着测试样本数 n 的增加,您得到错误答案的可能性会急剧减少。我经常发现 n=2 就足够了

答案 4 :(得分:-1)

这取决于......

如果您的数据集总是长度相同和/或数据列中的分隔符永远不会出现,您可以只读取文件的第一行,查看渴望分隔符的内容,设置它然后读取其余部分使用该分隔符的文件。

这样的东西
String csvFile = "/Users/csv/country.csv";
String line = "";
String cvsSplitBy = ",";
try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
    while ((line = br.readLine()) != null) {
        // use comma as separator
        if (line.contains(",")) {
            cvsSplitBy = ",";
        } else if (line.contains(";")) {
           cvsSplitBy = ";";
        } else {
            System.out.println("Wrong separator!");
        }
        String[] country = line.split(cvsSplitBy);
        System.out.println("Country [code= " + country[4] + " , name=" + country[5] + "]");
    }
} catch (IOException e) {
    e.printStackTrace();
}

Greetz Kai

答案 5 :(得分:-1)

添加这样的条件,

String [] country;
if(line.contains(",")
    country = line.split(",");
else if(line.contains(";"))
    country=line.split(";");