Java之Integer类源码学习

hresh 428 0

Java之Integer类源码学习

基本数据类型 int 的包装类即为 Integer,纵观 Integer、Long、Float、Double 数值类的源码,觉得 Integer 类的内容更加丰富,包含的小知识点更多,因此决定研究一下该类的源码,顺便做一下知识总结。该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法。

类定义

public final class Integer extends Number implements Comparable<Integer>

从类定义中我们可以知道以下几点:

1、Integer 类不能被继承(其他包装类也都是 final 修饰的,不可被继承)

2、Integer 类实现了 Comparable 接口,所以可以用 compareTo 进行比较并且 Integer 对象只能和
Integer 类型的对象进行比较,不能和其他类型比较(至少调用 compareTo 方法无法比较)。

3、Integer 继承了 Number 类,所以该类可以调用
longValue、floatValue、doubleValue等系列方法返回对应的类型的值。

属性

1、私有属性

Integer 类中定义了以下几个私有属性:

private final int value;
private static final long serialVersionUID = 1360826667806852920L;

serialVersionUID 和序列化有关。String 的源码学习中有介绍,这里不再赘述。

还有一个私有属性——value属性就是Integer对象中真正保存int值的。

当我们使用new Integer(10)创建一个Integer对象的时候,就会用以下形式给value赋值。还有其他的构造函数在后面会讲。

public Integer(int value) {
    this.value = value;
}

这里我们讨论一下 Interger 对象的可变性。从 value 的定义形式中可以看出 value 被定义成 final 类型。也就说明,一旦一个 Integer 对象被初始化之后,就无法再改变 value 的值。那么这里就深入讨论一下以下代码的逻辑:

public class IntegerTest {
    public static void main(String[] args) {
        Integer i = new Integer(10);
        i = 5;
    }
}

在以上代码中,首先调用构造函数 new 一个 Integer 对象,给私有属性 value 赋值,这时 value=10,接下来使用 i=5 的形式试图改变 i 的值。有一点开发经验的同学都知道,这个时候如果使用变量 i,那么它的值一定是 5,那么 i=5 这个赋值操作到底做了什么呢?到底是如何改变i的值的呢?是改变了原有对象 i 中 value 的值还是重新创建了一个新的 Integer 对象呢?

我们将上面的代码进行反编译,反编译之后的代码如下:

Java之Integer类源码学习

通过看反编译之后的代码我们发现,编译器会把 i=5 本质上是调用 Integer.valueOf()方法;这里先直接给出结论,i=5 操作并没有改变 new Integer(10)对象中的 value 值,只是将 i 指向了另外一个对象;给 Integer 类型的变量赋值。要么是直接返回一个已有对象,要么新建一个对象。这里的具体实现细节在后面讲解 valueOf 方法的时候给出。

2、公共属性

//值为 (-(2的31次方)) 的常量,它表示 int 类型能够表示的最小值。
public static final int MIN_VALUE = -2147483648;
//值为 ((2的31次方)-1) 的常量,它表示 int 类型能够表示的最大值。
public static final int MAX_VALUE = 2147483647;
//表示基本类型 int 的 Class 实例。
public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
static final char[] digits = new char[]{'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'};
static final char[] DigitTens = new char[]{'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 char[] DigitOnes = new char[]{'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'};
static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};
//用来以二进制补码形式表示 int 值的比特位数。
public static final int SIZE = 32;
//int的字节数
public static final int BYTES = 4;

方法

构造方法

Integer 提供了两个构造方法:

public Integer(int var1) {
    this.value = var1;
}

public Integer(String var1) throws NumberFormatException {
    this.value = parseInt(var1, 10);
}

从构造方法中我们可以知道,初始化一个 Integer 对象的时候只能创建一个十进制的整数,后续会讲解 parseInt()方法。

Integer valueOf()方法

valueOf()方法被声明为静态方法,共有三种实现方式。

public static Integer valueOf(String var0, int var1) throws NumberFormatException {
    return parseInt(var0, var1);
}

public static Integer valueOf(String var0) throws NumberFormatException {
    return parseInt(var0, 10);
}

public static Integer valueOf(int var0) {
    return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}

首先分析 Integer valueOf(int var0) 方法,在 Java 中基于各种数据类型分析 和 equals 的区别一节中有提到过,当传进 valueOf 的参数在[-128,127]范围内,则会从常量池中返回已有对象,而不会重新 new 一个对象。如下例所示:

Integer i = 100;//转换为Integer i = Integer.valueOf(100)
Integer j = 100;
System.out.println("i,it's memory address:" + System.identityHashCode(i));
System.out.println("j,it's memory address:" + System.identityHashCode(j));
Integer ii = 128;//转换为Integer ii = Integer.valueOf(128)
Integer jj = 128;
System.out.println("ii,it's memory address:" + System.identityHashCode(ii));
System.out.println("jj,it's memory address:" + System.identityHashCode(jj));

//执行结果为:
i,it's memory address:21685669
j,it's memory address:21685669
ii,it's memory address:2133927002
jj,it's memory address:1836019240

根据输出结果可知,在[-128,127]范围内,返回的是同一个对象地址,反之则重新生成一个新的对象。

这里就需要提一下 Integer 类中的私有静态类 IntegerCache,该部分内容会在 Integer 类被加载的时候就执行。

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;

    private IntegerCache() {
    }

    static {
        int var0 = 127;
        String var1 = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        int var2;
        if (var1 != null) {
            try {
                var2 = Integer.parseInt(var1);
                var2 = Math.max(var2, 127);
                var0 = Math.min(var2, 2147483518);
            } catch (NumberFormatException var4) {
            }
        }

        high = var0;
        cache = new Integer[high - -128 + 1];
        var2 = -128;

        for(int var3 = 0; var3 < cache.length; ++var3) {
            cache[var3] = new Integer(var2++);
        }

        assert high >= 127;

    }
}

当 Integer 被加载时,就新建了-128 到 127 的所有数字并存放在 Integer 数组 cache 中。

再回到 valueOf 代码,可以得出结论。当调用 valueOf 方法(包括后面会提到的重载的参数类型包含 String 的 valueOf 方法)时,如果参数的值在[-128,127]之间,则直接从缓存中返回一个已经存在的对象。如果参数的值不在这个范围内,则 new 一个 Integer 对象返回。

所以,当把一个 int 变量转成 Integer 的时候(或者新建一个 Integer 的时候),建议使用 valueOf 方法来代替构造函数。或者直接使用 Integer i = 100

当 valueOf 方法中有字符串参数时,实质上是调用 parseInt 方法,这里分析一下该方法。

//当传入的字符串含有中英文字符时,会提示报错。目前测试仅支持数字字符串,不包括小数点,可以包含正负符号
public static int parseInt(String var0, int var1) throws NumberFormatException {//var1为10
    if (var0 == null) {
        throw new NumberFormatException("null");
    } else if (var1 < 2) {
        throw new NumberFormatException("radix " + var1 + " less than Character.MIN_RADIX");
    } else if (var1 > 36) {
        throw new NumberFormatException("radix " + var1 + " greater than Character.MAX_RADIX");
    } else {
        int var2 = 0;
        boolean var3 = false;
        int var4 = 0;
        int var5 = var0.length();
        int var6 = -2147483647;
        if (var5 > 0) {
            char var9 = var0.charAt(0);//获取字符串首字符,确定最后结果的正负
            if (var9 < '0') {//Character.hashCode('0')等于48,'-'和'+'均小于它
                if (var9 == '-') {//假如var0为“-1234”时,当首字符为'-',最后结果也为负值,否则都为正值
                    var3 = true;
                    var6 = -2147483648;
                } else if (var9 != '+') {
                    throw NumberFormatException.forInputString(var0);
                }

                if (var5 == 1) {
                    throw NumberFormatException.forInputString(var0);
                }

                ++var4;
            }

            int var8;
            for(int var7 = var6 / var1; var4 < var5; var2 -= var8) {
            //在指定的基数返回字符ch的数值,在此程序中基数为十进制,若为中文字符,返回为-1
                var8 = Character.digit(var0.charAt(var4++), var1);
                if (var8 < 0) {
                    throw NumberFormatException.forInputString(var0);
                }

                if (var2 < var7) {
                    throw NumberFormatException.forInputString(var0);
                }

                var2 *= var1;
                if (var2 < var6 + var8) {
                    throw NumberFormatException.forInputString(var0);
                }
            }

            return var3 ? var2 : -var2;
        } else {
            throw NumberFormatException.forInputString(var0);
        }
    }
}

public static int parseInt(String var0) throws NumberFormatException {
    return parseInt(var0, 10);
}

使用第二个参数指定的基数(如果没指定,则按照十进制处理),将字符串参数解析为有符号的整数。除了第一个字符可以是用来表示负值的 ASCII 减号 ‘-‘ (‘\u002D’)外,字符串中的字符必须都是指定基数的数字(通过 Character.digit(char, int) 是否返回一个负值确定)。返回得到的整数值。

如果发生以下任意一种情况,则抛出一个 NumberFormatException 类型的异常:

字符串为 null 或一个长度为零的字符串。

基数小于 Character.MIN_RADIX 或者大于 Character.MAX_RADIX。

假如字符串的长度不小于 1,那么除了第一个字符(hashCode 小于'0'的字符)可以是减号 ‘-‘ 或加号'+'外,字符串中存在任意不是由指定基数的数字表示的字符。此外仅有一个字符(hashCode 小于'0')的情况也会报错。

字符串表示的值不是 int 类型的值,即存在中文字符。

示例:

public static void parseInt(String var0,int var1){
   System.out.println(Integer.parseInt(var0,var1));
}

parseInt("0", 10); //返回 0
parseInt("473", 10); //返回 473
parseInt("-0", 10);// 返回 0
parseInt("+0", 10);// 返回 0
parseInt("12345",16);//74565
parseInt("acbe",16);//44222
parseInt("-FF", 16);// 返回 -255
parseInt("1100110", 2);// 返回 102
parseInt("2147483647", 10);// 返回 2147483647
parseInt("-2147483648", 10);// 返回 -2147483648
parseInt("2147483648", 10);// 抛出 NumberFormatException
parseInt("77", 8);// 返回 63
parseInt("99", 8);// 抛出 NumberFormatException
parseInt("Hollis", 10);// 抛出 NumberFormatException
parseInt("Hollis", 27);// 抛出 NumberFormatException
parseInt("ADMIN", 27);// 返回 5586836

根据结果可知,当基数为 n 时,第一个参数中的字符串分为两部分:符号和字符序列,字符序列中每个字符的大小不得超过 n。比如说当基数为 16 时,字符串中的字符在[0,9]和[a,f]以及[A,F]取值,否则会抛出异常。

String 转成 Integer(int)的方法

Integer getInteger(String nm)
Integer getInteger(String nm, int val)
Integer getInteger(String nm, Integer val)
Integer decode(String nm)
Integer valueOf(String s)
Integer valueOf(String s, int radix)
int parseUnsignedInt(String s)
int parseUnsignedInt(String s, int radix)
int parseInt(String s)
int parseInt(String s, int radix)

以上所有方法都能实现将 String 类型的值转成 Integer(int)类型(如果 String 不包含可解析整数将抛出 NumberFormatException)

可以说,所有将 String 转成 Integer 的方法都是基于 parseInt 方法实现的。简单看一下以上部分方法的调用栈。

getInteger(String nm) ---> getInteger(nm, null);--->Integer.decode()--->Integer.valueOf()--->parseInt()
1、getInteger()方法
public static Integer getInteger(String var0, Integer var1) {
    String var2 = null;

    try {
        var2 = System.getProperty(var0);
    } catch (NullPointerException | IllegalArgumentException var4) {
    }

    if (var2 != null) {
        try {
            return decode(var2);
        } catch (NumberFormatException var5) {
        }
    }

    return var1;
}

确定具有指定名称的系统属性的整数值。 第一个参数被视为系统属性的名称。通过 System.getProperty(java.lang.String) 方法可以访问系统属性。然后,将该属性的字符串值解释为一个整数值,并返回表示该值的 Integer 对象。使用 getProperty 的定义可以找到可能出现的数字格式的详细信息。

Properties props = System.getProperties();
Set<Object> set = props.keySet();
for(Object obj:set){
    System.out.println(String.format("%s----:%s",obj.toString(),props.getProperty(obj.toString())));
}
props.put("hollis.integer.test.key","10000");
Integer i = Integer.getInteger("hollis.integer.test.key");
System.out.println(i);
//输出 10000

在这里插入图片描述
另外两个方法

getInteger(String nm,int val)
getInteger(String nm)
System.out.println(Integer.getInteger("hello"));//null
System.out.println(Integer.getInteger("hello",10));//输出10
System.out.println(Integer.getInteger("",10));//输出10
System.out.println(Integer.getInteger(null,10));//输出10

第二个参数是默认值。如果未具有指定名称的属性,或者属性的数字格式不正确,或者指定名称为空或 null,则返回默认值。

2、decode()方法
//将 String 解码为 Integer。接受十进制、十六进制和八进制数字
public static Integer decode(String var0) throws NumberFormatException {//var0由三部分组成:符号、基数说明符和字符序列
    byte var1 = 10;//基数默认为十进制
    int var2 = 0;
    boolean var3 = false;
    if (var0.length() == 0) {
        throw new NumberFormatException("Zero length string");
    } else {
        char var5 = var0.charAt(0);
        if (var5 == '-') {//符号判断
            var3 = true;
            ++var2;
        } else if (var5 == '+') {
            ++var2;
        }
        //0x,0X,#表示十六进制
        if (!var0.startsWith("0x", var2) && !var0.startsWith("0X", var2)) {
            if (var0.startsWith("#", var2)) {
                ++var2;
                var1 = 16;
            } else if (var0.startsWith("0", var2) && var0.length() > 1 + var2) {//0表示八进制
                ++var2;
                var1 = 8;
            }
        } else {
            var2 += 2;
            var1 = 16;
        }

        if (!var0.startsWith("-", var2) && !var0.startsWith("+", var2)) {
            Integer var4;
            try {
                var4 = valueOf(var0.substring(var2), var1);
                var4 = var3 ? -var4 : var4;
            } catch (NumberFormatException var8) {
                String var7 = var3 ? "-" + var0.substring(var2) : var0.substring(var2);
                var4 = valueOf(var7, var1);
            }

            return var4;
        } else {
            throw new NumberFormatException("Sign character in wrong position");
        }
    }
}

使用例子举例如下:

Integer DecimalI1 = Integer.decode("+10");
Integer OctI = Integer.decode("-010");
Integer HexI1 = Integer.decode("-0x10");
Integer HexI2 = Integer.decode("#10");
Integer HexI3 = Integer.decode("0X10");
System.out.println(DecimalI1);
System.out.println(OctI);
System.out.println(HexI1);
System.out.println(HexI2);
System.out.println(HexI3);
//10 -8 -16 16 16

decode 方法的具体实现也比较简单,首先就是判断 String 类型的参数 var 是否以(+/—)符号开头。然后再依次判断是否以”0x”、“#”、“0”开头,确定基数说明符(进制数,主要分为八进制、十进制、十六进制)的值。然后将字符串 var 进行截取,只保留其中纯数字部分。在用截取后的纯数字和基数调用 valueOf(String s, int radix)方法并返回其值。

3、Double.valueOf().intValue()方法

当传入的字符串包含小数点时,以上方法都不适合转换,如果仅仅是需要保存整数部分,则可以考虑使用该方法。

String value = "1.3";
System.out.println(Double.valueOf(value).intValue());

至于 valueOf 方法和 parseInt 方法在之前也做过分析。

总结

上面列举了很多能够将 String 转成 Integer 的方法。那么他们之间有哪些区别,又该如何选择呢?

parseInt 方法接收字符串参数,返回的是基本类型int

其他的方法返回的是 Integer

valueOf(String)方法会调用 parseInt (String) 方法。

如果只需要返回一个基本类型,而不需要一个对象,可以直接使用Integert.parseInt("123");

如果需要一个对象,那么建议使用 valueOf(),因为该方法可以借助缓存带来的好处。

如果和进制有关,那么就是用 decode 方法。

如果是从系统配置中取值,那么就是用 getInteger。

int 转成 String 的方法

String  toString()
static String   toString(int i)
static String   toString(int i, int radix)
static String   toBinaryString(int i)
static String   toHexString(int i)
static String   toOctalString(int i)
static String   toUnsignedString(int i)
static String   toUnsignedString(int i, int radix)

从 toString()方法开始讲起,虽然定义很简单,无须传入参数,将一个 Integer 类型的数字转换为字符串类型,平时经常使用该方法,调用起来非常简单,但是阅读该方法后,就会觉得其实现方式非常优秀。

 public String toString() {
     return toString(this.value);
 }

toString()方法不是静态方法,需要 Integer 对象来调用,实质上又是调用 Integer 类的静态方法 toString(int i)。

public static String toString(int var0) {
    if (var0 == -2147483648) {
        return "-2147483648";
    } else {
        int var1 = var0 < 0 ? stringSize(-var0) + 1 : stringSize(var0);
        char[] var2 = new char[var1];
        getChars(var0, var1, var2);
        return new String(var2, true);
    }
}

上述代码挨个进行分析,首先是:

if (var0 == -2147483648) {
   return "-2147483648";
} 

这里对 int 值做校验,如果等于 Int 能表示的最小值,则直接返回最小值的字符串形式。那么为什么-2147483648 要特殊处理呢?接下来会继续分析。

int var1 = var0 < 0 ? stringSize(-var0) + 1 : stringSize(var0);
char[] var2 = new char[var1];

这段代码主要是为了计算出 int 值的位数,并创建一个 char 数组。其中计算位数调用的是 stringSize()方法,该方法实现如下:

static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};

static int stringSize(int var0) {
    int var1;
    for(var1 = 0; var0 > sizeTable[var1]; ++var1) {
    }

    return var1 + 1;
}

该方法要求传入一个正整数,如果传入的数字 var0 的值是 10000,那么因为它大于 9,99,999,9999,小于 99999。所以它会返回 99999 在整型数组 sizeTable 中的下标 4,然后加1 返回 5。我们看 10000 这个数字的位数也确实是 5。所以,就实现了返回一个正整数的位数。

设置 size 时,当 var0<0 的时候返回的 size 在 stringSize 方法的基础上+1 的目的是这一位用来存储负号。

由于 stringSize 方法要求传入一个正整数,如果是负数,需要将负数转成正数传入。代码片段一中,将-2147483648的值直接返回的原因就是 int 最大只能表示 2147483647,无法将 stringSize(-var0)中的i赋值成 -2147483648。

getSize 使用了的体系结构知识:
1.局部性原理之空间局部性:sizeTable为数组,存储在相邻的位置,cpu一次加载一个块数组数据到cache中(多个数组数据),此后访问sizeTable 不需要访问内存。

2.基于范围的查找,是很实用的设计技术

接着分析代码如下:

getChars(var0, var1, var2);

那么接下来就深入理解一下 getChars 方法。这部分我把关于这段代码的分析直接写到注释中,便于结合代码理解。

static void getChars(int var0, int var1, char[] var2) {
    int var5 = var1;
    byte var6 = 0;
    if (var0 < 0) {
        var6 = 45;//'-'的hashCode为45,后续(char)45可以转换为'-'
        var0 = -var0;
    }

    int var3;
    int var4;
    //当var0值高于两字节,每次循环过后,会将var0中的最后后两位保存到字符数组var2中的最后两位中
    //当var0为12345678时,var2数组长度var1为8,第一次循环结束之后,var2[7] = 8,var2[6]=7。第二次循环结束之后,var2[5] = 6,var2[4] = 5。
    while(var0 >= 65536) {
        var3 = var0 / 100;
        var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));
        var0 = var3;
        --var5;
        //取DigitOnes[r]的目的其实取数字r%10的结果
        var2[var5] = DigitOnes[var4];
        --var5;
        //取DigitTens[r]的目的其实是取数字r/10的结果
        var2[var5] = DigitTens[var4];
    }

    //循环将低两字节数字存入字符数组中空余位置
    do {
        //这里其实就是除以10。取数52429和19的原因在后文分析
        var3 = var0 * 52429 >>> (19);
        var4 = var0 - ((var3 << 3) + (var3 << 1));
        --var5;
        //将数字i的最后一位存入字符数组,
        //还是12345678那个例子,这个for循环第一次结束后,buf[3]=4。
        var2[var5] = digits[var4];
        var0 = var3;
    } while(var3 != 0);

    if (var6 != 0) {//为负数的时候,数组第一个字符加上'-'
        --var5;
        var2[var5] = (char)var6;
    }

}

其中用到的 DigitTens、DigitOnes 和 digits 数组在 Integer 类中有声明,比如取 DigitTens[78],返回的是数字 7,DigitOnes[78]返回数字 8。

接下来分析两个问题:

问题一、为什么在 getChars 方法中,将整型数字写入到字符数组的过程中为什么按照数字65536分成了两部分呢?这个65535是怎么来的?

部分一

 while(var0 >= num1) {
     var3 = var0 / 100;
     var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));
     var0 = var3;
     --var5;
     var2[var5] = DigitOnes[var4];
     --var5;
     var2[var5] = DigitTens[var4];
 }

部分二

    do {
        var3 = var0 * num2>>> (num3);
        var4 = var0 - ((var3 << 3) + (var3 << 1));
        --var5;
        //将数字i的最后一位存入字符数组,
        //还是12345678那个例子,这个for循环第一次结束后,buf[3]=4。
        var2[var5] = digits[var4];
        var0 = var3;
    } while(var3 != 0);

使用 num1,num2,num3 三个变量代替源代码中的数字,便于后面分析使用。

问题二、在上面两段代码的部分二中,在对i进行除十操作的过程中为什么选择先乘以 52429 在向右移位 19 位。其中 52429 和 19 是怎么来的?

回答上面两个问题之前,首先要明确两点:

移位的效率比直接乘除的效率要高

乘法的效率比除法的效率要高

先理解以下代码:

var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));表示的其实是var4=var0 - (var3 * 100);,var0-var3 *2^6 - var3 *2^5 - var3 *2^2 = var0-64*var3 -32*var3 -4*var3 = var0-100*var3 。

var3 = var0 * num2>>> (num3);中,>>>表示无符号向右移位。代表的意义就是除以2^num3。 所以 var3 = (var0 * 52429) >>> (16+3); 可以理解为:var3 = (var0 * 52429) / 524288;那么就相当于 var3= var0 * 0.1 也就是 var3=var0/10,这样通过乘法和向右以为的组合的形式代替了除法,能提高效率。

再来回答上面两个问题中,部分一和部分二中最大的区别就是部分一代码使用了除法,第二部分只使用了乘法和移位。因为乘法和移位的效率都要比除法高,所以第二部分单独使用了乘法加移位的方式来提高效率。那么为什么不都使用乘法加移位的形式呢?为什么大于 num1(65536)的数字要使用除法呢?原因是 int 型变量最大不能超过(2^31-1)。如果使用一个太大的数字进行乘法加移位运算很容易导致溢出。那么为什么是 65536(2^16)这个数字呢?第二阶段用到的乘法的数字 num2 和移位的位数 num3 又是怎么来的呢?

既然我们要使用 var3 = var0 * num2>>> (num3); 的形式使用乘法和移位代替除法,那么 num2 和 num3 就要有这样的关系:num2= (2^num3 /10 +1)

只有这样才能保证(i * num2) >>> (num3)结果接近于0.1。

那么 52429 这个数是怎么来的呢?来看以下数据:

2^10=1024, 103/1024=0.1005859375
2^11=2048, 205/2048=0.10009765625
2^12=4096, 410/4096=0.10009765625
2^13=8192, 820/8192=0.10009765625
2^14=16384, 1639/16384=0.10003662109375
2^15=32768, 3277/32768=0.100006103515625
2^16=65536, 6554/65536=0.100006103515625
2^17=131072, 13108/131072=0.100006103515625
2^18=262144, 26215/262144=0.10000228881835938
2^19=524288, 52429/524288=0.10000038146972656
2^20=1048576, 104858/1048576=0.1000003815
2^21=2097152, 209716/2097152 = 0.1000003815
2^22= 4194304, 419431/4194304= 0.1000001431

超过 22 的数字就不列举了,因为如果 num3 越大,就会要求 var0 比较小,因为必须保证(var0 * num2) >>> (num3)的过程不会因为溢出而导致数据不准确。那么是怎么敲定 num1=65536,num2= 524288, num3=19 的呢? 这三个数字之间是有这样一个操作的:(num1* num2)>>> num3

因为要保证该操作不能因为溢出导致数据不准确,所以 num1 和 num2 就相互约束。两个数的乘积是有一定范围的,不成超过这个范围,所以,num1 增大,num2 就要随之减小。

我觉得有以下几个原因:

1.65536正好是2^16,一个整数占4个字节。65536正好占了2个字节,选定这样一个数字有利于CPU访问数据。
2.52429/524288=0.10000038146972656精度足够高。

3.下一个精度较高的num2和num3的组合是419431和22,此时 num1*num2会内存溢出。

不知道有没有人发现,其实 65536* 52429 是超过了int的最大值的,一旦超过就要溢出,那么为什么还能保证(num1* num2)>>> num3能得到正确的结果呢?

这和>>>有关,因为>>>表示无符号右移,它会在忽略符号位,空位都以 0 补齐。

一个有符号的整数能表示的范围是[-2147483648,2147483647],但是无符号的整数能表示的范围就是[0,4294967296(2^32)],所以,只要保证 num2*num3 的值不超过2^32次方就可以了。65536 是2^16,52429 正好小于2^16,所以,他们的乘积在无符号向右移位就能保证数字的准确性。

getChars使用了的体系结构知识

1.乘法比除法高效:var3 = var0 * 52429 >>> (16+3); => 约等于 var00.1, var052429是整数乘法器,结合位移避免除法。

2.重复利用计算结果:在获取var4=(var0%100)时,充分利用了var3 = var0 / 100;除法的结果,结合位移避免重复计算。

3.位移比乘法高效:var4 = var0 - ((var3 << 6) + (var3 << 5) + (var3 << 2));等价于 var4 = var0 – (var3 * 100);

4.局部性原理之空间局部性

(1).var2[var5] =DigitOnes[var4 ];buf[var5]=DigitTens[var4 ];通过查找数组,实现快速访问,避免除法计算

(2).var2[var5] = digits [var4];

最后一行代码:

new String(var2, true);

这里用到了一个 String 中提供的保护类型构造函数,关于此函数请查看String源码分析,该函数比使用其他的构造函数有更好的性能。

总结

所以,通过调用 Integer 类的静态方法能够将 int 值转成 String 类型。除了上面提到的一系列方法外,一般在要使用 String 的时候,很多人愿意使用如下形式:

Integer s = new Integer(199);
System.out.println(s + "");

反编译一下,结果如下:

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/lang/Integer
       3: dup
       4: sipush        199
       7: invokespecial #3                  // Method java/lang/Integer."<init>":(I)V
      10: astore_1
      11: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14: new           #5                  // class java/lang/StringBuilder
      17: dup
      18: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      21: aload_1
      22: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
      25: ldc           #8                  // String
      27: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      33: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: return
}

可以看出实际上是通过 StringBuilder 对象进行拼接的,使用 JMH 进行了测试,结果证明调用 Integer 自己的方法效率更高。

compareTo 方法

在介绍 Interger 的类定义的时候介绍过,Integer 类实现了Comparable接口,所以 Integer 对象可以和另外一个 Integer 对象进行比较。

public int compareTo(Integer var1) {
    return compare(this.value, var1.value);
}

public static int compare(int var0, int var1) {
    return var0 < var1 ? -1 : (var0 == var1 ? 0 : 1);
}

代码实现比较简单,就是拿出其中的 int 类型的 value 进行比较。

实现 Number 的方法

int intValue();
long longValue();
float floatValue();
double doubleValue();
byte byteValue();
short shortValue();

实现如下:

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

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

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

equals 和 hashCode 方法

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

public boolean equals(Object var1) {
    if (var1 instanceof Integer) {
        return this.value == (Integer)var1;
    } else {
        return false;
    }
}

Integer 类也重写了 equals 和 hashCode 方法,实现比较简单。

发表评论 取消回复
表情 图片 链接 代码

分享