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
Всё понятно. Битовые операции помогают ускорить код. Я, когда переписал свой драйвер адресной RGB-ленты для STM32 на битовые операции и указатели, производительность выросла в несколько раз
YesOn Уровень 13
18 декабря 2021
В эту статью следовало бы добавить больше примеров. Например, как с помощью этого метода найти слова, содержащие определённую букву/вы. Если это конечно применимо в использовании этого метода. (....спустя некоторое время...) Вот, на вскидку рабочий код (может кому пригодится):

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));
            }
        }
   }
Elvin Yagudin Уровень 34
12 ноября 2021
Почему я получаю не символ, а видимо номер ячейки. Например, у меня строка "01", я беру строка.charAt(0); Получаю '0' 48, и самое интересное, что при выполнении с этим символом алгоритмической операции, я получаю именно 48, а не 0. Хелп
Oleksii Уровень 36
12 февраля 2021
Мне для 10 уровня в самый раз. Значительная часть была полезна, остальное зайдет позднее, если нужно будет вернуться к теме.
🦔 Виктор Уровень 20 Expert
10 ноября 2020
Даже после 10 уровня, слишком душно. В общих чертах, конечно, понятно как метод работает, но когда начинаются подкапотные сдвиги байтов, то всё, мозг отключается и до конца статьи на автопилоте читает. Может, действительно, пример не самый удачный, может я ещё не созрел... В любом случае, спасибо за труды! Всё получится!
Евгений Уровень 27
8 октября 2020
Я нихрена не понял после слов "Что под капотом".. Неужели нельзя было придумать пример попроще и поинтереснее? З.Ы.ПРИМЕР: "АБВГДЕЖЗ" charAt(5) = "E". ВСЕ