JavaRush/Java блог/Random/Метод charAt() в Java
Автор
Александр Выпирайленко
Java-разработчик в Toshiba Global Commerce Solutions

Метод 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));
}
Приступим же к разбору, что тут собственно происходит. Первым делом в начале метода идёт ещё одна проверка допустимости индекса. Чтобы понять происходящее далее, нужно уяснить: когда не латинский символ попадает в массив 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-программировании.
Комментарии (12)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Олег
Уровень 13
19 декабря 2021, 20:24
Всё понятно. Битовые операции помогают ускорить код. Я, когда переписал свой драйвер адресной RGB-ленты для STM32 на битовые операции и указатели, производительность выросла в несколько раз
YesOn
Уровень 13
18 декабря 2021, 09:56
В эту статью следовало бы добавить больше примеров. Например, как с помощью этого метода найти слова, содержащие определённую букву/вы. Если это конечно применимо в использовании этого метода. (....спустя некоторое время...) Вот, на вскидку рабочий код (может кому пригодится):
ArrayList<String> list = new ArrayList<>();
list.add("Сила");
list.add("Воля");
list.add("Упорство");
   for (int i = 0; i < list.size(); i++) {
        for (int j = 0; j <list.get(i).length(); j++) {
            if (list.get(i).charAt(j) == 'л') {
                System.out.println(list.get(i));
            }
        }
   }
Anonymous #3399610
Уровень 51
30 января, 06:38
Что-то мне не нравится, что во втором цикле list.get(i) выполняется многократно. Может быть, перед вторым циклом получить один раз и в переменную положить? А также, когда нашли в первый раз букву "л", может быть, остальные буквы уже не надо проверять?
Elvin Yagudin QA Automation Engineer
12 ноября 2021, 18:21
Почему я получаю не символ, а видимо номер ячейки. Например, у меня строка "01", я беру строка.charAt(0); Получаю '0' 48, и самое интересное, что при выполнении с этим символом алгоритмической операции, я получаю именно 48, а не 0. Хелп
S1egmayer
Уровень 10
21 ноября 2021, 12:30
Character.getNumericValue(name.charAt(index));
Elvin Yagudin QA Automation Engineer
22 ноября 2021, 21:20
Нашел уже, но все равно благодарю
Oleksii
Уровень 36
12 февраля 2021, 19:23
Мне для 10 уровня в самый раз. Значительная часть была полезна, остальное зайдет позднее, если нужно будет вернуться к теме.
🦔 Виктор веду учебный тг-канал в t.me/Javangelion Expert
10 ноября 2020, 16:21
Даже после 10 уровня, слишком душно. В общих чертах, конечно, понятно как метод работает, но когда начинаются подкапотные сдвиги байтов, то всё, мозг отключается и до конца статьи на автопилоте читает. Может, действительно, пример не самый удачный, может я ещё не созрел... В любом случае, спасибо за труды! Всё получится!
NEZTOVSH0W
Уровень 6
6 ноября 2021, 12:21
Поддерживаю. Плаваю еще. Многое узнал, но остановился..
🦔 Виктор веду учебный тг-канал в t.me/Javangelion Expert
20 мая 2022, 15:20
Не останавливайтесь!
Евгений
Уровень 27
8 октября 2020, 19:07
Я нихрена не понял после слов "Что под капотом".. Неужели нельзя было придумать пример попроще и поинтереснее? З.Ы.ПРИМЕР: "АБВГДЕЖЗ" charAt(5) = "E". ВСЕ
finnadzorservice
Уровень 36
8 ноября 2021, 22:05
Ну или поискать не там где потерял, а под фонарём...