您的位置:beat365亚洲官方网站 > 互联网资讯 > Java操作符总结beat365亚洲官方网站

Java操作符总结beat365亚洲官方网站

2019-10-23 12:57

原标题:深入 V8 引擎:“小整数”到底有多小?

        Java语言中的表达式是由运算符与操作数组合而成的,所谓的运算符就是用来做运算的符号。
        在Java中的运算符,基本上可分为算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、转型运算符等。

原文:V8 Internals: How Small is a “Small Integer?”

链接:https://medium.com/fhinkel/v8-internals-how-small-is-a-small-integer-e0badc18b6da

作者:Franziska Hinkelmann

译者:justjavac(迷渡)

一、算术运算符        所谓算术运算符,也就是我们数学中学到的加、减、乘、除等运算。这些操作可以对几个不同类型的数字进行混合运算,为了保证操作的精度,系统在运算的过程中会做相应的转换。
      1、数字精度        所谓数字精度,也就是系统在做数字之间的算术运算时,为了尽最大可能地保持计算机的准确性,而自动进行相应的转换,将不同的数据类型转变为精度最高的数据类型。规则如下:
      1)当使用运算符把两个操作数结合到一起时,在进行运算前两个操作数会转化成相同的类型。
      2)两个操作数中有一个是double类型的,则另一个将转换成double型。
      3)两个操作数中有一个是float类型的,则另一个将也转换成float型。
      4)两个操作数中有一个是long类型的,则另一个将也转换成long型。
      5)任何其它类型的操作,两个操作数都要转换成int类型。
      2、整数型运算(int型)
       对于int型的相关操作,加、减、乘的结果都是非常容易理解的,重点讲一下除(/)的运算。
       两个整数相除的结果是整数,这一点与数学中是不同的,在Java中,两个整数相除的结果类似于数学中的求模运算。整除的余数是用%表示,例如15 / 2 = 7,而不是7.5,15 % 2 = 1。我们用程序验证一下:

原文副标题:“When binary is useful outside of a coding interview”

/*两个整数相除及求余数*/

public class Divide
{
   public static void main(String[] args)
   {
      int a = 15;
      int b = 2;
      double c = 2;
      System.out.println(a + "/" + b + "=" + (a / b));
      System.out.println(a + "%"+ b + "=" + (a % b));
      System.out.println(a + "/" + c + "=" + (a / c));
      System.out.println(a + "%" + c + "=" + (a % c));
    }
}

V8 是谷歌的开源 Java 引擎。Chrome,Node.js 和许多其他应用程序都使用了 V8 引擎。如果你曾经听过关于 V8 的演讲,或者读过关于 V8 的文章:Understanding V8’s Bytecode(中文:),你一定听说过 Smis,小整数。本文通过深入 V8 的源代码,具体研究一下 Smis到底有多大。

输出结果:
                 15 / 2 = 7
                 15 % 2 = 1
                 15 / 2.0  =  7.5
                 15 % 2.0 = 1.0

根据规范,Java 并不知道整数(除了最近引入的 BigInts)(中文:)。它只知道 IEEE 浮点数。但是许多操作都是基于整数,比如程序中的 for循环。所有 Java 引擎都有一个特殊的整数表示方式。V8 有所谓的 Smis小整数。

        3、自增和自减运算符
        在循环与控制中,我们经常会用到类似于计数器的运算,它们的特征是每次的操作都是加1或减1。在Java中提供了自增、自减运算符,X++使变量X的当前值每次增加1,而X--是自减运算符,每次操作使当前X的值减1。例如:

Smis在 64 位平台上的范围是 -2³¹ 到 2³¹-1(2³¹≈2*10⁹)。如果你查看 V8 源码,这可能并不是非常明显。 kSmiMinValue并 kSmiMaxValue在 include/v8.h 中定义如下:

/*测试自增、自减操作*/

public class SelfAction
{
   public static void main(String[] args)
   {
     int x = 10;
     int a = x+ x++;
     System.out.println("a =" + a);
     System.out.println("x =" + x);
     int b = x + ++x;
     System.out.println("b =" + b);
     System.out.println("x =" + x);
     int c = x + x--;
     System.out.println("c =" + c);
     System.out.println("x =" + x);
     int d = x + --x;
     System.out.println("d =" + d);
     System.out.println("x =" + x);
   }
}  
  1. static const int kSmiMinValue = (static_cast<unsigned int>(-1)) << (kSmiValueSize — 1);
  2. static const int kSmiMaxValue = -(kSmiMinValue + 1);

输出结果:
                 a = 20
                 x = 11
                 b = 23
                 x = 12
                 c = 24
                 x = 11
                 d = 21
                 x = 10

它是如何等于 -2³¹ 和 2³¹-1 的呢?让我们将上面的 C++ 代码分解一下。

二、关系运算符         Java具有完美的关系运算符。这些关系运算符基本上同数学中的关系运算符是一致的。“>”大于、“<”小于、“>=”大于等于、“<=”小于等于、“==”等于、“!=”不等于。例如:

左移

/*关系运算符测试*/

public class RelationTest
{
   public static void main(String[] args)
   {
     boolean x, y, z;
     int a = 15;
     int b = 2;
     double c =15;
     x = a > b;  //true;
     y = a < b;  //false;
     z = a != b;  //true;
     System.out.println("x =" + x);
     System.out.println("y =" + y);
     System.out.println("z =" + z);
   }
}

<<是按位左移运算。左移意味着我们将数字的二进制表示移到左边,并用零填充右边。例如,5 << 3 = 40。

输出结果:
                 x = true
                 y = false
                 z = true

beat365亚洲官方网站 1

三、逻辑运算符
        在Java语言中有三种逻辑运算符,它们是NOT(非,以符号“!”表示)、AND(与,以符号“&&”表示、)OR(或,以符号“||”表示)。
        1、NOT运算符          NOT运算符是用来表示相反的意思。
                        NOT逻辑关系值表                           

您可能已经注意到了,正数的左移与乘以 2 相同。

A

!A

true

false

false

true

静态转换为无符号整数

         2、AND运算符          AND运算符表示“与”的意思,也就是和的意思。
                       AND逻辑关系值表

  1. static_cast<unsigned int>(-1)

A

B

A&&B

false

false

false

true

false

false

当我们将负值转换为无符号整数(即正数)时会发生什么?所有的二进制位保持不变,但值的表示却不同了。转换为无符号整数后,我们就可以把这个数作为正数来进行左移运算了。

false

true

false

true

true

true

那么 -1的二进制表示是什么呢?在二进制补码中, -1表示为 (111...111)_2因为 2⁶³-2⁶²-2⁶¹- … -2²-2¹–1 = 1。

         3、OR运算符
         OR运算符是用来表示“或”就像我们日常生活中理解的一样,两者只要有一个为“真”,结果就为“真”。
                     OR逻辑关系值表

beat365亚洲官方网站 2

A

B

A||B

false

false

false

true

false

true

false

true

true

合在一起

true

true

true

如果您查看 V8 源代码中的 definitions,您会发现在 64 位计算机上 kSmiValueSize被定义为 32,于是:

/*逻辑运算符测试*/

public class LogicSign
{
   public static void main(String[] args)
   {
     boolean x, y, z, a, b;
     a = 'a' > 'b';
     b = 'R' != 'r';
     x = !a;
     y = a && b;
     z = a || b;
     System.out.println("x =" + x);
     System.out.println("y =" + y);
     System.out.println("z =" + z);
   }
}
  1. kSmiMinValue =(static_cast<unsigned int>(-1)) << (kSmiValueSize — 1)
  2. = (111...111)_2 << (32-1)
  3. = (111...111)_2 << 31
  4. = (11...1100...00)_2 // 31个零
  5. = -2^31

输出结果:
                 x = true
                 y = false
                 z = true

现在我们可以使用这个结果来对 kMaxValue进行初始化。

       4、“短路”现象
       在运用逻辑运算符进行相关的操作时,我们会遇到一种很有趣的现象;短路现象。
       对于true && false根据我们的讲述,处理的结果已经是false了,也就是说无论后面是结果是“真”还是“假”,整个语句的结果肯定是false了,所以系统就认为已经没有必要再进行比较下去了。也就不会再执行了,这种理象就是我们所说的短路现象。

  1. int kSmiMaxValue = -(kSmiMinValue + 1);

四、位运算符
          所有的数据、信息在计算机中都是以二进制形式存在的。我们可以对整数的二进制位进行相关的操作。这就是按位运算符,它主要包括:位的“与”、位的“或”、位的“非”、位的“异或”。
        1)位的“与”,用符号“&”表示,它属于二元运算符。 与位运算值表:

显而易见, kSmiMaxValue=-(-2^31+1)=2^31-1。在 64 位平台上 V8 对 Smis定义的范围是 [-2³¹,2³¹-1]。

A

B

A&B

1

1

1

1

0

0

0

1

0

32 位平台

0

0

0

在 32 位平台上 kSmiValueSize=31。所以我们把它换为 30,计算得到 kMinValue=-2^30。注意,2³⁰≈10⁹。

         2)位的“或”用符号“|”表示,它属于二元运算符。。   或位运算值表:

为什么 Smis在 32 位平台上的范围要小一点呢?在引擎内部,V8 使用最低有效位将所有 Java 值标记为堆栈对象或者 Smis。如果最低有效位是 1,则是指针。如果是 0,则是 Smi。这意味着 32 位整数只能使用 31 位存储 Smi值,因为一位(最低有效位)用作标记。

A

B

A|B

1

1

1

0

1

1

1

0

1

V8 使用最低有效位将所有值标记为 Smis 或堆指针。

0

0

0

其实 Smis并没有你想象的那么小,但它们很容易以按位编码的方式来存储到 32 位或 64 位整数中。

          3)位的“非”,用符号“~”表示,它是一元运算符,只对单个自变量起作用。它的作用是使二进制按位“取反”。 非位运算值表:

附加题:给定一个非空的整数数组,其中某个元素只出现了一次,其余每个元素都出现了两次。找到那个唯一元素。您可以使用二进制表示方式,时间复杂度为 O(n),空间复杂度为 O(1),你能找到解决方案吗? class="backword">返回搜狐,查看更多

A

~A

1

0

0

1

责任编辑:

           4)位的“异或”,用符号“^”表示,它属于二元运算符。异或位运算值表:

A

B

A^B

1

1

0

0

1

1

1

0

1

0

0

0

/*测试位的四种运算*/

public class BitOperation
{
 public static void main(String[] args)
 {
  int a = 15;
  int b = 2;
  int x = a & b;
  int y = a | b;
  int z = a ^ b;
  System.out.println(a + "&" + b + "=" + x);
  System.out.println(a + "|" + b + "=" + y);
  System.out.println(a + "^" + b + "=" + z);
 }
}

输出结果:
                  15 & 2 = 2
                  15 | 2 = 15
                  15 ^ 2 = 13

五、移位运算符

        移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。
  在移位运算时,byte、short和char类型移位后的结果会变成int类型,对于byte、short、char和int进行移位时,规定实际移动的次数是移动次数和32的余数,也就是移位33次和移位1次得到的结果相同。移动long型的数值时,规定实际移动的次数是移动次数和64的余数,也就是移动66次和移动2次得到的结果相同。
三种移位运算符的移动规则和使用如下所示:
<<运算规则:
按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
  语法格式:   需要移位的数字 << 移位的次数
  例如: 3 << 2,则是将数字3左移2位
  计算过程:   3 << 2
  首先把3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011,然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,最后在低位(右侧)的两个空位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12.数学意义:
  在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。
>>运算规则:按二进制形式把所有的数字向右移动对应巍峨位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1.
  语法格式:   需要移位的数字 >> 移位的次数
  例如11 >> 2,则是将数字11右移2位
  计算过程:11的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 0010.转换为十进制是3.数学意义:右移一位相当于除2,右移n位相当于除以2的n次方。
>>>运算规则:按二进制形式把所有的数字向右移动对应巍峨位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。
  其他结构和>>相似。
  小结
  二进制运算符,包括位运算符和移位运算符,使程序员可以在二进制基础上操作数字,可以更有效的进行运算,并且可以以二进制的形式存储和转换数据,是实现网络协议解析以及加密等算法的基础。
  实例操作:   

public class URShift {
  public static void main(String[] args) {
  int i = -1;
  i >>>= 10;
  //System.out.println(i);
  mTest();
  }
  public static void mTest(){
  //左移
  int i = 12; //二进制为:0000000000000000000000000001100
  i <<= 2; //i左移2位,把高位的两位数字(左侧开始)抛弃,低位的空位补0,二进制码就为0000000000000000000000000110000
  System.out.println(i); //二进制110000值为48;
  System.out.println("<br>");
  //右移
  i >>=2; //i右移2为,把低位的两个数字(右侧开始)抛弃,高位整数补0,负数补1,二进制码就为0000000000000000000000000001100
  System.out.println(i); //二进制码为1100值为12
  System.out.println("<br>");
  //右移example
  int j = 11;//二进制码为00000000000000000000000000001011
  j >>= 2; //右移两位,抛弃最后两位,整数补0,二进制码为:00000000000000000000000000000010
  System.out.println(j); //二进制码为10值为2
  System.out.println("<br>");
  byte k = -2; //转为int,二进制码为:0000000000000000000000000000010
  k >>= 2; //右移2位,抛弃最后2位,负数补1,二进制吗为:11000000000000000000000000000
  System.out.println(j); //二进制吗为11值为2
  }
  }

  在Thinking in Java第三章中的一段话:
  移位运算符面向的运算对象也是
  二进制的“位”。 可单独用它们处理整数类型(主类型的一种)。左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。 “有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
  若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int。只有右侧的5个低位才会用到。这样可防止我们在一个int数里移动不切实际的位数。若对一个long值进行处理,最后得到的结果也 是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。但在进行“无符号”右移位时,也可能遇到一个问题。若对byte或 short值进行右移位运算,得到的可能不是正确的结果(Java 1.0和Java 1.1特别突出)。它们会自动转换成int类型,并进行右移位。但“零扩展”不会发生,所以在那些情况下会得到-1的结果。   


Java 定义的位运算(bitwise operators )直接对整数类型的位进行操作,这些整数类型包括long,int,short,char,and byte 。表5.1 列出了位运算:
表5.1 位运算符及其结果

运算符 结果
~ 按位非(NOT)(一元运算)
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
>> 右移
>>> 右移,左边空出的位以0填充
运算符 结果
<< 左移
&= 按位与赋值
|= 按位或赋值
^= 按位异或赋值
>>= 右移赋值
>>>= 右移赋值,左边空出的位以0填充
<<= 左移赋值

续表

既然位运算符在整数范围内对位操作,因此理解这样的操作会对一个值产生什么效果是重要的。具体地说,知道Java 是如何存储整数值并且如何表示负数的是有用的。因此,在继续讨论之前,让我们简短概述一下这两个话题。

所有的整数类型以二进制数字位的变化及其宽度来表示。例如,byte 型值42的二进制代码是00101010 ,其中每个位置在此代表2的次方,在最右边的位以20开始。向左下一个位置将是21,或2,依次向左是22,或4,然后是8,16,32等等,依此类推。因此42在其位置1,3,5的值为1(从右边以0开始数);这样42是21+23+25的和,也即是2+8+32 。

所有的整数类型(除了char 类型之外)都是有符号的整数。这意味着他们既能表示正数,又能表示负数。Java 使用大家知道的2的补码(two’s complement )这种编码来表示负数,也就是通过将与其对应的正数的二进制代码取反(即将1变成0,将0变成1),然后对其结果加1。例如,-42就是通过将42的二进制代码的各个位取反,即对00101010 取反得到11010101 ,然后再加1,得到11010110 ,即-42 。要对一个负数解码,首先对其所有的位取反,然后加1。例如-42,或11010110 取反后为00101001 ,或41,然后加1,这样就得到了42。

如果考虑到零的交叉(zero crossing )问题,你就容易理解Java (以及其他绝大多数语言)这样用2的补码的原因。假定byte 类型的值零用00000000 代表。它的补码是仅仅将它的每一位取反,即生成11111111 ,它代表负零。但问题是负零在整数数学中是无效的。为了解决负零的问题,在使用2的补码代表负数的值时,对其值加1。即负零11111111 加1后为100000000 。但这样使1位太靠左而不适合返回到byte 类型的值,因此人们规定,-0和0的表示方法一样,-1的解码为11111111 。尽管我们在这个例子使用了byte 类型的值,但同样的基本的原则也适用于所有Java 的整数类型。

因为Java 使用2的补码来存储负数,并且因为Java 中的所有整数都是有符号的,这样应用位运算符可以容易地达到意想不到的结果。例如,不管你如何打算,Java 用高位来代表负数。为避免这个讨厌的意外,请记住不管高位的顺序如何,它决定一个整数的符号。

5.1 位逻辑运算符
位逻辑运算符有“与”(AND)、“或”(OR)、“异或(XOR )”、“非(NOT)”,分别用“&”、“|”、“^”、“~”表示,4-3 表显示了每个位逻辑运算的结果。在继续讨论之前,请记住位运算符应用于每个运算数内的每个单独的位。
表5.2  位逻辑运算符的结果
A 0 1 0 1 B 0 0 1 1 A | B 0 1 1 1 A & B 0 0 0 1 A ^ B 0 1 1 0 ~A 1 0 1 0

按位非(NOT)

按位非也叫做补,一元运算符NOT“~”是对其运算数的每一位取反。例如,数字42,它的二进制代码为:

00101010

经过按位非运算成为

11010101

按位与(AND)

按位与运算符“&”,如果两个运算数都是1,则结果为1。其他情况下,结果均为零。看下面的例子:

00101010 42 &00001111 15

00001010 10

按位或(OR)

本文由beat365亚洲官方网站发布于互联网资讯,转载请注明出处:Java操作符总结beat365亚洲官方网站

关键词: