1. 简介

在CSV(逗号分隔值)文件中,分隔符用于分隔数据字段,不同文件可能使用不同的分隔符。常见分隔符包括逗号、分号或制表符。处理CSV文件时,正确识别分隔符至关重要,这能确保数据解析准确并避免数据损坏。

本文将探讨如何确定CSV文件中的分隔符。

2. 理解CSV文件中的分隔符

CSV文件中的分隔符用于分隔记录中的各个字段。最常见的分隔符包括:

  • 逗号(,):大多数CSV文件的标准分隔符
  • 分号(;):在逗号用作小数点的地区常用
  • 制表符(\t):通常出现在制表符分隔值文件中
  • 竖线(|):偶尔用于避免与传统分隔符冲突

处理CSV文件时,必须使用正确的分隔符来解析数据。

例如,假设有一个包含以下内容的CSV文件:

Location,Latitude,Longitude,Elevation(m)
New York,40.7128,-74.0060,10
Los Angeles,34.0522,-118.2437,71

可以看到,逗号(,)被用作字段分隔符。

3. 简单行采样法

确定分隔符的一种方法是采样文件中的几行,统计常见分隔符字符的出现次数。

首先,定义我们要测试的可能分隔符:

private static final char[] POSSIBLE_DELIMITERS = {',', ';', '\t', '|'};

可以假设在多行中出现频率最高的字符很可能是分隔符

@Test
public void givenCSVLines_whenDetectingDelimiterUsingFrequencyCount_thenCorrectDelimiterFound() {
    char[] POSSIBLE_DELIMITERS = {',', ';', '\t', '|'};
    Map<Character, Integer> delimiterCounts = new HashMap<>();

    for (char delimiter : POSSIBLE_DELIMITERS) {
        int count = 0;
        for (String line : lines) {
            count += line.length() - line.replace(String.valueOf(delimiter), "").length();
        }
        delimiterCounts.put(delimiter, count);
    }

    char detectedDelimiter = delimiterCounts.entrySet().stream()
      .max(Map.Entry.comparingByValue())
      .map(Map.Entry::getKey)
      .orElse(',');

    assertEquals(',', detectedDelimiter);
}

此方法使用replace()移除行中的分隔符,通过length()的差异计算每个分隔符的出现次数。然后将计数存储在HashMap中。最后使用stream().max()找出出现次数最多的分隔符并返回。如果未找到分隔符,则通过orElse()方法默认使用逗号

4. 基于采样的动态分隔符检测

更健壮的检测方法是获取第一行的所有字符集,然后采样额外行来测试哪个字符能始终产生相同的列数

@Test
public void givenCSVLines_whenDetectingDelimiter_thenCorrectDelimiterFound() {
    String[] lines = {
            "Location,Latitude,Longitude,Elevation(m)",
            "New York,40.7128,-74.0060,10",
            "Los Angeles,34.0522,-118.2437,71",
            "Chicago,41.8781,-87.6298,181"
    };

    char detectedDelimiter = ',';
    for (char delimiter : lines[0].toCharArray()) {
        boolean allRowsHaveEqualColumnCounts = Arrays.stream(lines)
          .map(line -> line.split(Pattern.quote(String.valueOf(delimiter))))
          .map(columns -> columns.length)
          .distinct()
          .count() == 1;

        if (allRowsHaveEqualColumnCounts) {
            detectedDelimiter = delimiter;
            break;
        }
    }

    assertEquals(',', detectedDelimiter);
}

此方法遍历第一行的每个字符,将其视为潜在分隔符。然后检查该字符在所有行中是否产生一致的列数。方法使用split()分割每行,并通过Pattern.quote()处理特殊字符(如|\t)。

对于每个潜在分隔符,我们用它分割所有行并计算每行的列数(字段数)。算法的关键部分是使用distinct()验证列数在所有行中是否一致。

最后,如果当前分隔符在每行中都产生一致的列数,则假定它是正确的分隔符。如果未找到一致的分隔符,我们也会默认使用逗号

5. 结论

本文探讨了两种检测CSV文件分隔符的方法。第一种方法使用简单的行采样并统计潜在分隔符的出现次数。第二种更健壮的方法通过检查多行中一致的列数来识别正确的分隔符。可根据CSV文件的复杂度选择任一方法。

本文的完整代码示例可在GitHub上找到。


原始标题:How to Determine the Delimiter in CSV File | Baeldung