在Integer类中有这么一个方法,你可以给它传入一个数字,它将返回小于等于这个数字的一个2的幂次方数。这个方法就是highestOneBit(int i)。

比如下面的Demo,注意方法的输入与返回值:

  1. System.out.println(Integer.highestOneBit(15)); // 输出8
  2. System.out.println(Integer.highestOneBit(16)); // 输出16
  3. System.out.println(Integer.highestOneBit(17)); // 输出16

这个方法的实现代码量也是非常少的:

  1. public static int highestOneBit(int i) {
  2. // HD, Figure 3-1
  3. i |= (i >> 1);
  4. i |= (i >> 2);
  5. i |= (i >> 4);
  6. i |= (i >> 8);
  7. i |= (i >> 16);
  8. return i - (i >>> 1);
  9. }

接下来,我们就来详细分析一下这块代码的逻辑。

首先,对于这个方法的功能:给定一个数字,找到小于或等于这个数字的一个2的幂次方数。

如果我们要自己来实现的话,我们需要知道:怎么判断一个数字是2的幂次方数。

说真的,我一下想不到什么好方法来判断,唯一能想到的就是一个数字如果把它转换成二进制表示的话,它会有一个规律:如果一个数字是2的幂次方数,那么它对应的二进制表示仅有一个bit位上是1,其他bit位全为0。

比如:

十进制6,二进制表示为:0000 0110

十进制8,二进制表示为:0000 1000

十进制9,二进制表示为:0000 1001

所以,我们可以利用一个数字的二进制表示来判断这个数字是不是2的幂次方数。关键代码怎么实现呢?去遍历每个bit位?可以,但是不好,那怎么办?我们还是回头仔细看看Integer是如何实现的吧?

  1. public static int highestOneBit(int i) {
  2. // HD, Figure 3-1
  3. i |= (i >> 1);
  4. i |= (i >> 2);
  5. i |= (i >> 4);
  6. i |= (i >> 8);
  7. i |= (i >> 16);
  8. return i - (i >>> 1);
  9. }

我们发现这段代码中没有任何的遍历,只有位运算与一个减法,也就是说它的实现思路和我们自己的实现思路完全不一样,它的思路就是:给定一个数字,通过一系列的运算,得到一个小于或等于该数字的一个2的幂次方数。

也就是:如果给定一个数字18,通过运算后,要得到16。

18用二进制表示为: 0001 0010

想要得到的结果(16)是:0001 0000

那么这个运算的过程无非就是将18对应的二进制数中除最高位的1之外的其他bit位都清零,则拿到了我们想要的结果。

那怎么通过位运算来实现这个过程呢?

我们拿18对应的二进制数0001 0010来举个例子就行了:

先将0001 0010右移1位,

得到0000 1001,再与自身进行或运算:

得到0001 1011

再将0001 1011右移2位,

得到0000 0110,再与自身进行或运算:

得到0001 1111

再将0001 1111右移4位,

得到0000 0001,再与自身进行或运算:

得到0001 1111

再将0001 1111右移8位,

得到0000 0000,再与自身进行或运算:

得到0001 1111

再将0001 1111右移16位,

得到0000 0000,再与自身进行或运算:

得到0001 1111

再将0001 1111无符号右移1位,

得到0000 1111

关于无符号右移,可以看我之前写的文章。

最后用0001 1111 - 0000 1111 = 0001 0000

震惊!得到了我们想要的结果。

其实这个过程可以抽象成这样:

现在有一个二进制数据,0001****,我们不关心低位的取值情况,我们对其进行右移并且进行或运算。

先将0001****右移1位,

得到00001***,再与自身进行或运算:

得到00011***

再将00011***右移2位,

得到0000011*,再与自身进行或运算:

得到0001111*

再将0001111*右移4位,

得到00000001,再与自身进行或运算:

得到00011111

后面不用再推算了,到这里我们其实可以发现一个规律:

右移与或运算的目的就是想让某个数字的低位都变为1,再用该结果 减去 该结果右移一位后的结果,则相当于清零了原数字的低位。即得到了我们想要的结果。

到此,只能感叹JDK作者对于位运算的使用已经达到了出神入化的境界了。

如果想学习更多的精彩的Java、分布式、微服务等方面的知识,请关注微信公众号:1点25

Integer.highestOneBit(int i)方法的作用与底层实现的更多相关文章

  1. 详解Integer.toString(int i)方法和String.valueOf(int i)方法

    通过查看String类的源码: public static String valueOf(int i) { return Integer.toString(i); } 我们可以看到,String.va ...

  2. integer与int区别以及integer.values()方法详解

    声明:本文为博主转载文章,原文地址见文末. 知识点1:integer和int的区别 /* * int是java提供的8种原始数据类型之一.Java为每个原始类型提供了封装类,Integer是java为 ...

  3. Integer类toString(int i,int radix)方法

    Integer类toString(int i,int radix)方法: 首先抛出java的api中的介绍: public static String toString(int i, int radi ...

  4. Integer 与 int -- 自动装箱(autoboxing)与自动拆箱(unboxing)

    转载于 http://www.ticmy.com/?p=110 jdk1.5引入了自动装箱(autoboxing)与自动拆箱(unboxing),这方便了集合类以及一些方法的调用,同时也使初学者对其感 ...

  5. equal(),hashcode(),toString()方法的作用

    equal(),hashcode(),toString()方法的作用 这三个方法都是java.lang.Object的方法. equal();判断两对象是否相等hashcode();为对象在容器中添加 ...

  6. 区分Integer.getInteger和Integer.valueOf、Integer.parseInt() 的使用方法

    Integer类有两个看起来很类似的静态方法,一个是Integer.getInteger(String),另外一个是Integer.valueOf(String).如果只看方法名称的话,很容易将这两个 ...

  7. java克隆对象clone()的使用方法和作用

    转自:997.html">http://www.okrs.cn/blog/news/?997.html 内容摘要 若需改动一个对象,同一时候不想改变调用者的对象.就要制作该对象的一个本 ...

  8. 你知道Object中有哪些方法及其作用吗?

    一.引言二.Object方法详解1.1.registerNatives()1.2.getClass()1.2.1.反射三种方式:1.3.hashCode()1.4.equals()1.4.clone( ...

  9. Object中有哪些方法及其作用

    你知道Object中有哪些方法及其作用吗? 一.引言 二.Object方法详解 1.1.registerNatives() 1.2.getClass() 1.2.1.反射三种方式: 1.3.hashC ...

随机推荐

  1. Expectation Maximization Algorithm

    期望最大化算法EM. 简介 EM算法即期望最大化算法,由Dempster等人在1976年提出[1].这是一种迭代法,用于求解含有隐变量的最大似然估计.最大后验概率估计问题.至于什么是隐变量,在后面会详 ...

  2. LeetCode 873. Length of Longest Fibonacci Subsequence

    原题链接在这里:https://leetcode.com/problems/length-of-longest-fibonacci-subsequence/ 题目: A sequence X_1, X ...

  3. go选项模式

    package main import "fmt" type optionClient func(*options) func setAge(a int) optionClient ...

  4. 异常过滤器的好坏(CLR)

    为什么有些语言支持它们而另一些不支持呢?把它们加到我的新语言里是个好主意吗?我应该什么时候使用过滤器和catch/rethrow?就像很多事情一样,异常过滤器有好的一面也有坏的一面… 什么是异常过滤器 ...

  5. WinDbg常用命令系列---源代码操作相关命令

    lsf, lsf- (Load or Unload Source File) lsf和lsf-命令加载或卸载源文件. lsf Filename lsf- Filename 参数: Filename指定 ...

  6. 洛谷P2744 量取牛奶

    题目 DP或者迭代加深搜索,比较考验递归的搜索. 题目第一问可以用迭代加深搜索限制层数. 第二问需要满足字典序最小,所以我们可以在搜索的时候把比当前答案字典序大的情况剪枝掉. 然后考虑怎么搜索,对于每 ...

  7. Doors Breaking and Repairing

    题目链接:Doors Breaking and Repairing 题目大意:有n个门,先手攻击力为x(摧毁),后手恢复力为y(恢复),输入每个门的初始“生命值”,当把门的生命值攻为0时,就无法恢复了 ...

  8. DDL 语言

    数据库模式定义语言并非程序设计语言,DDL数据库模式定义语言是SQL语言(结构化查询语言)的组成部分. SQL语言包括四种主要程序设计语言类别的语句:数据定义语言(DDL),数据操作语言(DML),数 ...

  9. ORA-55624: 此时无法为闪回归档启用表

    我们在某应用中使用了FDA特性,但是某些表在解除归档后重新启用时报"ORA-55624: 此时无法为闪回归档启用表",经查询google和MOS相关信息,原因就是太频繁.解决方法: ...

  10. java里的 int vs Integer

    int vs Integer 基本类型int的默认值为0;对应的封装类型Integer的默认值为null Integer对象会占用更多的内存.Integer是一个对象,需要存储对象的元数据.但是int ...