Istnieje wiele podstawowych technik, których używamy regularnie, nawet o tym nie myśląc. A co, jeśli się nad tym zastanowić i przyjrzeć się, jak wdrażane są pewne pozornie proste metody? Myślę, że to pomoże nam zbliżyć się o krok do Javy) Wyobraźmy sobie sytuację, w której musimy wyodrębnić pewien znak z jakiegoś ciągu znaków. Jak możemy to zrobić w Javie? Na przykład dzwoniąc do
Java String charAt
. O tej metodzie charAt()
porozmawiamy w dzisiejszym artykule.
Składnia
char charAt(int index)
zwraca wartość char o określonym indeksie. Indeks waha się od 0 do length()-1
. Oznacza to, że pierwsza char
wartość ciągu znajduje się w index 0
, następna w index 1
itd., tak jak ma to miejsce w przypadku indeksowania tablicy.
Przykład
public static void main(String[] args) {
System.out.print("JavaRush".charAt(0));
System.out.print("JavaRush".charAt(1));
System.out.print("JavaRush".charAt(2));
System.out.print("JavaRush".charAt(3));
}
Pierwsza linia zajmuje pierwszy znak, druga linia zajmuje drugi i tak dalej. Ponieważ nie println
, ale jest tutaj użyte print
, bez nowej linii, otrzymamy następujące dane wyjściowe na konsolę:
Java
Jeżeli char
dany indeks jest reprezentowany jako Unicode, wynikiem metody java charAt()
będzie znak reprezentujący ten Unicode:
System.out.println("J\u0061vaRush".charAt(1));
Wyjście konsoli:
a
Co kryje się „pod maską”
Jak to działa, pytasz? Faktem jest, że każdy obiektString
zawiera tablicę byte
zawierającą bajty elementów danej linii:
private final byte[] value;
A oto sama metoda chatAt
:
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
isLatin1
- flaga wskazująca, czy nasz ciąg zawiera tylko znaki łacińskie, czy też nie. To określa, która metoda zostanie wywołana jako następna.
isLatin1 = prawda
Jeśli ciąg zawiera tylko znaki łacińskie, wywoływana jest metodacharAt
klasy statycznej StringLatin1
:
public static char charAt(byte[] value, int index) {
if (index < 0 || index >= value.length) {
throw new StringIndexOutOfBoundsException(index);
}
return (char)(value[index] & 0xff);
}
Pierwszym krokiem jest sprawdzenie, czy przychodzący indeks jest większy lub równy 0 i czy nie wykracza poza wewnętrzną tablicę bajtów, a jeśli tak nie jest, zgłaszany jest wyjątek new StringIndexOutOfBoundsException(index)
. Jeśli kontrole zostaną zaliczone, potrzebny nam element zostanie zabrany. Na koniec widzimy:
&
rozciąga się dla operacji binarnych nabyte
bitowe0xff
nic nie robi, a jedynie&
wymaga argumentu(char)
konwertuje dane z tabeli ASCII dochar
isLatin1 = fałsz
Gdybyśmy mieli więcej niż tylko znaki łacińskie, klasa zostanie użytaStringUTF16
i wywołana zostanie jej metoda statyczna:
public static char charAt(byte[] value, int index) {
checkIndex(index, value);
return getChar(value, index);
}
Co z kolei wywołuje:
public static void checkIndex(int off, byte[] val) {
String.checkIndex(off, length(val));
}
I deleguje do metody statycznej String
:
static void checkIndex(int index, int length) {
if (index < 0 || index >= length) {
throw new StringIndexOutOfBoundsException("index " + index +
", length " + length);
}
}
Tutaj faktycznie sprawdza się, czy indeks jest ważny: ponownie, czy jest dodatni, czy zerowy i czy nie przekroczył granic tablicy. Ale w klasie StringUTF16
metody charAt
wywołanie drugiej metody będzie bardziej interesujące:
static char getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
Zacznijmy analizować, co się tu właściwie dzieje. Pierwszym krokiem na początku metody jest ponowne sprawdzenie ważności indeksu. Aby zrozumieć, co stanie się dalej, musisz zrozumieć: kiedy znak inny niż łaciński wchodzi do tablicy value
, jest on reprezentowany przez dwa bajty (dwie komórki tablicy). Jeśli mamy ciąg dwóch znaków cyrylicy - „av”, to:
- dla „a” jest to para bajtów - 48 i 4;
- dla „w” - 50 i 4.
value
- {48, 4, 50, 4} W rzeczywistości ta metoda działa z dwiema komórkami tablicy value
. Dlatego kolejnym krokiem jest przesunięcie index <<= 1;
, aby dostać się bezpośrednio do indeksu pierwszego bajtu żądanego znaku w tablicy value
. Powiedzmy teraz, że mamy ciąg znaków "абвг"
. Wtedy tablica wartości będzie wyglądać następująco: {48, 4, 49, 4, 50, 4, 51, 4}. Prosimy o trzeci element ciągu i wtedy reprezentacja binarna to 00000000 00000011. Po przesunięciu o 1 otrzymamy 00000000 00000110, czyli index = 6
. Aby odświeżyć swoją wiedzę na temat operacji bitowych, możesz przeczytać ten artykuł . Widzimy też pewne zmienne: HI_BYTE_SHIFT
w tym przypadku jest to 0, LO_BYTE_SHIFT
w tym przypadku jest to 8. W ostatniej linijce tej metody:
- Element jest pobierany z tablicy wartości i przesuwany bitowo o
HI_BYTE_SHIFT
, czyli o 0, podczas zwiększaniaindex +1
.W przykładzie z ciągiem
"абвг"
szósty bajt – 51 – pozostanie taki, ale jednocześnie indeks wzrośnie do 7. - Następnie pobierany jest kolejny element tablicy i przesuwany bitowo w ten sam sposób, ale o LO_BYTE_SHIFT, czyli o 8 bitów.
A gdybyśmy mieli bajt 4, który ma reprezentację binarną - 00000000 00000100, to po przesunięciu o 8 bitów otrzymamy 00000100 00000000. Jeśli jest to liczba całkowita - 1024.
- Następnie dla tych dwóch wartości następuje operacja
| (OR)
.A gdybyśmy mieli bajty 51 i 1024, które w reprezentacji binarnej wyglądały jak 00000000 00110011 i 00000100 00000000, to po operacji
OR
otrzymamy 00000100 00110011, co oznacza liczbę 1075 w systemie dziesiętnym.Otóż na koniec liczba 1075 jest konwertowana na typ char, a przy konwersji int -> char wykorzystywana jest tablica ASCII, a w niej pod liczbą 1075 znajduje się znak 'g'.
charAt()
w programowaniu w Javie.
GO TO FULL VERSION