Java. Cмещение битов

193
30 июля 2018, 02:10

Код взят отсюда: ссылка

Есть такое решение вышеупомянутой задачи (UTF - 16BE):

try (InputStream reader = new FileInputStream("test.txt")) {
    long[] unicodeArray = new long[65536];
    byte[] buf = new byte[4096];
    int len;
    short maxChar;
    long maxCharCnt = 0;
    while ((len = reader.read(buf)) > 0) {
        for (int i = 0; i < len; i += 2) {
            short c = (buf[i] << 8) || (buf[i + 1] & 0xff);
            unicodeArray[c]++;
            if (unicodeArray[c] > maxCharCnt) {
                maxChar = c;
                maxCharCnt = unicodeArray[c];
        }
    }
    System.out.println((char)maxChar);
}

Так как изучаю Java недавно, сложно даётся нижняя строка и намерения автора, но хочу разобраться.

short c = (buf[i] << 8) || (buf[i + 1] & 0xff);

Я понял так:

Здесь buf[i] << 8 происходит сдвиг влево на байт, получается что младший байт состоит из нулей. Результат кастуется в int, получается подобное:

00000000 00000000 11101110 00000000

Здесь buf[i + 1] & 0xff "обрезаются" все байты кроме младшего(т.к 0xff == 11111111). Я не понял зачем это нужно, ведь изначально у нас и так был всего один байт. Можно было просто написать (int)buf[i+1](?). Результат снова int и выйдет:

00000000 00000000 00000000 10101010

Биты от балды написал.

В итоге имеются два таких int. Далее применяется побитовый OR(?):

 00000000 00000000 11101110 00000000
|
 00000000 00000000 00000000 10101010
 00000000 00000000 11101110 10101010

Результат: 11101110 10101010, то есть то что написано выше без двух старших байтов?.

Я всё правильно понял?

Помимо обозначенных вопросов:

-Почему по факту это не компилируется? Ошибка: can't apply || with (int)(int)

-Разве так быстрее (на больших объёмах данных) чем считать из файла строку, сразу разбить её на массив char и работать сразу с символами?

Заранее спасибо.

Answer 1

Я не понял зачем это нужно, ведь изначально у нас и так был всего один байт. Можно было просто написать (int)buf[i+1](?).

Приведение производится с сохранением знака, т.е., например, байт 10101010 будет приведен к инту 11111111 11111111 11111111 10101010.

В остальном все верно, кроме оператора || вместо | в приведенном коде - поэтому и не компилируется.

И да, побитовые операции сами по себе "быстрые", к тому же строки так устроены, что любое изменение происходит через создание новой строки(и копирование массива символов), при переводе строки в массив символов будет возвращена копия всего внутреннего массива, а не указатель на существующий массив.

Answer 2

Для кодирования UTF - 16BE используются 16 бит*, если не отводить один бит на знак, то 16 битами можно представить 65536 (2^16) значения, такое "беззнаковое" использование характерно для примитивного типа char в Java.
Некоторые пояснения
Используем массив

long[] unicodeArray = new long[65536];

для хранения количества вхождений каждого из встретившихся символов. Цикл

while((len = reader.read(buf)) > 0) {}

последовательно считывает байты в массив byte[] buf, когда читать нечего, read возвращает -1 и цикл прерывается. Для обработки считанных в массив buf данных, используется цикл

for (int i = 0; i < len; i += 2) {}

Выражение

char c = (char)((buf[i] << 8) | (buf[i + 1] & 0xff));

формирует 16 битное представления символа(16 бит = 2 байта) из считанных в данных. Буфер buf это массива байт, т.е нам нужно брать по два байта из массива и "склеивать" их, для. "Логика склеивания" следующая.

Предположим что: buf[i] содержит последовательность бит (это один байт из файла)

10000001 (-127)

При использовании побитовых операторов происходит неявное преобразование типов к int. При преобразовании в int получим

11111111 11111111 11111111 10000001

Выражение

(buf[i] << 8)

выполняет сдвиг влево на 8 бит, получаем

11111111 11111111 10000001 00000000

Мы получили первый байт, смещенный на 8 бит влево, т.е. освободили место для второго байта

Далее предположим buf[i+1] содержит значение последовательность бит

10000000 (-128)

при преобразовании в int получим (расширение учитывает знак)

11111111 11111111 11111111 10000000

Выражение

(buf[i + 1] & 0xff)

побитно перемножает

11111111 11111111 11111111 10000000 - получено из buf[i+1]

&

00000000 00000000 00000000 11111111 - это 0xff в двоичной форме

=

00000000 00000000 00000000 10000000

В результате второй байт расположен в последних 8 битах

Далее

(buf[i] << 8) | (buf[i + 1] & 0xff)

складывает преобразованный и смещенный первый байт (из buf[i]) и подготовленный второй (из buf[i+1])

11111111 11111111 10000001 00000000

|

00000000 00000000 00000000 10000000

=

11111111 11111111 10000001 10000000

Полученное значение имеет размерность int, оно явно преобразуется в char(отсекаются первые 16 бит, если считать слева направо) и сохраняется в переменной char с . В результате получаем то, что хотели ("склееные" два байта)

10000001 10000000

Полученное значение используется как "адрес символа" в массиве, и производится инкремент "счетчика" для данного символа

unicodeArray[c]++;

Вот такой код работает.

import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.IOException ;
public class MaxFinder {
    public static void main(String[] args) throws IOException {
        try (InputStream reader = new FileInputStream("test.txt")) {
            //массив примитивных типов long
            //для хранения количества вхождений каждого из встретившихся символов
            long[] unicodeArray = new long[65536];
            //массив для хранения считанных данных
            byte[] buf = new byte[4096];
            //сколько байт прочитали в буфер
            int len;
            //самый часто встречающийся символ
            char maxChar = 0;
            //количество вхождений самого часто встречающегося символа
            long maxCharCnt = 0;
            //в цикле читаем данные в буфер, пока не закончатся данные (read вернет -1)
            while ((len = reader.read(buf)) > 0) {
                //в цикле обрабабатываем по 2 байта из буфера,
                //из которых формируют один символ в UTF - 16 
                for (int i = 0; i < len; i += 2) {
                    //формирум 16 битное представления из считанных байт
                    //при использовании побитовых операторов
                    //(buf[i] << 8) - сдвиг первый байт на 8 бит влево
                    //"освобождает" место для второго байта
                    //(buf[i + 1] & 0xff) "готовит" второй байт 
                    //унарные операторы преобразуют тип к int
                    //преобразуем в тип char (отсекая первые 16 бит слева)
                    char c = (char)((buf[i] << 8) | (buf[i + 1] & 0xff));
                    //используем char (диапазон значений от 0 до 65536) как адрес в массиве,
                    //и увеличивая счетчик для соответствующего символа
                    unicodeArray[c]++;
                    //если больше текущего максимума, обновляем максимум
                    if (unicodeArray[c] > maxCharCnt) {
                        maxChar = c;
                        maxCharCnt = unicodeArray[c];
                    }
                }
            }
            System.out.println((char)maxChar);
        }
    }
}
READ ALSO
Android storage access framework

Android storage access framework

Всем привет, знаю, что уже очень много вопросов на эту тему, однако, как грамотно использовать Android storage access frameworkВот примерно такой:

229
Индексированный поиск в JAVA

Индексированный поиск в JAVA

Ребята нужна помощь и это касается быстрого поискаПишу загрузку данных из DBF в Postgresql

210
Ошибка в расширении класса ArrayAdapter

Ошибка в расширении класса ArrayAdapter

Пытаюсь расширить адаптер для спиннера, написал класс включающий в себя проверку: находится ли в списке данных для спиннера нужная строка:

164
Авторизация и аутентификация Servlet

Авторизация и аутентификация Servlet

День добрыйПодскажите пожалуйста в каком направлении идти

215