Dlaczego w Javie 8 split czasami usuwa puste ciągi na początku tablicy wyników?

Przed Java 8 kiedy dzielimy na pusty łańcuch jak

String[] tokens = "abc".split("");

Mechanizm podziału podzieliłby się w miejscach oznaczonych |

|a|b|c|

Ponieważ pusta przestrzeń "" istnieje przed i po każdym znaku. Tak więc jako wynik generowałby on najpierw tą tablicę

["", "a", "b", "c", ""]

I później usunie końcowe puste ciągi (ponieważ nie podaliśmy jawnie ujemnej wartości argumentu limit), więc w końcu zwróci

["", "a", "b", "c"]

W Wydaje się, że mechanizm podziału Java 8 uległ zmianie. Teraz kiedy używamy

"abc".split("")

Otrzymamy ["a", "b", "c"] tablicę zamiast ["", "a", "b", "c"], więc wygląda na to, że puste ciągi na początku są również usuwane. Ale ta teoria zawodzi, ponieważ na przykład

"abc".split("a")

Zwraca tablicę z pustym łańcuchem na początku ["", "bc"].

Czy ktoś może wyjaśnić o co tu chodzi i jak zmieniły się zasady podziału w tym przypadku w Javie 8?

Author: Community, 2014-03-28

3 answers

Zachowanie String.split (wywołujące Pattern.split) zmienia się pomiędzy Javą 7 a Javą 8.

Dokumentacja

Porównując dokumentację Pattern.splitw Java 7 i Java 8 , obserwujemy dodawanie następującej klauzuli:

Jeżeli na początku sekwencji wejściowej występuje dopasowanie o dodatniej szerokości, to na początku tablicy wynikowej jest dołączony pusty podłańcuch wiodący. Mecz o zerową szerokość na początku jednak nigdy wytworzy taki pusty podciąg wiodący.

Ta sama klauzula jest również dodana do String.splitw Java 8, w porównaniu do Java 7 .

Implementacja odniesienia

Porównajmy kod Pattern.split implementacji odniesienia w Javie 7 i Javie 8. Kod jest pobierany z grepcode, dla wersji 7u40-b43 i 8-b132.

Java 7

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

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

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).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);
}

Java 8

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

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

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).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);
}

Dodanie następującego kodu w Javie 8 wyklucza zerową długość dopasuj na początku łańcucha wejściowego, co wyjaśnia zachowanie powyżej.

            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }

Utrzymanie zgodności

[[38]}następujące zachowanie w Javie 8 i nowszych [39]}

Aby split zachowywał się konsekwentnie we wszystkich wersjach i był zgodny z zachowaniem w Javie 8:

  1. jeśli Twoje regex może dopasować łańcuch o zerowej długości, po prostu dodaj (?!\A) na koniec Z regex i zawiń oryginalne regex w grupie nie przechwytywającej (?:...) (jeśli konieczne).
  2. jeśli twój regex nie może dopasować łańcucha o zerowej długości, nie musisz nic robić.
  3. jeśli nie wiesz, czy regex może dopasować łańcuch o zerowej długości, wykonaj obie akcje w kroku 1.

(?!\A) sprawdza, czy łańcuch nie kończy się na początku łańcucha, co oznacza, że dopasowanie jest puste na początku łańcucha.

[[38]}następujące zachowanie w Javie 7 i wcześniejsze

Nie ma ogólnego rozwiązania upewnij się, że split jest wstecznie kompatybilny z Java 7 i wcześniejszymi, bez zastąpienia wszystkich instancji split, aby wskazać własną, niestandardową implementację.

 77
Author: nhahtdh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-30 04:13:54

Zostało to określone w dokumentacji split(String regex, limit).

Gdy na początku tego ciągu występuje dopasowanie dodatniej szerokości wtedy na początku jest dołączony pusty podłańcuch wiodący wynikowa tablica. Mecz o zerową szerokość na początku jednak nigdy wytworzy taki pusty podciąg wiodący.

W "abc".split("") masz dopasowanie o zerowej szerokości na początku, więc wiodący pusty podłańcuch nie jest zawarty w wynikowej tablicy.

Jednak w drugim fragmencie po rozdzieleniu na "a" otrzymujesz dodatnie dopasowanie szerokości (w tym przypadku 1), więc pusty podciąg wiodący jest uwzględniany zgodnie z oczekiwaniami.

(usunięto nieistotny kod źródłowy)

 30
Author: Alexis C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-19 16:28:19

Nastąpiła drobna zmiana w dokumentach dla split() od Javy 7 do Javy 8. W szczególności dodano następujące stwierdzenie:

Jeżeli na początku tego łańcucha jest dopasowanie o dodatniej szerokości, to na początku tablicy wynikowej jest dołączony pusty podłańcuch wiodący. dopasowanie o zerowej szerokości na początku nigdy nie tworzy takiego pustego podcięcia.

(moje)

Pusty podział łańcuchów generuje dopasowanie o zerowej szerokości na początku, więc pusty łańcuch nie jest dołączany na początku wynikowej tablicy zgodnie z powyższym opisem. Dla kontrastu, twój drugi przykład, który dzieli się na "a" generuje dodatnie dopasowanie szerokości na początku łańcucha, więc pusty łańcuch jest w rzeczywistości zawarty na początku tablicy wynikowej.

 12
Author: arshajii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-28 16:57:43