JavaRush /Blog Java /Random-PL /Stringi w Javie (klasa java.lang.String)
Viacheslav
Poziom 3

Stringi w Javie (klasa java.lang.String)

Opublikowano w grupie Random-PL

Wstęp

Ścieżka programisty to złożony i długi proces. W większości przypadków zaczyna się od programu wyświetlającego na ekranie Hello World. Java nie jest wyjątkiem (zobacz Lekcję: Aplikacja „Hello World!” ). Jak widzimy, komunikat jest wysyłany za pomocą System.out.println("Hello World!"); interfejsu API języka Java, metoda System.out.println przyjmuje String jako parametr wejściowy . Ten typ danych zostanie omówiony.

String jako ciąg znaków

Właściwie String przetłumaczony z angielskiego jest ciągiem. Zgadza się, typ String reprezentuje ciąg tekstowy. Co to jest ciąg tekstowy? Ciąg tekstowy to rodzaj uporządkowanej sekwencji znaków, które następują po sobie. Symbolem jest znak. Sekwencja – sekwencja. Więc tak, absolutnie poprawne, String jest implementacją java.lang.CharSequence. A jeśli zajrzysz do samej klasy String, to znajdziesz w niej tylko tablicę znaków: private final char value[]; ma ona java.lang.CharSequencedość prosty kontrakt:
Stringi w Javie (klasa java.lang.String) - 1
Mamy metodę uzyskiwania liczby elementów, pobierania określonego elementu i uzyskiwania zestawu elementów + samą metodę toString, która to zwróci). Bardziej interesujące jest zrozumienie metod, które przyszły do ​​nas w Javie 8, a to jest : chars()i codePoints() Przypomnijmy sobie z samouczka firmy Oracle „ Priitive Data” Types „, którym jest char single 16-bit Unicode character. Oznacza to, że zasadniczo char jest typem o połowę mniejszym od int (32 bity), który reprezentuje liczby od 0 do 65535 (patrz wartości dziesiętne w tabeli ASCII ). Oznacza to, że jeśli chcemy, możemy reprezentować znak jako int. I Java 8 wykorzystała to. Począwszy od wersji 8 Java, mamy IntStream - strumień do pracy z prymitywnymi intami. Dlatego w charSequence możliwe jest uzyskanie IntStream reprezentującego znaki lub codePoints. Zanim do nich przejdziemy, zobaczymy przykład pokazujący wygodę takiego podejścia. Użyjmy kompilatora Java Tutorialspoint online i wykonajmy kod:
public static void main(String []args){
        String line = "aaabccdddc";
        System.out.println( line.chars().distinct().count() );
}
Teraz w ten prosty sposób możesz zdobyć wiele unikalnych symboli.

Punkty kodowe

Więc mówiliśmy o znakach. Nie jest teraz jasne, jakiego rodzaju są to punkty kodowe. Koncepcja codePoint pojawiła się, ponieważ kiedy pojawiła się Java, do zakodowania znaku wystarczyło 16 bitów (pół int). Dlatego znak w Javie jest reprezentowany w formacie UTF-16 (specyfikacja „Unicode 88”). Później pojawił się Unicode 2.0, którego koncepcja polegała na reprezentowaniu znaku jako pary zastępczej (2 znaki). Pozwoliło nam to rozszerzyć zakres możliwych wartości do wartości int. Aby uzyskać więcej informacji, zobacz przepełnienie stosu: „ Porównywanie znaku z punktem kodowym? ” UTF-16 jest również wspomniany w JavaDoc dla Character . Tam, w JavaDoc, jest powiedziane, że: Jest In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF). dość trudne (a może nawet niemożliwe) odtworzenie tego w standardowych alfabetach. Ale symbole nie kończą się na literach i cyfrach. W Japonii wymyślono coś tak trudnego do zakodowania jak emoji – język ideogramów i emotikonów. Na Wikipedii jest ciekawy artykuł na ten temat: „ Emoji ”. Znajdźmy przykład emoji, na przykład: „ Emoji Ghost ”. Jak widzimy, jest tam nawet wskazany ten sam codePoint (wartość = U+1F47B). Jest on wskazany w formacie szesnastkowym. Jeśli zamienimy na liczbę dziesiętną, otrzymamy 128123. To więcej niż pozwala na to 16 bitów (tj. więcej niż 65535). Skopiujmy to:
Stringi w Javie (klasa java.lang.String) - 2
Niestety platforma JavaRush nie obsługuje takich znaków w tekście. Dlatego w poniższym przykładzie będziesz musiał wstawić wartość do String. Dlatego teraz zrozumiemy prosty test:
public static void main(String []args){
	    String emojiString = "Вставте сюда эмоджи через ctrl+v";
	    //На один emojiString приходится 2 чара (т.к. не влезает в 16 бит)
	    System.out.println(emojiString.codePoints().count()); //1
	    System.out.println(emojiString.chars().count()); //2
}
Jak widać, w tym przypadku 1 codePoint odpowiada 2 znakom. To jest magia.

Postać

Jak widzieliśmy powyżej, ciągi znaków w Javie składają się z znaków. Typ pierwotny pozwala na przechowywanie wartości, ale opakowanie java.lang.Characterna typ pierwotny pozwala na wykonanie wielu przydatnych rzeczy za pomocą tego symbolu. Na przykład możemy przekonwertować ciąg znaków na wielkie litery:
public static void main(String[] args) {
    String line = "организация объединённых наций";
    char[] chars = line.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        if (i == 0 || chars[i - 1] == ' ') {
            chars[i] = Character.toUpperCase(chars[i]);
        }
    }
    System.out.println(new String(chars));
}
No cóż, różne ciekawe rzeczy: isAlphabetic(), isLetter(), isSpaceChar(), isDigit(), isUpperCase(), isMirrored()(np. nawiasy. '(' ma lustrzane odbicie ')').

Basen sznurkowy

Ciągi znaków w Javie są niezmienne, czyli stałe. Jest to również wskazane w dokumencie JavaDoc samej klasy java.lang.String . Po drugie, bardzo ważne, ciągi znaków można określić jako literały:
String literalString = "Hello, World!";
String literalString = "Hello, World!";
Oznacza to, że każdy cytowany ciąg znaków, jak stwierdzono powyżej, jest w rzeczywistości obiektem. I tu nasuwa się pytanie – jeśli używamy ciągów znaków tak często i często mogą one być takie same (na przykład tekst „Błąd” lub „Pomyślnie”), czy jest jakiś sposób, aby upewnić się, że ciągi nie zostaną utworzone za każdym razem? Nawiasem mówiąc, nadal mamy Mapy, gdzie kluczem może być ciąg znaków. Wtedy zdecydowanie nie możemy mieć tych samych ciągów znaków będących różnymi obiektami, w przeciwnym razie nie będziemy mogli pobrać obiektu z mapy. Programiści Java myśleli, myśleli i wymyślili String Pool. Jest to miejsce w którym przechowywane są ciągi znaków, można je nazwać pamięcią podręczną ciągów. Nie wszystkie linie tam trafiają, ale tylko te, które są określone w kodzie za pomocą literału. Możesz sam dodać żyłkę do basenu, ale o tym później. Zatem w pamięci mamy gdzieś tę pamięć podręczną. Uczciwe pytanie: gdzie znajduje się ten basen? Odpowiedź na to pytanie można znaleźć na stackoverflow: „ Gdzie znajduje się pula stałych ciągów Java, sterta czy stos? " Znajduje się w pamięci sterty, w specjalnym obszarze stałej puli czasu wykonywania. Pula stała Runtime jest przydzielana, gdy maszyna wirtualna tworzy klasę lub interfejs z obszaru metod — specjalnego obszaru w stercie, do którego mają dostęp wszystkie wątki wewnątrz wirtualnej maszyny Java. Co daje nam String Pool? Ma to kilka zalet:
  • Obiekty tego samego typu nie zostaną utworzone
  • Porównanie przez odniesienie jest szybsze niż porównywanie znak po znaku za pomocą równań
Co jednak w sytuacji, gdy będziemy chcieli umieścić stworzony obiekt w tej pamięci podręcznej? Następnie mamy specjalną metodę: String.intern Ta metoda dodaje ciąg do puli String. Warto zaznaczyć, że nie jest to byle jaki rodzaj pamięci podręcznej w postaci tablicy (jak w przypadku liczb całkowitych). Metoda intern jest określona jako „natywna”. Oznacza to, że sama metoda jest zaimplementowana w innym języku (głównie C++). W przypadku podstawowych metod Java można do nich zastosować różne inne optymalizacje na poziomie JVM. Ogólnie rzecz biorąc, wydarzy się tutaj magia. Ciekawie jest przeczytać post o stażyście: https://habr.com/post/79913/#comment_2345814 I wydaje się, że to dobry pomysł. Ale jaki to będzie miało na nas wpływ? Ale to naprawdę będzie miało wpływ)
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal");
    System.out.println(test == test2);
}
Jak widać, linie są takie same, ale wynik będzie fałszywy. A wszystko dlatego, że == porównuje nie według wartości, ale przez odniesienie. A oto jak to działa:
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test == test2);
}
Pamiętaj tylko, że nadal będziemy tworzyć nowy String. Oznacza to, że intern zwróci nam String z pamięci podręcznej, ale oryginalny String, którego szukaliśmy w pamięci podręcznej, zostanie wyrzucony do czyszczenia, ponieważ nikt inny o nim nie wie. Jest to wyraźnie niepotrzebne zużycie zasobów =( Dlatego należy zawsze porównywać ciągi znaków za pomocą znaków równości, aby w miarę możliwości uniknąć nagłych i trudnych do wykrycia błędów.
public static void main(String[] args) {
    String test = "literal";
    String test2 = new String("literal").intern();
    System.out.println(test.equals(test2));
}
Program Equals porównuje ciąg znak po znaku.

Powiązanie

Jak pamiętamy, linie można dodawać. I jak pamiętamy, nasze ciągi znaków są niezmienne. Jak to w takim razie działa? Zgadza się, tworzona jest nowa linia, która składa się z symboli dodawanych obiektów. Istnieje milion wersji działania łączenia plus. Niektórzy myślą, że za każdym razem będzie nowy obiekt, inni, że będzie coś innego. Ale tylko jedna osoba może mieć rację. I tym kimś jest kompilator Java. Skorzystajmy z usługi kompilatora online i uruchommy:
public class HelloWorld {

    public static void main(String[] args) {
        String helloMessage = "Hello, ";
        String target = "World";
        System.out.println(helloMessage + target);
    }

}
Zapiszmy to teraz jako archiwum zip, rozpakujmy do katalogu i wykonajmy: javap –c HelloWorld I tutaj dowiemy się wszystkiego:
Stringi w Javie (klasa java.lang.String) - 3
Oczywiście w pętli lepiej jest wykonać konkatenację samodzielnie za pomocą StringBuilder. I to nie za sprawą jakiejś magii, ale po to, żeby StringBuilder był tworzony przed cyklem, a w samym cyklu następuje tylko dopisywanie. Swoją drogą, jest tu jeszcze jedna ciekawa rzecz. Jest świetny artykuł: „ Przetwarzanie ciągów w Javie. Część I: String, StringBuffer, StringBuilder .” Dużo przydatnych informacji w komentarzach. Na przykład określono, że podczas łączenia widoku new StringBuilder().append()...toString()obowiązuje wewnętrzna optymalizacja, regulowana przez opcję -XX:+OptimizeStringConcat, która jest domyślnie włączona. wewnętrzny - tłumaczony jako „wewnętrzny”. JVM radzi sobie z takimi rzeczami w specjalny sposób, przetwarzając je jako Native, tylko bez dodatkowych kosztów JNI. Przeczytaj więcej: „ Metody wewnętrzne w HotSpot VM ”.

StringBuilder i StringBuffer

Jak widzieliśmy powyżej, StringBuilder jest bardzo przydatnym narzędziem. Ciągi znaków są niezmienne, tj. niezmienny. I chcę to złożyć. Dlatego do pomocy mamy 2 klasy: StringBuilder i StringBuffer. Główna różnica między nimi polega na tym, że StringBuffer został wprowadzony w JDK 1.0, podczas gdy StringBuilder pojawił się w Javie 1.5 jako niezsynchronizowana wersja StringBuffer, aby wyeliminować zwiększony narzut związany z niepotrzebną synchronizacją metod. Obie te klasy są implementacjami klasy abstrakcyjnej AbstractStringBuilder - Zmienna sekwencja znaków. Wewnątrz przechowywana jest tablica uroków, która jest rozwijana według reguły: wartość.długość * 2 + 2. Domyślnie rozmiar (pojemność) StringBuilder wynosi 16.

Porównywalny

Struny są porównywalne, tj. zaimplementuj metodę CompareTo. Odbywa się to poprzez porównanie znak po znaku. Co ciekawe, spośród dwóch ciągów wybierana jest minimalna długość i na niej wykonywana jest pętla. Dlatego CompareTo albo zwróci różnicę między wartościami int pierwszych niedopasowanych znaków aż do najmniejszej długości ciągu, albo zwróci różnicę między długościami ciągu, jeśli wszystkie znaki mieszczą się w minimalnej długości ciągu. Porównanie to nazywa się „leksykograficznym”.

Praca z ciągami Java

String ma wiele przydatnych metod:
Stringi w Javie (klasa java.lang.String) - 4
Istnieje wiele zadań związanych z pracą z ciągami znaków. Na przykład w Coding Bat . Jest też kurs na Courserze: „ Algorytmy na ciągach znaków ”.

Wniosek

Nawet krótki przegląd tej klasy zajmuje imponującą ilość miejsca. I to nie wszystko. Gorąco polecam obejrzenie relacji z JPoint 2015: Alexey Shipilev - Katechizm java.lang.String
#Wiaczesław
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION