Непонятный вывод из String.split

Я не понимаю вывод этого кода:

public class StringDemo{              
    public static void main(String args[]) {
        String blank = "";                    
        String comma = ",";                   
        System.out.println("Output1: "+blank.split(",").length);  
        System.out.println("Output2: "+comma.split(",").length);  
    }
}

И получил следующий вывод:

Output1: 1 
Output2: 0

8 ответов

Документация:

За: System.out.println("Output1: "+blank.split(",").length);

Массив, возвращаемый этим методом, содержит каждую подстроку этой строки, которая заканчивается другой подстрокой, которая соответствует данному выражению или заканчивается концом строки. Подстроки в массиве расположены в том порядке, в котором они встречаются в этой строке. Если выражение не совпадает ни с одной частью ввода, тогда результирующий массив имеет только один элемент, а именно эту строку.

Он просто вернет всю строку, поэтому возвращает 1.


Во втором случае String.split откажется от , поэтому результат будет пустым.

String.split silently discards trailing separators

см. guava StringsExplained тоже

Все происходит по плану, но давайте сделаем это шаг за шагом (надеюсь, у вас есть время).

Согласно документацииисходному коду) split(String regex) метод:

Этот метод работает так, как если бы он вызывал метод разбиения с двумя аргументами с данным выражением и предельным аргументом, равным нулю.

Поэтому, когда вы вызываете

split(String regex)

вы на самом деле получаете результат от split(String regex, int limit) метод, который вызывается таким образом:

split(regex, 0)

Так вот limit установлен в 0,

Вам нужно знать кое-что об этом параметре:

  • Если limit положительно, вы ограничиваете длину результирующего массива указанным вами положительным числом, поэтому "axaxaxaxa".split("x",2) вернет массив, ["a", "axaxaxa"] не ["a","a","a","a","a"],
  • Если limit является 0 тогда вы не ограничиваете длину результирующего массива. Но это также означает, что любые завершающие пустые строки будут удалены. Например:

    "fooXbarX".split("X")
    

    при запуске сгенерирует массив, который будет выглядеть так:

    ["foo", "bar", ""]
    

    ("barX" разделить на "X" генерирует "bar" а также ""), но с тех пор split удаляет все завершающие пустые строки, он вернет

    ["foo", "bar"]
    
  • Поведение отрицательного значения limit похоже на поведение, где предел установлен на 0 (это не будет ограничивать длину массива результатов). Единственное отличие состоит в том, что он не удалит пустые строки из конца результирующего массива. Другими словами

    "fooXbarX".split("X",-1)
    

вернусь ["foo", "bar", ""]


Давайте посмотрим на дело,

",".split(",").length

который (как объяснено ранее) такой же, как

",".split(",", 0).length

Это означает, что мы используем версию split, которая не будет ограничивать длину результирующего массива, но удалит все завершающие пустые строки, "", Вы должны понимать, что когда мы разделяем одну вещь, мы всегда получаем две вещи.

Другими словами, если мы разделим "abc" на месте b, мы получим "a" а также "c",
Сложнее понять, что если мы разделим "abc" в c мы получим "ab" а также "" (пустой строки).

Используя эту логику, если мы разделим "," на , мы получим "" а также "" (две пустые строки).

Вы можете проверить это используя split с отрицательным пределом:

for (String s: ",".split(",", -1)){
    System.out.println("\""+s+"\"");
}

который напечатает

""
""

Итак, как мы видим, массив результатов здесь сначала ["", ""],

Но так как по умолчанию мы используем limit установлен в 0, все завершающие пустые строки будут удалены. В этом случае результирующий массив содержит только завершающие пустые строки, поэтому все они будут удалены, оставляя вас с пустым массивом. [] который имеет длину 0,


Чтобы ответить на случай с

"".split(",").length

Вы должны понимать, что удаление конечных пустых строк имеет смысл, только если такие конечные пустые строки являются результатом разделения (и, скорее всего, не нужны).
Таким образом, если бы не было мест, в которых мы могли бы разделиться, не было бы возможности создать пустую строку, поэтому нет смысла запускать этот процесс "очистки".

Эта информация упоминается в документации split(String regex, int limit) метод, где вы можете прочитать:

Если выражение не совпадает ни с одной частью ввода, тогда результирующий массив имеет только один элемент, а именно эту строку.

Вы также можете увидеть это поведение в исходном коде этого метода (из Java 8):

 2316 public String [] split (регулярное выражение строки, предельное значение int) { 
2317 / * fastpath, если регулярное выражение является
2318 (1) Строка с одним символом, и этот персонаж не является одним из
2319 Мета-символы RegEx ".$|()[{^?*+\\", или
2320 (2) Строка с двумя символами и первый символ с обратной косой чертой и
2321 второй не является цифрой ascii или буквой ascii.
2322 * /
2323 символа ch = 0;
2324 if (((regex.value.length == 1 &&
2325 ". $ | () [{^? * + \\". indexOf (ch = regex. charAt (0)) == -1) ||
2326 (регулярное выражение length () == 2 &&
2327 регулярное выражение charAt (0) == '\\' &&
2328 (((ch = regex. CharAt (1)) - '0') | ('9'-ch)) <0 &&
2329 ((ch-'a ') | (' z'-ch)) <0 &&
2330 ((ch-'A ') | (' Z'-ch)) <0)) &&
2331 (ch 2332 ch> Character.MAX_LOW_SURROGATE))
2333 {
2334 int off = 0;
2335 int следующий = 0;
2336 логическое ограничение = предел> 0;
2337 ArrayList < String > list = new ArrayList <> ();
2338 while ((next = indexOf (ch, off))! = -1) {
2339 if (! Limited || list. Size () 2340 список. добавить ( подстрока (выкл, следующая));
2341 выкл = следующий + 1;
2342 } else {// последний
2343 // assert (list.size() == limit - 1);
2344 список. добавить ( подстрока (выкл, значение. длина));
2345 off = value.length;
2346 перерыв;
2347 }
2348 }
2349 // Если совпадений не найдено, верните это
2350 if (off == 0)
2351 вернуть новую строку [] { this };
2353 // Добавить оставшийся сегмент
2354 if (! Limited || list. Size () 2355 список. добавить ( подстрока (выкл, значение. длина));
2357 // Построить результат
2358 int resultSize = список. размер ();
2359 if (limit == 0) {
2360 while (resultSize> 0 && list. Get (resultSize - 1). Length () == 0) {
2361 resultSize--;
2362 }
2363 }
2364 String [] result = new String [resultSize];
2365 возвратный список. subList (0, resultSize). toArray (результат);
2366 }
2367 Возвращение Выкройки. компилировать (регулярное выражение). разделить (это, предел);
2368 }

где вы можете найти

if (off == 0)
    return new String[]{this};

фрагмент, который означает

  • if (off == 0) - если off (позиция, с которой метод должен начать поиск следующего возможного соответствия для регулярного выражения, переданного как split аргумент) еще 0 после перебора всей строки мы не нашли соответствия, поэтому строка не была разбита
  • return new String[]{this}; - в этом случае давайте просто вернем массив с оригинальной строкой (представленной this).

поскольку "," не может быть найден в "" даже однажды, "".split(",") должен вернуть массив с одним элементом (пустой строкой, к которой вы вызвали split). Это означает, что длина этого массива 1,

КСТАТИ. Java 8 представила другой механизм. Он удаляет начальные пустые строки (если они были созданы во время процесса разделения), если мы разделяем, используя регулярное выражение нулевой длины (например, "" или с осмотром (?<!x)). Больше информации на: Почему в Java 8 split иногда удаляет пустые строки в начале массива результатов?

Из документации Java 1.7

Разбивает строку вокруг совпадений заданного регулярного выражения.

Метод split() работает так, как если бы он вызывал метод split с двумя аргументами с данным выражением и предельным аргументом, равным нулю. Поэтому завершающие пустые строки не включаются в результирующий массив.

В случае 1 blank.split(",") does not match any part of the input then the resulting array has just one element, namely this String.

It will return entire String. Итак, длина будет 1,

В случае 2 comma.split(",") will return empty.

split() ожидая регулярное выражение в качестве аргумента, верните массив результатов в соответствие с этим регулярным выражением.

Итак, длина 0

Например( Документация)

Строка "boo: and: foo", дает следующие результаты с этими выражениями:

Regex     Result
  :     { "boo", "and", "foo" }
  o     { "b", "", ":and:f" }

Параметры: regex - регулярное выражение-разделитель

Возвращает: массив строк, вычисленный путем разбиения этой строки вокруг совпадений заданного регулярного выражения

Броски: PatternSyntaxException - если синтаксис регулярного выражения недопустим

Из строкового класса Javadoc для public String[] split(String regex) метод:

Разбивает эту строку вокруг совпадений заданного регулярного выражения.

Этот метод работает так, как если бы он вызывал метод разбиения с двумя аргументами с заданным выражением и предельным аргументом, равным нулю. Поэтому завершающие пустые строки не включаются в результирующий массив.

В первом случае выражение не совпадает ни с одной частью ввода, поэтому мы получили массив только с одним элементом - входом.

Во втором случае выражение соответствует вводу, и split должен вернуть две пустые строки; но, согласно javadoc, они отбрасываются (потому что они тянутся и пусты).

Мы можем взглянуть на исходный кодjava.util.regex.Pattern который позади String.split, Путь вниз по кроличьей норе методом

public String[] split(CharSequence input, int limit)

вызывается.

вход ""

Для ввода "" этот метод называется

String[] parts = split("", 0);

Интересная часть этого метода:

  int index = 0;
  boolean matchLimited = limit > 0;
  ArrayList<String> matchList = new ArrayList<>();
  Matcher m = matcher(input);

  while(m.find()) {
    // Tichodroma: this will not happen for our input
  }

  // If no match was found, return this
  if (index == 0)
    return new String[] {input.toString()};

И вот что происходит: new String[] {input.toString()} возвращается

вход ","

Для ввода "," интересная часть

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);

Вот resultSize == 0 а также limit == 0 так new String[0] возвращается

От JDK 1,7

 public String[] split(String regex, int limit) {
        /* fastpath if the regex is a
           (1)one-char String and this character is not one of the
              RegEx's meta characters ".$|()[{^?*+\\", or
           (2)two-char String and the first char is the backslash and
              the second is not the ascii digit or ascii letter.
        */
        char ch = 0;
        if (((regex.count == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    list.add(substring(off, count));
                    off = count;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[] { this };

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, count));

            // Construct result
            int resultSize = list.size();
            if (limit == 0)
                while (resultSize > 0 && list.get(resultSize-1).length() == 0)
                    resultSize--;
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

Таким образом, для этого случая регулярное выражение будет обрабатываться первым if,

Для первого случая blank.split(",")

// If no match was found, return this
if (off == 0)
   return new String[] { this };

Таким образом, эта функция будет возвращать массив, который содержит один элемент, если нет совпадений.

Для второго случая comma.split(",")

List<String> list = new ArrayList<>();
//...
int resultSize = list.size();
if (limit == 0)
    while (resultSize > 0 && list.get(resultSize-1).length() == 0)
           resultSize--;
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);

Как вы заметили, последний цикл while удалил все пустые элементы в конце списка, поэтому resultSize 0,

String blank = "";                    
String comma = ",";                   
System.out.println("Output1: "+blank.split(",").length);  // case 1
System.out.println("Output2: "+comma.split(",").length);  // case 2

Случай 1 - Здесь blank.split(",") вернусь "" так как нет , в blank вы получите то же самое, так что длина будет 1

случай 2- Здесь comma.split(",") вернет пустой массив , если ты хочешь считать comma с длиной 1 иначе длина будет 0

Снова comma.split(",") split() ожидает regex в качестве аргумента он вернет массив результатов в соответствие с этим regex,

Массив, возвращаемый этим методом, содержит каждую подстроку этой строки, которая заканчивается другой подстрокой, которая соответствует данному выражению или заканчивается концом строки.

еще

Если выражение не совпадает ни с одной частью ввода, тогда результирующий массив имеет только один элемент, а именно эту строку.

API для метода split гласит: "Если выражение не соответствует какой-либо части входных данных, то результирующий массив имеет только один элемент, а именно эту строку".

Таким образом, так как бланк String не содержит ",", возвращается String[] с одним элементом (т.е. самим бланком).

Для строковой запятой "ничего" не осталось от исходной строки, поэтому возвращается пустой массив.

Это кажется лучшим решением, если вы хотите обработать возвращенный результат, например

String[] splits = aString.split(",");
for(String split: splits) {
   // do something
}
Другие вопросы по тегам