Java:拆分逗号分隔的字符串,但忽略引号中的逗号
我有一根细绳,大概是这样的:Java:拆分逗号分隔的字符串,但忽略引号中的逗号,java,regex,string,Java,Regex,String,我有一根细绳,大概是这样的: foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy" boolean foundQuote = false; if(charAtIndex(currentStringIndex) == '"') { foundQuote = true; } if(foundQuote == true) { //do nothing } else { string[] split = currentString.s
foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy"
boolean foundQuote = false;
if(charAtIndex(currentStringIndex) == '"')
{
foundQuote = true;
}
if(foundQuote == true)
{
//do nothing
}
else
{
string[] split = currentString.split(',');
}
我想用逗号分开,但我需要忽略引号中的逗号。我该怎么做?似乎regexp方法失败了;我想当我看到一个报价时,我可以手动扫描并进入一个不同的模式,但最好使用现有的库。(编辑:我想我指的是已经是JDK的一部分或者已经是Apache Commons等常用库的一部分的库。)
上述字符串应分为:
foo
bar
c;qual="baz,blurb"
d;junk="quux,syzygy"
注意:这不是一个CSV文件,它是一个包含在整体结构更大的文件中的单个字符串请尝试:
public class Main {
public static void main(String[] args) {
String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
String[] tokens = line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
for(String t : tokens) {
System.out.println("> "+t);
}
}
}
输出:
> foo
> bar
> c;qual="baz,blurb"
> d;junk="quux,syzygy"
换句话说:仅当逗号前面有零个引号或偶数个引号时,才在逗号上拆分
或者,对眼睛来说更友好一点:
public class Main {
public static void main(String[] args) {
String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
String otherThanQuote = " [^\"] ";
String quotedString = String.format(" \" %s* \" ", otherThanQuote);
String regex = String.format("(?x) "+ // enable comments, ignore white spaces
", "+ // match a comma
"(?= "+ // start positive look ahead
" (?: "+ // start non-capturing group 1
" %s* "+ // match 'otherThanQuote' zero or more times
" %s "+ // match 'quotedString'
" )* "+ // end group 1 and repeat it zero or more times
" %s* "+ // match 'otherThanQuote'
" $ "+ // match the end of the string
") ", // stop positive look ahead
otherThanQuote, quotedString, otherThanQuote);
String[] tokens = line.split(regex, -1);
for(String t : tokens) {
System.out.println("> "+t);
}
}
}
这将产生与第一个示例相同的结果
编辑
正如@MikeFHay在评论中提到的:
我更喜欢使用,因为它有更合理的默认值(请参阅上面关于通过String#split()
修剪空匹配的讨论),所以我:
Splitter.on(Pattern.compile(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"))
我会这样做:
foo,bar,c;qual="baz,blurb",d;junk="quux,syzygy"
boolean foundQuote = false;
if(charAtIndex(currentStringIndex) == '"')
{
foundQuote = true;
}
if(foundQuote == true)
{
//do nothing
}
else
{
string[] split = currentString.split(',');
}
(上一个库的分支,它允许生成的输出在不运行Windows时具有Windows行终止符\r\n
)
不要使用lookahead和其他疯狂的正则表达式,只需首先取出引号。也就是说,对于每个引号分组,将该分组替换为
\u IDENTIFIER\u 1
或其他一些指示符,并将该分组映射到字符串、字符串的映射
使用逗号拆分后,用原始字符串值替换所有映射的标识符。尝试类似的
(?!\”、(?!\”
。这应该与、
匹配,它们没有被“
包围。您正处于一个恼人的边界区域,regexp几乎无法完成(正如巴特所指出的那样,逃避引用会让生活变得艰难),但一个成熟的解析器似乎有些矫枉过正
如果您可能很快需要更高的复杂性,我会去找一个解析器库。例如,我很不耐烦,选择不等待答案…作为参考,这样做看起来并不难(这适用于我的应用程序,我不需要担心转义引号,因为引号中的内容仅限于一些受约束的形式):
final静态私有模式splitSearchPattern=Pattern.compile(“[\”,]”);
私有列表按逗号分隔(字符串s){
如果(s==null)
返回集合。emptyList();
列表=新的ArrayList();
匹配器m=splitSearchPattern.Matcher;
int pos=0;
布尔quoteMode=false;
while(m.find())
{
字符串sep=m.group();
如果(“\”。等于(sep))
{
quoteMode=!quoteMode;
}
else如果(!quoteMode&&,“.equals(sep))
{
int-toPos=m.start();
列表。添加(s.子字符串(位置、拓扑));
pos=m.end();
}
}
如果(位置
(读者练习:通过查找反斜杠扩展到处理转义引号。)虽然我通常喜欢正则表达式,但对于这种依赖于状态的标记化,我相信有一个简单的解析器(在本例中,它比这个词听起来简单得多)可能是一种更清洁的解决方案,尤其是在可维护性方面,例如:
String input = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
List<String> result = new ArrayList<String>();
int start = 0;
boolean inQuotes = false;
for (int current = 0; current < input.length(); current++) {
if (input.charAt(current) == '\"') inQuotes = !inQuotes; // toggle state
else if (input.charAt(current) == ',' && !inQuotes) {
result.add(input.substring(start, current));
start = current + 1;
}
}
result.add(input.substring(start));
String input=“foo,bar,c;qual=”baz,blurb\”,d;junk=”qux,syzygy\”;
列表结果=新建ArrayList();
int start=0;
布尔inQuotes=false;
对于(int current=0;current
如果您不关心保留引号中的逗号,可以通过将引号中的逗号替换为其他逗号,然后在逗号处拆分来简化此方法(无需处理起始索引,无需最后一个字符的特殊情况):
String input = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
StringBuilder builder = new StringBuilder(input);
boolean inQuotes = false;
for (int currentIndex = 0; currentIndex < builder.length(); currentIndex++) {
char currentChar = builder.charAt(currentIndex);
if (currentChar == '\"') inQuotes = !inQuotes; // toggle state
if (currentChar == ',' && inQuotes) {
builder.setCharAt(currentIndex, ';'); // or '♡', and replace later
}
}
List<String> result = Arrays.asList(builder.toString().split(","));
String input=“foo,bar,c;qual=”baz,blurb\”,d;junk=”qux,syzygy\”;
StringBuilder=新的StringBuilder(输入);
布尔inQuotes=false;
对于(int currentIndex=0;currentIndex
我不会建议Bart给出正则表达式的答案,我发现在这种特殊情况下解析解决方案更好(正如Fabian所建议的)。我已经尝试过正则表达式解决方案和自己的解析实现,我发现:
String tested = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\",";
long start = System.nanoTime();
String[] tokens = tested.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
long timeWithSplitting = System.nanoTime() - start;
start = System.nanoTime();
List<String> tokensList = new ArrayList<String>();
boolean inQuotes = false;
StringBuilder b = new StringBuilder();
for (char c : tested.toCharArray()) {
switch (c) {
case ',':
if (inQuotes) {
b.append(c);
} else {
tokensList.add(b.toString());
b = new StringBuilder();
}
break;
case '\"':
inQuotes = !inQuotes;
default:
b.append(c);
break;
}
}
tokensList.add(b.toString());
long timeWithParsing = System.nanoTime() - start;
System.out.println(Arrays.toString(tokens));
System.out.println(tokensList.toString());
System.out.printf("Time with splitting:\t%10d\n",timeWithSplitting);
System.out.printf("Time with parsing:\t%10d\n",timeWithParsing);
String tested=“foo,bar,c;qual=”baz,blurb\”,d;junk=”qux,syzygy\”;
长启动=System.nanoTime();
String[]tokens=tested.split(“,(?=([^\”]*\“[^\”]*\”*[^\“]*\”*”)*[^\“]*$”);
long-timeWithSplitting=System.nanoTime()-开始;
start=System.nanoTime();
List tokensList=new ArrayList();
布尔inQuotes=false;
StringBuilder b=新的StringBuilder();
for(char c:tested.toCharArray()){
开关(c){
案例',':
如果(以引号引){
b、 附加(c);
}否则{
tokensList.add(b.toString());
b=新的StringBuilder();
}
打破
案例“\”:
inQuotes=!inQuotes;
违约:
b、 附加(c);
打破
String[] a = p.matcher(input).results()
.map(m -> m.group(m.start(1)<0? 2: 1))
.toArray(String[]::new);
for(Matcher m = p.matcher(input); m.find(); ) {
String token = m.group(m.start(1)<0? 2: 1);
System.out.println("found: "+token);
}
Pattern p = Pattern.compile("\\G((\"(.*?)\"|[^,])*),?");
String s = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\"";
String[] split = s.split( "(?<!\".{0,255}[^\"]),|,(?![^\"].*\")" );