资讯专栏INFORMATION COLUMN

Java源码阅读笔记之Integer

wenzi / 2032人阅读

摘要:估计这就是推荐使用的主要原因吧正负标识判断输入的字符串是否为开头转化逻辑字符串转化为的关键在于数组,以进制为例,用表示到,满才会进。

Integer的基本实现
Integer的使用
Integer封装的操作
Integer的基本实现

基本描述:
Integer是对原生基本类型int的封装,其定义value来存储值和一些用于描述int的信息

 int value;//int
 int SIZE = 32;//1位正负标识+31位数据
 int BYTES = SIZE / Byte.SIZE;//所占字节
 int   MIN_VALUE = 0x80000000;//最小值,32个1
 int   MAX_VALUE = 0x7fffffff;//最大值,0+31个1

构造函数:
允许通过String和int入参来为value赋值,但是两个构造函数都已弃用

通过注释可以看到,推荐通过valueOf()的方法来返回一个Integer

    /**
    * @deprecated
     * It is rarely appropriate to use this constructor. The static factory
     * {@link #valueOf(int)} is generally a better choice, as it is
     * likely to yield significantly better space and time performance.
     */
  @Deprecated(since="9")
  public Integer(int value) {
     this.value = value;
   }

    /**
     * @deprecated
     * It is rarely appropriate to use this constructor.
     * Use {@link #parseInt(String)} to convert a string to a
     * {@code int} primitive, or use {@link #valueOf(String)}
     * to convert a string to an {@code Integer} object.
     */
  @Deprecated(since="9")
  public Integer(String s) throws NumberFormatException {
     this.value = parseInt(s, 10);
  }

使用推荐的方法获取Integer实例和构造方法有何不同?

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

//----------------------String入参------------------
    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }

    //radix表示进制,取值范围为[2, 36]
    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }

int入参

如果入参中的int在IntegerCache内部类的Integer cache[]中存在则返回数组中的Integer否则通过构造函数创建(弃用的那个)

String入参

通过parseInt(s,radix)方法解析字符串,返回int值
radix参数表示字符串转换的int值的进制,其取值范围为[2,36]

解析IntegerCache和parseInt的实现

IntegerCache
    //The cache is initialized on first usage.
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            //The size of the cache may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
            String integerCacheHighPropValue =
                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;
        }

        private IntegerCache() {}
    }

IntegerCache是一个私有静态内部类该类内部定义了一个数组Integer cache[],数组内的数据由-128起始,默认至127为止(byte的范围)

该数组的最大值可通过在jvm中设置
-XX:AutoBoxCacheMax=来设置其大小

数组cache[128]为0,valueof(int)参数的值符合这个范围都会直接从数组中返回Integer

有意思的是valueof(int)是@HotSpotIntrinsicCandidate的,关于它的描述是这样的:

JDK的源码中,被@HotSpotIntrinsicCandidate标注的方法,在HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时,HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效率。

估计这就是推荐使用的主要原因吧!

parseInt
    public static int parseInt(String s, int radix)
                throws NumberFormatException
    {
      ...

        boolean negative = false;//正负标识
        int i = 0, len = s.length();
        int limit = -Integer.MAX_VALUE;

        if (len > 0) {
            char firstChar = s.charAt(0);
            //判断输入的字符串是否为"-"开头
            if (firstChar < "0") { // Possible leading "+" or "-"
                if (firstChar == "-") {
                    negative = true;
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != "+") {
                    throw NumberFormatException.forInputString(s);
                }

                if (len == 1) { // Cannot have lone "+" or "-"
                    throw NumberFormatException.forInputString(s);
                }
                i++;
            }
            //转化逻辑
            int multmin = limit / radix;
            int result = 0;
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                int digit = Character.digit(s.charAt(i++), radix);
                if (digit < 0 || result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix;
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;
            }
            return negative ? result : -result;
        } else {
            throw NumberFormatException.forInputString(s);
        }
    }

    static final char[] digits = {
        "0" , "1" , "2" , "3" , "4" , "5" ,
        "6" , "7" , "8" , "9" , "a" , "b" ,
        "c" , "d" , "e" , "f" , "g" , "h" ,
        "i" , "j" , "k" , "l" , "m" , "n" ,
        "o" , "p" , "q" , "r" , "s" , "t" ,
        "u" , "v" , "w" , "x" , "y" , "z"
    };

字符串转化为int的关键在于digits数组,以16进制为例,用0...9,a...f表示0到15,满16才会进1。也就是超过10进制以后,大于10的数要使用a开始的字母表示,但是字母只有26个,进制又必须从2开始,故进制的取值范围也就定义为[2, 36]

故入参的字符串s也必须符合digits数组中的元素以及额外的只可能存在第一位"+"或者"-"

parseInt的转化逻辑为:
在每次循环中

取出digit,确定进制后转化的int数

通过result *= radix;把上一次循环的数据进一位

通过result -= digit;把当前的数据加入result

然后返回结果,通过:
return negative ? result : -result;

Integer的使用
   int a = 5;
   Integer w = 6;
   Integer test = Integer.valueOf(w);
   int testP = Integer.valueOf(a);

转化成对应的字节码,则

int a = 5

0: iconst_5
1: istore_1
直接将自然数压栈

Integer w = 6
2: bipush 6
4: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
7: astore_2

调用Integer的静态方法valueof(6)得到Integer实例

Integer test = Integer.valueOf(w)
8: aload_2
9: invokevirtual #3 // Method java/lang/Integer.intValue:()I
12: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
15: astore_3

获取操作数栈中w的引用,调用intValue返回int值,再通过valueof获取Integer实例

int testP = Integer.valueOf(a)
16: iload_1
17: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20: invokevirtual #3 // Method java/lang/Integer.intValue:()I

获取操作数栈中的a,调用valueof获取Integer实例,再通过intValue返回int值

由此可知,对于基本类型的封装类,编译器会自动调用其一些方法来实现用户操作的简化!

Integer封装的操作
Object虚函数的实现
父类Number的虚函数实现
字节操作

Object虚函数的实现

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

    public static int hashCode(int value) {
        return value;
    }

    public static String toString(int i) {
        int size = stringSize(i);
        if (COMPACT_STRINGS) {
            byte[] buf = new byte[size];
            getChars(i, size, buf);
            return new String(buf, LATIN1);
        } else {
            byte[] buf = new byte[size * 2];
            StringUTF16.getChars(i, size, buf);
            return new String(buf, UTF16);
        }
    }

equals

通过Integer的intValue获取入参的Integer封装的int值并与value进行==寻址判断

hashCode

hashCode返回的就是一个int值,故直接使用value本身

toString

使用char数组做中转,通过String实例化一个String实例
根据是否开启压缩机制判断使用的是LATIN1还是UTF16

父类Number的虚函数实现

    public byte byteValue() {
        return (byte)value;
    }

    public double doubleValue() {
        return (double)value;
    }

    public float floatValue() {
        return (float)value;
    }

    public int intValue() {
        return value;
    }

    public long longValue() {
        return (long)value;
    }

    public short shortValue() {
        return (short)value;
    }

只是对value进行强转

字节操作

计算int二进制形式左(右)侧有几个0,遇到1就停止计数
计算int二进制形式1的数量
左(右)移二进制形式
按位(字节)置换

计算int二进制形式左(右)侧有几个0,遇到1就停止计数

    //左侧
    public static int numberOfLeadingZeros(int i) {
        // HD, Count leading 0"s
        if (i <= 0)
            return i == 0 ? 32 : 0;
        int n = 31;
        if (i >= 1 << 16) { n -= 16; i >>>= 16; }
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; }
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; }
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; }
        return n - (i >>> 1);
    }

    //右侧
    public static int numberOfTrailingZeros(int i) {
        // HD, Figure 5-14
        int y;
        if (i == 0) return 32;
        int n = 31;
        y = i <<16; if (y != 0) { n = n -16; i = y; }
        y = i << 8; if (y != 0) { n = n - 8; i = y; }
        y = i << 4; if (y != 0) { n = n - 4; i = y; }
        y = i << 2; if (y != 0) { n = n - 2; i = y; }
        return n - ((i << 1) >>> 31);
    }

左侧:numberOfLeadingZeros

1 负数1标识,左侧无0,0全为0,直接返回32(int为32位)
2 通过1 << 16判断,判断条件为是否比它大,左边16位是否全为0,决定接下来操作左或右半边
3 再通过i << 8,4,2,1折半再折半计算出不为0的数字的位置,从而得出0的数量

右侧:numberOfTrailingZeros

通过i <<16,不为0则右边有1,再i << 8,4,2,1,判断出右边数起的第一个1,从而计算出0的数量

计算int二进制形式1的数量

    public static int bitCount(int i) {
        // HD, Figure 5-2
        i = i - ((i >>> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
        i = (i + (i >>> 4)) & 0x0f0f0f0f;
        i = i + (i >>> 8);
        i = i + (i >>> 16);
        return i & 0x3f;
    }

0x5 = 0101,通过做&运算记录双数位的数据情况

0x3 = 0011,通过做&运算记录后两位的数据情况
0x0f = 0000 1111,通过做&运算记录后四位的数据情况

1 int的二进制形式的可能有 00,01,10,11

先做>>>右移一位再与01做&运算,记录了两位二进制左边数字的1的数量,再用原来的二进制数减去记录的值
如11:11-01=10(11有两个1)
2 经过第一步计算,记录了以两位数为单位的1的数量
把第一步的结果与0011做&运算得到四位二进制结果的后两位计算,0011再与四位二进制结果>>>右移两位计算前两位的结果,再把其相加得到四位数中1的数量
如1011
1011 - 0101 = 0110
0001 + 0010 = 0011(1011有三个1)
3 i + (i >>> 4),i + (i >>> 8),i + (i >>> 16)分别把得到的上一步计算的结果整合计算
计算完成后记录结果的有效位数只有右边八位,32位数一共最多32个1,所以实际的有效位数只有右边6位

左(右)移二进制形式

    public static int rotateLeft(int i, int distance) {
        return (i << distance) | (i >>> -distance);
    }

    public static int rotateRight(int i, int distance) {
        return (i >>> distance) | (i << -distance);
    }

移动

调用<<或>>运算符移动,同时通过 | >>> -distance得到移动消逝的数据,并将其放在补0的位置

-distance表示移动-distance负数的表现形式int截取5位,long截取6位,如-1为32个1,截取5位为1 1111,为31,也就是不算位移,移动的“路程”是32,正好把移出的数据再补回补0的地方

按位(字节)置换

    public static int reverseBytes(int i) {
        return (i << 24)            |
               ((i & 0xff00) << 8)  |
               ((i >>> 8) & 0xff00) |
               (i >>> 24);
    }

    public static int reverse(int i) {
        // HD, Figure 7-1
        i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
        i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
        i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;

        return reverseBytes(i);
    }

按字节置换:reverseBytes

i << 24与i >>> 24做 | 运算得到最左右两边的置换
0xff00二进制形式为1111 1111 0000 0000
正好用来处理中间左八位和右八位的交换,主要是&和移动的先后来实现不同的位的清零

按位置换:reverse

1 使用01来记录两位二进制中的一位,再通过移动记录另一位,做 | 运算的会把两位的二进制数交换位置
2 通过0011来交换四位中的前两位和后两位
3 通过0000 1111来交换前四位和后四位
4 通过前三步实现交换每8位的循序,再通过按字节置换交换全部的顺序

后话

Integer中还有关于

    static final byte[] DigitTens = {
        "0", "0", "0", "0", "0", "0", "0", "0", "0", "0",
        "1", "1", "1", "1", "1", "1", "1", "1", "1", "1",
        "2", "2", "2", "2", "2", "2", "2", "2", "2", "2",
        "3", "3", "3", "3", "3", "3", "3", "3", "3", "3",
        "4", "4", "4", "4", "4", "4", "4", "4", "4", "4",
        "5", "5", "5", "5", "5", "5", "5", "5", "5", "5",
        "6", "6", "6", "6", "6", "6", "6", "6", "6", "6",
        "7", "7", "7", "7", "7", "7", "7", "7", "7", "7",
        "8", "8", "8", "8", "8", "8", "8", "8", "8", "8",
        "9", "9", "9", "9", "9", "9", "9", "9", "9", "9",
        } ;

    static final byte[] DigitOnes = {
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
        } ;

这两个数组的应用和字符和byte之间转换的精彩实现,有时间会记录。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74526.html

相关文章

  • 源码|jdk源码Object及装箱类型分析

    摘要:作为条件变量的的不仅可以认为内嵌了一把锁,还内嵌了一个条件变量。操作条件变量的函数将当前线程在条件变量上阻塞,一般是为了等待其他线程的某件事情执行完成。其它装箱类其它装箱类的代码这里就不分析了。重点关注下各装箱类的缓存范围。 jdk源码读到现在这里,重要的集合类也读了一部分了。集合类再往下读的话,就要涉及到两个方向。第一,是比较典型的但是不常用的数据结构,这部分我准备将数据结构复习、回...

    VioletJack 评论0 收藏0
  • PHP 手册阅读笔记 - 语言参考篇

    摘要:最近计划把手册,认真的先过一遍。语言参考类型新认知强制转换类型用。后期静态绑定从这里开始语言参考生成器新认知生成器汗水的核心是关键字。语言参考预定义变量超全局变量前一个错误信息原始数据以上 showImg(https://segmentfault.com/img/remote/1460000010147451); 最近计划把 PHP手册,认真的先过一遍。记录一些以前不知道,不明确的知识...

    Developer 评论0 收藏0
  • Laravel学习笔记Seeder填充数据小技巧

    摘要:而且,与是一对多关系一个分类下有很多,一个只能归属于一个与是一对多关系一篇博客下有很多,一条只能归属于一篇与是多对多关系一篇有很多,一个下有很多。 说明:本文主要聊一聊Laravel测试数据填充器Seeder的小技巧,同时介绍下Laravel开发插件三件套,这三个插件挺好用哦。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率。 备注:在设计个人博客软件时,总会碰到有分类Ca...

    cgspine 评论0 收藏0
  • Java笔记-容器源码(持续更新)

    摘要:加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行操作即重建内部数据结构,从而哈希表将具有大约两倍的桶数。成员变量每个对由封装,存在了对象数组中。 虽是读书笔记,但是如转载请注明出处 http://segmentfault.com/blog/exploring/ .. 拒绝伸手复制党 LinkedLis...

    mrli2016 评论0 收藏0
  • [学习笔记-Java集合-8] Map - ConcurrentHashMap 源码分析(二)

    摘要:那么,如果之后不是简单的操作,而是还有其它业务操作,之后才是,比如下面这样,这该怎么办呢其它业务操作这时候就没办法使用提供的方法了,只能业务自己来保证线程安全了,比如下面这样其它业务操作这样虽然不太友好,但是最起码能保证业务逻辑是正确的。 删除元素 删除元素跟添加元素一样,都是先找到元素所在的桶,然后采用分段锁的思想锁住整个桶,再进行操作。 public V remove(Objec...

    2501207950 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<