Как разделить строку, но при этом сохранить разделители?
У меня есть многострочная строка, которая разделена набором разных разделителей:
(Text1)(DelimiterA)(Text2)(DelimiterC)(Text3)(DelimiterB)(Text4)
Я могу разделить эту строку на ее части, используя String.split
, но, похоже, я не могу получить фактическую строку, которая соответствовала регулярному выражению с разделителем.
Другими словами, это то, что я получаю:
Text1
Text2
Text3
Text4
Это то, что я хочу
Text1
DelimiterA
Text2
DelimiterC
Text3
DelimiterB
Text4
Есть ли какой-либо способ JDK разделить строку с помощью регулярного выражения-разделителя, но при этом сохранить разделители?
Переведено автоматически
Ответ 1
Вы можете использовать lookahead и lookbehind , которые являются функциями регулярных выражений.
System.out.println(Arrays.toString("a;b;c;d".split("(?<=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("(?=;)")));
System.out.println(Arrays.toString("a;b;c;d".split("((?<=;)|(?=;))")));
И вы получите:
[a;, b;, c;, d]
[a, ;b, ;c, ;d]
[a, ;, b, ;, c, ;, d]
Последнее - это то, что вы хотите.
((?<=;)|(?=;))
равно выделению пустого символа до ;
или после ;
.
РЕДАКТИРОВАТЬ: Комментарии Фабиана Стига по удобочитаемости верны. С регулярными выражениями всегда проблема с удобочитаемостью. Одна вещь, которую я делаю, чтобы сделать регулярные выражения более удобочитаемыми, - это создать переменную, имя которой отражает то, что делает регулярное выражение. Вы даже можете поместить заполнители (например, %1$s
) и использовать Java String.format
для замены заполнителей фактической строкой, которую вам нужно использовать; например:
static public final String WITH_DELIMITER = "((?<=%1$s)|(?=%1$s))";
public void someMethod() {
final String[] aEach = "a;b;c;d".split(String.format(WITH_DELIMITER, ";"));
...
}
Ответ 2
Вы хотите использовать поисковые системы и разделять по совпадениям нулевой ширины. Вот несколько примеров.:
public class SplitNDump {
static void dump(String[] arr) {
for (String s : arr) {
System.out.format("[%s]", s);
}
System.out.println();
}
public static void main(String[] args) {
dump("1,234,567,890".split(","));
// "[1][234][567][890]"
dump("1,234,567,890".split("(?=,)"));
// "[1][,234][,567][,890]"
dump("1,234,567,890".split("(?<=,)"));
// "[1,][234,][567,][890]"
dump("1,234,567,890".split("(?<=,)|(?=,)"));
// "[1][,][234][,][567][,][890]"
dump(":a:bb::c:".split("(?=:)|(?<=:)"));
// "[][:][a][:][bb][:][:][c][:]"
dump(":a:bb::c:".split("(?=(?!^):)|(?<=:)"));
// "[:][a][:][bb][:][:][c][:]"
dump(":::a::::b b::c:".split("(?=(?!^):)(?<!:)|(?!:)(?<=:)"));
// "[:::][a][::::][b b][::][c][:]"
dump("a,bb:::c d..e".split("(?!^)\\b"));
// "[a][,][bb][:::][c][ ][d][..][e]"
dump("ArrayIndexOutOfBoundsException".split("(?<=[a-z])(?=[A-Z])"));
// "[Array][Index][Out][Of][Bounds][Exception]"
dump("1234567890".split("(?<=\\G.{4})"));
// "[1234][5678][90]"
// Split at the end of each run of letter
dump("Boooyaaaah! Yippieeee!!".split("(?<=(?=(.)\\1(?!\\1))..)"));
// "[Booo][yaaaa][h! Yipp][ieeee][!!]"
}
}
И да, в последнем шаблоне это утверждение с тройным вложением.
Вопросы по теме
- Java split съедает мои символы.
- Можете ли вы использовать регулярное выражение, соответствующее нулевой ширине, при разделении строки?
- Как мне преобразовать camelCase в понятные человеку имена в Java?
- Обратные ссылки в lookbehind
Смотрите также
Ответ 3
Очень наивным решением, которое не требует регулярного выражения, было бы выполнить замену строки в вашем разделителе в строках (предполагая, что разделитель заменен запятой):
string.replace(FullString, "," , "~,~")
Где вы можете заменить тильду (~) на соответствующий уникальный разделитель.
Тогда, если вы выполните разделение с вашим новым разделителем, я верю, что вы получите желаемый результат.
Ответ 4
import java.util.regex.*;
import java.util.LinkedList;
public class Splitter {
private static final Pattern DEFAULT_PATTERN = Pattern.compile("\\s+");
private Pattern pattern;
private boolean keep_delimiters;
public Splitter(Pattern pattern, boolean keep_delimiters) {
this.pattern = pattern;
this.keep_delimiters = keep_delimiters;
}
public Splitter(String pattern, boolean keep_delimiters) {
this(Pattern.compile(pattern==null?"":pattern), keep_delimiters);
}
public Splitter(Pattern pattern) { this(pattern, true); }
public Splitter(String pattern) { this(pattern, true); }
public Splitter(boolean keep_delimiters) { this(DEFAULT_PATTERN, keep_delimiters); }
public Splitter() { this(DEFAULT_PATTERN); }
public String[] split(String text) {
if (text == null) {
text = "";
}
int last_match = 0;
LinkedList<String> splitted = new LinkedList<String>();
Matcher m = this.pattern.matcher(text);
while (m.find()) {
splitted.add(text.substring(last_match,m.start()));
if (this.keep_delimiters) {
splitted.add(m.group());
}
last_match = m.end();
}
splitted.add(text.substring(last_match));
return splitted.toArray(new String[splitted.size()]);
}
public static void main(String[] argv) {
if (argv.length != 2) {
System.err.println("Syntax: java Splitter <pattern> <text>");
return;
}
Pattern pattern = null;
try {
pattern = Pattern.compile(argv[0]);
}
catch (PatternSyntaxException e) {
System.err.println(e);
return;
}
Splitter splitter = new Splitter(pattern);
String text = argv[1];
int counter = 1;
for (String part : splitter.split(text)) {
System.out.printf("Part %d: \"%s\"\n", counter++, part);
}
}
}
/*
Example:
> java Splitter "\W+" "Hello World!"
Part 1: "Hello"
Part 2: " "
Part 3: "World"
Part 4: "!"
Part 5: ""
*/
Мне не очень нравится другой способ, когда вы получаете пустой элемент спереди и сзади. Разделитель обычно не находится ни в начале, ни в конце строки, таким образом, вы чаще всего теряете два хороших слота массива.
Правка: Исправлены предельные случаи. Прокомментированный источник с тестовыми примерами можно найти здесь: http://snippets.dzone.com/posts/show/6453