Integer解析

作为一名Java开发,相信大家对于Integer都不会陌生,接下来就其分析下

开箱与装箱

  开箱装箱主要针对于Java中出现的几种包装类,比如int与之对应的Integer。通俗一点的理解就是,Integer可以与int自动的相互转换,这个转换过程对于开发人员来说是透明的,JVM在底层帮我们进行了转化。

装箱:
举个栗子:

Integer i = 10; //Integer i = new Integer(10)

上面的代码实质生成的Integer对象,怎么生成的呢?,在JVM底层,它帮我们调用了Integer.valueOf(int x),这就是所谓的装箱过程。

拆箱:
举个栗子:

int b = i; //int i = Integer.intValue(i)

上面的代码是将Integer i转换为int i,怎么生成的呢?,在JVM底层,它帮我们调用了Integer.intValue(int x),这就是所谓的拆箱过程过程。

在这里插入图片描述
在这里插入图片描述

自动装箱存在的缓存

  相信备战面试的小伙伴,都会知道Integer在一段区域中会进行缓存,这个区间默认是[-127,128]之间。注意奥:缓存只能在自动装箱或者是调用Integer.valueOf(int x)才会有用。

举个?:

Integer a = new Integer(11)
Integer b = new Integer(11)
a == b ?  // false

为什么为false?,因为ab是两个对象,两个对象用==,当然不相等啊,这比较的是内存地址,hashcode。用equals才是值(value)的比较。

Integer a = 11
Integer b = 11
a == b ?  // true

这里为什么又是true?,因为ab实质是同一个对象,只是引用不同,两者都指向的同一内存地址。前面说到[-127,128]区间,就是当调用Integer.valueOf(int x)(ps:装箱实质就是调用这个方法)时,在这个区间调用都是从缓存中直接拿值,超过这个区间才会创造新的对象。

下面看下源码:

  public static Integer valueOf(int i) {
  // low = -128 high = 127
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

考虑下如下场景:

  Integer c = Integer.valueOf(100);
  Integer b = new Integer(100);
  b == c ? // false

你怎么讲?第一次调用Integer.valueOf,对象从何而来?是不是觉得很奇怪,low<100<highcache里面的值应该从哪里来?
思路一:
当我们每次创建对象的时候,通过值判断如果在区间[low ,high ],我们就将其缓存到cache中,方便下次调用。
很明显,这样我们第一次调用值从那哪里来?这是一个问题,我想JDK的开发者不会这个蠢吧,hah。
思路二:
JVM启动时,在装载类的时候就将其给初始化,依次给[low ,high ]创建对应的对象。这样就解决了第一次调用的问题了。

看下源码:

  static final int low = -128;
  static final int high;
  static final Integer cache[];

 static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

确实是这样处理的,大家有没有注意到一点high写死,而是在静态代码块中才初始化的值,还可以赋值进行变动,这说明了一个问题我们的缓存块是可以调整的,所以说Integer仅仅在区间[-127,128]进行缓存是不准确的,-127是固定边界,high边界是可以调整的。

注意: -XX:AutoBoxCacheMax=<size>可以调整这个high值的大小。

举个?:


      Integer b = 200;
      Integer c = 200;
      System.out.println(c == b);
     
     Integer a = 400;
     Integer d = 400;
     System.out.println(a == d);

在这里插入图片描述

进制转换

Integer中支持多种的进制转换,比如说十进制转换成二进制八进制十六进制。在Integer的进制转换是设计得十分巧妙的,不是像我们所说的那种辗转相除法。下面我们分析一下源码:

toUnsignedString0函数

private static String toUnsignedString0(int val, int shift) {
	 // assert shift > 0 && shift <=5 : "Illegal shift value";
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    char[] buf = new char[chars];

    formatUnsignedInt(val, shift, buf, 0, chars);

    // Use special constructor which takes over "buf".
    return new String(buf, true);
}

几乎所有的进制转换都离不开这个函数,这个函数通过一个shift来区分二进制、八进制、十六机制,分别由1,3,4表示,你说为啥没有2,当然也是有的只不过内部不提供,需要你自己去调用相关API来自己实现,实现起来也比较方便,直接调用API.

观察重点:numberOfLeadingZeros与formatUnsignedInt的作用
numberOfLeadingZeros看函数意思,大概就是寻找0吧,
贴下源码:

public static int numberOfLeadingZeros(int i) {
    // HD, Figure 5-6
      if (i == 0)
          return 32;
      int n = 1;
      if (i >>> 16 == 0) { n += 16; i <<= 16; } 
      if (i >>> 24 == 0) { n +=  8; i <<=  8; }
      if (i >>> 28 == 0) { n +=  4; i <<=  4; }
      if (i >>> 30 == 0) { n +=  2; i <<=  2; }
      n -= i >>> 31;
      return n;
}

这段代码设计的十分巧妙,起初看的时候一脸懵逼,后来慢慢才发现一些蛛丝马迹。这是一个二分搜索:
用来寻找i最高非0位左边存在的0的个数。
大概逻辑就是:

  1. 先对i进行无符号右移16位判断(移走低16位,剩下高16位),如果这个i全部包含在低16位中,那么我们需要加上16。然后才是真正的移位。如果不包含,那么我们直接考虑高16位。
  2. 第一步舍去一半了,剩下的我们继续进行二分,与步骤1一样。
  3. 一直重复到还剩下两位,二分情况就是:(16,8,4,2)
  4. n -= i >>> 31; 通过对最后两位判断,如果首位为1那么我们需要减去初始的n=1,如果首位为0那么我们什么都不错,这个0在初始的时候就已经加上了。

chars 代表的是char的个数,mag代表的是除去前面0的个数

int chars = Math.max(((mag + (shift - 1)) / shift), 1);

为什么是(mag + (shift - 1)) / shift,这是防止chars为0,我们需要chars至少为1,当mag=1时,((mag + (shift - 1)) / shift)是为1的。我们使用shift - 1

formatUnsignedInt解析

static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    int charPos = len;
      int radix = 1 << shift;
      int mask = radix - 1;
      do {
          buf[offset + --charPos] = Integer.digits[val & mask];
          val >>>= shift;
      } while (val != 0 && charPos > 0);

      return charPos;
  }

重点在于mask,取移位后的反码,当shift为4时,则mask=1111,我们通过val & mask4位4位的截取,就是16进制,1100这样为一组.
charPos为什么从高往低处自减?
11111111对应的就是FF,截取的顺序是从右到左,所以我们需要从高到低位的设置char[]的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值