Тщательно изучите операторы сдвига в Java.

Java

предисловие

заоператор смены, многие люди чувствуют себя как знакомыми, так и незнакомыми. Знакомый, потому что оператор сдвига является одним из самых основных операторов, и он включен почти в каждый язык программирования; незнакомый, потому что его трудно использовать, если только он не используется в редких случаях, таких как стремление к экстремальной производительности. Откройте исходный код JDK, вы обнаружите, что оператор сдвига чрезвычайно распространен, и очень полезно понять его использование для чтения исходного кода.

Операция сдвига — это операция, которая обрабатывает данные как двоичное число и сдвигает их влево или вправо на несколько битов. В языке программирования Java есть три оператора сдвига, которые<<(сдвиг влево),>>(подписанный правый сдвиг) и>>>(беззнаковый сдвиг вправо), эти три оператора могут работать только наlong,int,short,byte,charна четыре основных целочисленных типа.

Оператор сдвига влево

оператор сдвига влево<<После преобразования данных в двоичные числа,Сдвиньте несколько битов влево, отбросьте старшие биты и заполните младшие биты нулями. См. следующий пример:

public static void main(String[] args) {
    int i = -1;
    System.out.println("Before << , i's value is " + i);
    System.out.println("i's binary string is " + Integer.toBinaryString(i));
    i <<= 10;
    System.out.println("After << , i's value is " + i);
    System.out.println("i's binary string is " + Integer.toBinaryString(i));
}

Яваintзанимает 32 бита, поэтомуi = -1Преобразуйте в двоичное число, а затем сдвиньте влево на 10 бит.В результате старшие 10 бит слева отбрасываются, а младшие 10 бит справа дополняются.0, а затем преобразовать в десятичную, мы получаемi = -1024результат.

int -1 << 10 = -1024
int -1 << 10 = -1024

Таким образом, вывод приведенного выше примера:

Before << , i's value is -1
i's binary string is 11111111111111111111111111111111
After << , i's value is -1024
i's binary string is 11111111111111111111110000000000

Подписанный оператор правой смены >>

Как мы все знаем, когда целое число в Java представляет отрицательное число, старший бит является битом знака, а положительное число0, отрицательное число равно1.>>является оператором сдвига вправо со знаком после преобразования данных в двоичные числа,Сдвиньте несколько битов вправо, старший бит дополняет знаковый бит, а младший отбрасывает. При выполнении операции сдвига вправо для положительного числа она реализуется как дополнение высокого порядка.0; Отрицательные числа дополняются1. См. следующий пример:

public static void main(String[] args) {
    // 对正数进行右移操作
    int i1 = 4992;
    System.out.println("Before >> , i1's value is " + i1);
    System.out.println("i1's binary string is " + Integer.toBinaryString(i1));
    i1 >>= 10;
    System.out.println("After >> , i1's value is " + i1);
    System.out.println("i1's binary string is " + Integer.toBinaryString(i1));
    // 对负数进行右移操作
    int i2 = -4992;
    System.out.println("Before >> , i2's value is " + i2);
    System.out.println("i2's binary string is " + Integer.toBinaryString(i2));
    i2 >>= 10;
    System.out.println("After >> , i2's value is " + i2);
    System.out.println("i2's binary string is " + Integer.toBinaryString(i2));
}

В примереi1 = 4992Преобразование в двоичное число, сдвинутое вправо на 10 бит, дает левое старшее 10-битное дополнение.0, младшие 10 бит справа отбрасываются, а затем преобразуются в десятичные числа, чтобы получитьi1 = 4результат. Так же,i2 = -4992, сдвиг вправо на 10 бит, дополнение влево на 10 старших битов1, младшие 10 бит справа отбрасываются, и мы получаемi2 = -5результат.

int 4992 >> 10 = 4 和 int -4992 >> 10 = -5
интервал 4992 >> 10 = 4 и интервал -4992 >> 10 = -5

Таким образом, вывод приведенного выше примера:

Before >> , i1's value is 4992
i1's binary string is 1001110000000
After >> , i1's value is 4
i1's binary string is 100
Before >> , i2's value is -4992
i2's binary string is 11111111111111111110110010000000
After >> , i2's value is -5
i2's binary string is 11111111111111111111111111111011

Беззнаковый оператор сдвига вправо >>>

беззнаковый оператор сдвига вправо>>>и>>Точно так же данные преобразуются в двоичное число, а затем сдвигаются вправо на несколько битов, разница в том, что независимо от того, отрицательное число или нет, результатСтаршие биты заполняются нулями, младшие отбрасываются. См. следующий пример:

public static void main(String[] args) {
    int i3 = -4992;
    System.out.println("Before >>> , i3's value is " + i3);
    System.out.println("i3's binary string is " + Integer.toBinaryString(i3));
    i3 >>>= 10;
    System.out.println("After >>> , i3's value is " + i3);
    System.out.println("i3's binary string is " + Integer.toBinaryString(i3));
}

такое же правоi3 = -4992Выполните операцию, после преобразования его в двоичное число сдвиньте 10 бит вправо, и в результате получится дополнение старших 10 бит слева.0, младшие 10 бит справа отбрасываются, а затем преобразуются в десятичные числа, чтобы получитьi3 = 4194299результат.

int -4992 >>> 10 = 4194299
int -4992 >>> 10 = 4194299

Таким образом, вывод приведенного выше примера:

Before >>> , i3's value is -4992
i3's binary string is 11111111111111111110110010000000
After >>> , i3's value is 4194299
i3's binary string is 1111111111111111111011

Вы действительно понимаете?

Операции сдвига на коротких, байтовых, символьных

Рассмотрим следующий пример:

public static void main(String[] args) {
    byte b = -1;
    System.out.println("Before >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
    b >>>= 6;
    System.out.println("After >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
}

ЯваbyteЗанимают 8 бит, по описанному выше принципу справаb = -1После преобразования в двоичное число сдвиньте вправо на 6 бит и дополните старшие 6 бит слева0, нижние правые биты отбрасываются, и результат должен бытьb = 3.

int -1 >>> 6 = 3 ?
int -1 >>> 6 = 3 ?

Это действительно так? Давайте посмотрим на результат запуска примера:

Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is -1
b's binary string is 11111111111111111111111111111111

Текущий результат не тот, что мы ожидали!

Оказывается, Java обрабатываетbyte,short,charперед началом смены будетпревратить его вintвведите, а затем выполните операцию! В частности, при использовании этих трех<<=,>>=и>>>=, фактически получаем сдвинутую паруintРезультат после усечения младшего разряда! Проверьте изменения примера:

public static void main(String[] args) {
    byte b = -1;
    System.out.println("Before >> , b's value is " + b);
    System.out.println("b's binary string is " + Integer.toBinaryString(b));
    System.out.println("After >> , b's value is " + (b >>> 6));
    System.out.println("b's binary string is " + Integer.toBinaryString(b >>> 6));
}

В этом примере нет>>>=правильноbпереназначение, но непосредственноb >>> 6вывод (обратите внимание,b >>> 6Результатinttype), вывод которого выглядит следующим образом:

Before >> , b's value is -1
b's binary string is 11111111111111111111111111111111
After >> , b's value is 67108863
b's binary string is 11111111111111111111111111

Следовательно, реальный процесс работы в первом примере должен быть таким:

byte -1 >>> 6 = -1
byte -1 >>> 6 = -1

заshortиcharПринцип работы сдвига тот же, и читатель может провести экспериментальную проверку самостоятельно.

Что произойдет, если количество сдвинутых битов превысит количество битов, занимаемых значением?

Во всех приведенных до сих пор примерах число сдвинутых битов находится в пределах числа битов, занимаемых значением, например, дляintНи один из сдвигов типа не превышает 32. тогда, если правильноintЧто происходит, когда тип сдвинут более чем на 32 бита? И посмотрите следующий пример:

public static void main(String[] args) {
    int i4 = -1;
    System.out.println("Before >>> , i4's value is " + i4);
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4));
    System.out.println("After >>> 31 , i4's value is " + (i4 >>> 31));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 31));
    System.out.println("After >>> 32 , i4's value is " + (i4 >>> 32));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 32));
    System.out.println("After >>> 33 , i4's value is " + (i4 >>> 33));
    System.out.println("i4's binary string is " + Integer.toBinaryString(i4 >>> 33));
}

По описанному выше принципу дляi4 >>> 31Мы легко можем заключить, что1.

int -1 >>> 31 = 1
int -1 >>> 31 = 1

Так,i4 >>> 32результат будет0?

NO! Java правый операнд оператора сдвигаrhsСуществуют специальные методы леченияintТипа, взять только его младшие 5 бит, то есть взятьrhs % 32Результат; для длинного типа берутся только младшие 6 бит, то есть берутсяrhs % 64результат. Следовательно, дляi4 >>> 32,в действительностиi4 >>> (32 % 32), этоi4 >>> 0, результат все равно-1.

int -1 >>> 32 = -1
int -1 >>> 32 = -1

Точно так же дляi4 >>> 33Эквивалентноi4 >>> 1, результат2147483647.

int -1 >>> 33 = 2147483647
int -1 >>> 33 = 2147483647

Таким образом, вывод приведенного выше примера выглядит следующим образом:

Before >>> , i4's value is -1
i4's binary string is 11111111111111111111111111111111
After >>> 31 , i4's value is 1
i4's binary string is 1
After >>> 32 , i4's value is -1
i4's binary string is 11111111111111111111111111111111
After >>> 33 , i4's value is 2147483647
i4's binary string is 1111111111111111111111111111111

заlongТо же самое относится и к типам, и читатели могут сами провести экспериментальную проверку.

Суммировать

Хотя оператор сдвига является одним из самых основных операторов в Java, если вы не совсем понимаете детали, легко сделать ошибку, если вы не будете осторожны. Типы, которые на самом деле поддерживаются операторами сдвига, являются толькоintиlong, компилятор исправляетshort,byte,charтип преобразуется вintвведите снова. Наиболее распространенное использование оператора сдвига в исходном коде JDK — трактовать его как умножение.*или кроме/Использование оператора: сдвиг целого числа влево на единицу, что эквивалентно умножению на 2; Причина в том, что по сравнению с использованием*и/, используемый в коде Java<<и>>Преобразованный скрипт будет работать более эффективно.