【问题标题】:Splitting on comma outside quotes在引号外的逗号上拆分
【发布时间】:2013-09-24 11:04:57
【问题描述】:

我的程序从文件中读取一行。此行包含逗号分隔的文本,例如:

123,test,444,"don't split, this",more test,1

我希望拆分的结果是这样的:

123
test
444
"don't split, this"
more test
1

如果我使用String.split(","),我会得到这个:

123
test
444
"don't split
 this"
more test
1

换句话说:子字符串"don't split, this" 中的逗号不是分隔符。如何处理?

【问题讨论】:

  • 为什么有这个要求?您能否提供更多有关您要解决的问题的信息?
  • 我不相信这个问题与前面提到的问题重复,因为这里双引号的字符串用逗号分隔;前面的问题没有这个要求。它希望(给出的示例)foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy" 被拆分为 foobarc;qual="baz,blurb"d;junk="quux,syzygy"。这不是一个微不足道的区别,因为匹配@LAFKsaysReinstateMonica 的正则表达式"\"[^\"]*\"|[^,]+ 在这里有效,但在那里无效。我已投票决定重新开放。

标签: java regex string split


【解决方案1】:

你可以试试这个正则表达式:

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

这将分割, 上的字符串,后跟偶数个双引号。换句话说,它在双引号之外的逗号上拆分。如果您的字符串中有平衡的引号,这将起作用。

解释:

,           // Split on comma
(?=         // Followed by
   (?:      // Start a non-capture group
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
     [^"]*  // 0 or more non-quote characters
     "      // 1 quote
   )*       // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
   [^"]*    // Finally 0 or more non-quotes
   $        // Till the end  (This is necessary, else every comma will satisfy the condition)
)

您甚至可以在您的代码中输入这样的内容,在您的正则表达式中使用(?x) 修饰符。修饰符会忽略正则表达式中的任何空格,因此更容易阅读分成多行的正则表达式,如下所示:

String[] arr = str.split("(?x)   " + 
                     ",          " +   // Split on comma
                     "(?=        " +   // Followed by
                     "  (?:      " +   // Start a non-capture group
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "    [^\"]* " +   // 0 or more non-quote characters
                     "    \"     " +   // 1 quote
                     "  )*       " +   // 0 or more repetition of non-capture group (multiple of 2 quotes will be even)
                     "  [^\"]*   " +   // Finally 0 or more non-quotes
                     "  $        " +   // Till the end  (This is necessary, else every comma will satisfy the condition)
                     ")          "     // End look-ahead
                         );

【讨论】:

  • 这个答案这么多年还是有价值的!
  • 让我的程序与您的解释一起工作。谢谢!现在,有没有办法也可以为此添加换行符? \n 和 \r?
  • 嗨,我的字符串是这样的:\“不要拆分,这个\”(它在“前面有那些反斜杠。如何修改正则表达式?
  • 嗨 Rohit,我遵循了您的解决方案,其中我有两个分隔符,和/或。并使用以下正则表达式: (\s+and\s+|\s+or\s+)(?=(?:[^\"]*"[^\"]*\")*[^\"]*$ ). 它适用于大多数用例,但输入失败:'brand == "Kellogg\'\'s" 或 country == \'UnitedStates and "India\''。请你帮助我好吗?我对正则表达式很陌生。
  • 这不适用于非平凡的情况,例如如果文本本身包含引号,则需要转义,例如 \"
【解决方案2】:

既然可以匹配,为什么要拆分?

重新提出这个问题,因为出于某种原因,没有提到简单的解决方案。这是我们精美紧凑的正则表达式:

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

这将匹配所有需要的片段 (see demo)。

说明

  • "[^"]*",我们匹配完整的"double-quoted strings"
  • |
  • 我们匹配[^,]+ 任何不是逗号的字符。

一种可能的改进是改进交替的字符串端,以允许引用的字符串包含转义的引号。

【讨论】:

  • 因为我喜欢这个而不是拆分,所以我将它与 Matcher 中的 Java 9 改进相结合,允许流式传输。我的答案包含演示它的 jshell 会话。
  • 如果您还需要获取空字符串,此解决方案将不起作用,但我喜欢它。
  • @zx81 你知道如何使用转义引号\" 吗?
【解决方案3】:

你可以很容易地做到这一点,无需复杂的正则表达式:

  1. 拆分字符"。你得到一个字符串列表
  2. 处理列表中的每个字符串:将列表中偶数位置上的每个字符串(从零开始索引)拆分为“,”(您会在列表中获得一个列表),单独保留每个奇数位置的字符串(直接将其放入列表中的列表中)。
  3. 加入列表列表,因此您只得到一个列表。

如果你想处理 '"' 的引用,你必须稍微调整一下算法(加入一些部分,你有不正确的拆分,或者将拆分更改为简单的正则表达式),但基本结构保持不变。

所以基本上是这样的:

public class SplitTest {
    public static void main(String[] args) {
        final String splitMe="123,test,444,\"don't split, this\",more test,1";
        final String[] splitByQuote=splitMe.split("\"");
        final String[][] splitByComma=new String[splitByQuote.length][];
        for(int i=0;i<splitByQuote.length;i++) {
            String part=splitByQuote[i];
            if (i % 2 == 0){
               splitByComma[i]=part.split(",");
            }else{
                splitByComma[i]=new String[1];
                splitByComma[i][0]=part;
            }
        }
        for (String parts[] : splitByComma) {
            for (String part : parts) {
                System.out.println(part);
            }
        }
    }
}

承诺使用 lambda 会更简洁!

【讨论】:

    【解决方案4】:

    基于 @zx81's 的回答,因为匹配的想法非常好,我添加了 Java 9 results 调用,它返回一个 Stream。由于 OP 想使用split,我已经收集到String[],就像split 一样。

    如果逗号分隔符 (a, b, "c,d") 后面有空格,请注意。然后你需要改变模式。

    Jshell 演示

    $ jshell
    -> String so = "123,test,444,\"don't split, this\",more test,1";
    |  Added variable so of type String with initial value "123,test,444,"don't split, this",more test,1"
    
    -> Pattern.compile("\"[^\"]*\"|[^,]+").matcher(so).results();
    |  Expression value is: java.util.stream.ReferencePipeline$Head@2038ae61
    |    assigned to temporary variable $68 of type java.util.stream.Stream<MatchResult>
    
    -> $68.map(MatchResult::group).toArray(String[]::new);
    |  Expression value is: [Ljava.lang.String;@6b09bb57
    |    assigned to temporary variable $69 of type String[]
    
    -> Arrays.stream($69).forEach(System.out::println);
    123
    test
    444
    "don't split, this"
    more test
    1
    

    代码

    String so = "123,test,444,\"don't split, this\",more test,1";
    Pattern.compile("\"[^\"]*\"|[^,]+")
        .matcher(so)
        .results()
        .map(MatchResult::group)
        .toArray(String[]::new);
    

    说明

    1. 正则表达式 [^"] 匹配:引号,除引号外的任何内容,引号。
    2. 正则表达式 [^"]* 匹配:一个引号,除了 0(或更多)次引号之外的任何内容,一个引号。
    3. 该正则表达式需要首先“获胜”,否则匹配除了逗号 1 次或更多次 - 即:[^,]+ - 将“获胜”。
    4. results() 需要 Java 9 或更高版本。
    5. 它返回Stream&lt;MatchResult&gt;,我使用group() 调用将其映射并收集到字符串数组。无参数的toArray() 调用将返回Object[]

    【讨论】:

      【解决方案5】:

      请看下面的代码sn-p。此代码仅考虑快乐流。根据您的要求更改

      public static String[] splitWithEscape(final String str, char split,
              char escapeCharacter) {
          final List<String> list = new LinkedList<String>();
      
          char[] cArr = str.toCharArray();
      
          boolean isEscape = false;
          StringBuilder sb = new StringBuilder();
      
          for (char c : cArr) {
              if (isEscape && c != escapeCharacter) {
                  sb.append(c);
              } else if (c != split && c != escapeCharacter) {
                  sb.append(c);
              } else if (c == escapeCharacter) {
                  if (!isEscape) {
                      isEscape = true;
                      if (sb.length() > 0) {
                          list.add(sb.toString());
                          sb = new StringBuilder();
                      }
                  } else {
                      isEscape = false;
                  }
      
              } else if (c == split) {
                  list.add(sb.toString());
                  sb = new StringBuilder();
              }
          }
      
          if (sb.length() > 0) {
              list.add(sb.toString());
          }
      
          String[] strArr = new String[list.size()];
      
          return list.toArray(strArr);
      }
      

      【讨论】:

        猜你喜欢
        • 2015-11-30
        • 2019-07-05
        • 1970-01-01
        • 2011-12-25
        相关资源
        最近更新 更多