Пользователь Эллеонора Керри
Эллеонора Керри
41 уровень

Метод charAt() в Java

Статья из группы Random
Есть множество базовых методов, которые мы регулярно используем, даже не задумываясь. Ну а что, если задуматься и посмотреть, каким образом реализованы некоторые простые, на первый взгляд, методы? Думаю, это поможет нам стать на шаг ближе к Java) charAt() в Java - 1Представим ситуацию, в которой нам нужно вытащить определенный символ в какой-то строке. Как мы это можем сделать в Java? Например, с помощью вызова метода Java String charAt. О методе charAt() мы и поговорим в сегодняшней статье.

Синтаксис

char charAt(int index) возвращает значение char по указанному индексу. Индекс колеблется от 0 до length()-1. То есть, первое char значение последовательности находится в index 0, следующее — в index 1 и т.д., как и в случае с индексацией массива.

Пример


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));
}
В первой строке берется первый символ, во второй — второй, и так далее. Так как здесь используется не println, а print, без перехода на новую строку, мы получим вывод в консоль:

Java
Если char под заданным индексом представлен в виде юникода, результатом работы метода java charAt() будет символ, который представляет данный юникод:

System.out.println("J\u0061vaRush".charAt(1));
Вывод в консоль:

a

Что “под капотом”

Как же оно работает, спросите вы?charAt() в Java - 2Дело в том, что в каждом объекте String есть массив byte с байтами элементов данной строки:

private final byte[] value;
А вот и сам метод chatAt:

public char charAt(int index) {
   if (isLatin1()) {
       return StringLatin1.charAt(value, index);
   } else {
       return StringUTF16.charAt(value, index);
   }
}
isLatin1 — флаг, указывающий на то, есть ли в нашей строке только латинские символы или нет. От это зависит, какой метод будет вызываться далее.

isLatin1 = true

Если в строке есть только латинские символы, вызывается статический метод charAt класса StringLatin1:

public static char charAt(byte[] value, int index) {
   if (index < 0 || index >= value.length) {
       throw new StringIndexOutOfBoundsException(index);
   }
   return (char)(value[index] & 0xff);
}
Первым делом проверяется, что пришедший индекс больше либо равен 0, и что он не выходит за пределы внутреннего массива байт, и если это не так, то кидается исключение — new StringIndexOutOfBoundsException(index). Если же проверки пройдены, далее берется нужный нам элемент. В конце мы видим:
  • & расширяет для двоичной операции для byte побитово
  • 0xff ничего не делает, но & требует аргумент
  • (char) приводит данные по таблице ASCII к char

isLatin1 = false

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

public static char charAt(byte[] value, int index) {
   checkIndex(index, value);
   return getChar(value, index);
}
Который в свою очередь вызывает:

public static void checkIndex(int off, byte[] val) {
   String.checkIndex(off, length(val));
}
А он делегирует статическому методу String:

static void checkIndex(int index, int length) {
   if (index < 0 || index >= length) {
       throw new StringIndexOutOfBoundsException("index " + index +
                                                 ", length " + length);
   }
}
Здесь, собственно, происходит проверка на допустимость индекса: опять же, положительный ли он либо ноль, и не выходил ли он за пределы массива. Но в классе StringUTF16 в методе charAt более интересным будет вызов второго метода:

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));
}
charAt() в Java - 3Приступим же к разбору, что тут собственно происходит. Первым делом в начале метода идёт ещё одна проверка допустимости индекса. Чтобы понять происходящее далее, нужно уяснить: когда не латинский символ попадает в массив value, его представляют два байта (две ячейки массива). Если у нас есть строка из двух кириллических символов — “ав”, то:
  • для ‘а’ это пара байтов — 48 и 4;
  • для ‘в’ — 50 и 4.
То есть, если мы создадим строку “ав”, у нее будет массив value — {48, 4, 50, 4} Собственно, в этом методе и идёт работа с двумя ячейками массива value. Поэтому дальше идёт сдвиг index <<= 1;, чтобы попасть непосредственно на индекс первого байта искомого символа в массиве value. А теперь допустим, что у нас есть строка "абвг". Тогда массив value будет выглядет так: {48, 4, 49, 4, 50, 4, 51, 4}. Мы запрашиваем третий элемент строки, и тогда двоичное представление — 00000000 00000011. При сдвиге на 1, мы получим 00000000 00000110, то есть index = 6. Чтобы освежить знания по побитовым операциям, можешь почитать вот эту статью.charAt() в Java - 4Также мы видим некоторые переменные: HI_BYTE_SHIFT в данном случае равно 0. LO_BYTE_SHIFT в данном случае равно 8. В последней строке данного метода:
  1. Берется элемент из массива value и побитово сдвигается на HI_BYTE_SHIFT, то есть 0, при этом увеличивая index +1.

    В примере с строкой "абвг", шестой байт — 51 — так бы и остался, но при этом увеличивается индекс до 7.

  2. После этого берется следующий элемент массива и так же побитово сдвигается, но на LO_BYTE_SHIFT , то есть на 8 битов.

    И если у нас это был байт 4, который имеет двоичное представление — 00000000 00000100, то после сдвига на 8 битов у нас будет 00000100 00000000. Если целым числом — 1024.

  3. Далее для этих двух значений следует операция | (OR).

    И если у нас были байты 51 и 1024, которые в двоичном представлении выглядели как 00000000 00110011 и 00000100 00000000, то после операции OR мы получим 00000100 00110011, что значит число 1075 в десятичной системе.

    Ну и в конце концов, переводится число 1075 в тип char, а при переводе int -> char используется таблица ASCII, и в ней под номером 1075 стоит символ ‘г’.

Собственно, таким образом мы и получим ‘г’ как результат работы метода charAt() в Java-программировании.
Комментарии (3)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Oleksii 27 уровень, Харьков
12 февраля 2021
Мне для 10 уровня в самый раз. Значительная часть была полезна, остальное зайдет позднее, если нужно будет вернуться к теме.
🦔 Виктор 20 уровень, Москва Expert
10 ноября 2020
Даже после 10 уровня, слишком душно. В общих чертах, конечно, понятно как метод работает, но когда начинаются подкапотные сдвиги байтов, то всё, мозг отключается и до конца статьи на автопилоте читает. Может, действительно, пример не самый удачный, может я ещё не созрел... В любом случае, спасибо за труды! -- tlgrm: LetsCodeIt / SefoNotasi
Евгений 27 уровень, Минск
8 октября 2020
Я нихрена не понял после слов "Что под капотом".. Неужели нельзя было придумать пример попроще и поинтереснее? З.Ы.ПРИМЕР: "АБВГДЕЖЗ" charAt(5) = "E". ВСЕ