Apache Spark RegEx:双引号无法转义分隔符

u1ehiz5o  于 2022-11-16  发布在  Apache
关注(0)|答案(1)|浏览(178)

我已经写了一个程序,这是要从csv文件读取使用分隔符,同时我有一个用例,我需要创建一个字符串的分隔数据,所以我已经创建了一个正则表达式来拆分列数据使用分隔符.
现在的挑战是,当分隔符出现在双引号中时,理想情况下,我不应该拆分数据,spark正在转义分隔符,但我的正则表达式不知何故没有。

private static void readFromSourceFile(SparkSession sparkSession) {
    String delType = ",";
    final String regex = "["+delType+ "]{"+delType.length()+"}(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
    Dataset<Row> csv = sparkSession
            .read().option("delimiter",delType)
            .option("header",false)
            .option("inferSchema",true)
            .csv("src/main/resources/quotes2.csv");

    char separator= '\u0001';
    
    csv.show(false);
    List<Row> df = csv.collectAsList();
    String split[] =  df.get(0).toString().split(regex);
    System.out.println(split.length);
    Arrays.stream(split).forEach(System.out::println);

}
程序的操作程序为-

红色标记区域是带双引号的字符串,它不应该拆分该列。
输入文件csv文件-
新款,667.88,现货,现在,true,true,B 09 D 7 MQ 69 X,B 09 D 7 MQ 69 X,NUC 10 i5 FNHN 16 GB +512 GB,“英特尔NUC 10 NUC 10 i5 FNHN家用和商用台式迷你电脑,第十代英特尔®酷睿™ i5- 10210 U,高达4.2 GHz,4核,8线程,25瓦英特尔®超高清显卡,16 GB RAM,512 GB PCIe固态硬盘,Win 10 Pro 8 GB RAM +256 GB SSD”,false ",[英特尔NUC 10 i5 FNHN with RAM & SSD]英特尔NUC 10 NUC 10 i5 FNHN Mini PC/HTPC With All New Parts Assembled本店热销英特尔NUC 11 i5、i7、NUC 10 i5、i7、NUC 8、准系统和Mini PC配备各种尺寸的RAM或SSD,如果您需要了解更多,请点击我们的商店名称:“GEEK + Computer Mall”---------“产品”,或点击标题下的“访问GEEK+商店”。:BRK:[四核处理器和显卡]第10代英特尔酷睿i5- 10210 U,1. 6 GHz -4. 2 GHz睿频,4核,8线程,6 MB高速缓存,25 W英特尔UHD显卡,最高1. 0 GHz,80 EU单元。:BRK:[存储扩展选项]金斯顿16 GB DDR4 RAM
有人可以建议或提供一个提示,以改善正则表达式。

ruarlubt

ruarlubt1#

我发现用复杂的分隔符进行拆分会导致正则表达式的复杂性,正如您的代码所展示的那样。事实上,子表达式"["+delType+ "]{"+delType.length()+"}"没有太大意义,我强烈怀疑这是您代码中的一个bug(例如,如果delType<>,您的代码也会在出现><时进行拆分)。
作为一种替代方法,可以考虑使用一个正则表达式来详尽地描述输入的词法语法,然后匹配所有标记。这在使用命名组时效果特别好。
在您的情况下(带有带引号的字段的CSV,使用双引号对其进行转义),可以通过以下标记类型来描述标记的词法语法:

  • 一个定界符(可配置,因此我们可能需要处理长度大于1的字符串)
  • 带引号的字段(由"…"包围的任意标记,其中中的字符可以是除"之外的任何字符,但也可以包含""
  • 未加引号的字段(下一个分隔符之前的任意标记)。

作为正则表达式,可以在Java中编写如下:

Pattern.compile(
    "(?<delim>" + d + ")|" +
    "\"(?<quotedField>(?:[^\"]|\"\")*)\"|" +
    "(?<field>.*?(?:(?=" + d + ")|$))"
);

其中d定义为Pattern.quote(delim)(如果分隔符包含正则表达式特殊字符,则引号很重要!)。
这里唯一稍微有点复杂的是最后一个标记类型,因为我们要匹配直到下一个分隔符(.*?非贪婪地匹配)的所有内容,或者直到字符串的结尾。
然后,我们遍历所有匹配项,并收集那些设置了组fieldquotedField的匹配项。

static String[] parseCsvRow(String row, String delim) {
    final String d = Pattern.quote(delim);
    final Pattern pattern = Pattern.compile(
        "(?<delim>" + d + ")|" +
        "\"(?<quotedField>(?:[^\"]|\"\")*)\"|" +
        "(?<field>.*?(?:(?=" + d + ")|$))"
    );
    final Matcher matcher = pattern.matcher(row);
    final List<String> results = new ArrayList<>();

    while (matcher.find()) {
        if (matcher.group("field") != null) {
            results.add(matcher.group("field"));
        } else if (matcher.group("quotedField") != null) {
            results.add(matcher.group("quotedField").replaceAll("\"\"", "\""));
        }
    }
    return results.toArray(new String[0]);
}

在真实的代码中,我会将其 Package 在CsvParser类中,而不是单个方法中,其中构造函数基于分隔符创建模式,这样就不必为每行重新编译模式。

相关问题