美文网首页JDK源码解析
Integer包装类源码阅读

Integer包装类源码阅读

作者: Hey_Shaw | 来源:发表于2018-03-19 14:20 被阅读6次

    背景
    很多面试题都有Java拆装箱问题,其中Integer与int区别最多,下面结合自己看的一些视频及文章做下总结。

    先上测试代码:

    public static void main(String[] args) {  
        int i = 150;  
        Integer i2 = 150;  ❸
        Integer i3 = new Integer(150);  ❷
    
        System.out.println(i == i2); // ★ Integer会自动拆箱为int,所以为true  ❶
        System.out.println(i == i3); //true,理由同上    ❶
        System.out.println(i2 == i3);  //false 
        System.out.println(i2.equals(i3)); //true
    
        Integer i4 = 127;  ❸
        Integer i5 = 127;  ❸
        System.out.println(i4 == i5);//true  ❹
        Integer i6 = 128;  
        Integer i7 = 128;  
        System.out.println(i6 == i7);//false ❺
                
        Integer i8 = new Integer(127);  
        System.out.println(i5 == i8); //false  ❻
    }  
    

    ❶属于自动拆箱,这里之所以选150测试,是避免Integer在-128到127之间进行缓存的误解。

    注:自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。

    ❷属于直接创建Integer对象,new过程会直接从堆中新开辟一个内存保存Integer数据,将地址存入栈中对象。==比较的就是栈中对象的地址信息。

    equals则不同,看源码

    /**
     * 将此对象与指定对象进行比较。
     * 当且仅当参数不是null并且是一个包含与该对象相同的int值的整数对象时,结果是真的。
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue(); // ⚘
        }
        return false;
    }
    
    public int intValue() {  // ♆
        return value;
    }
    
    public Integer(int value) {
        this.value = value;
    }
    
    private final int value;
    

    可以看到,equals比较的是初始化时传入的int值。一般比较Integer值相等最好用equals()方法。

    ❸部分在编译时被翻译成:Integer i4 = Integer.valueOf(参数);

    看源码:

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

    该部分代码进行了一个判断:在 IntegerCache.low 和 IntegerCache.high之间的值,则从IntegerCache.cache[i + (-IntegerCache.low)]数组对应的位置去数据,否则在堆内存中重新创建新的Integer对象。

    为什么是 IntegerCache.cache[i + (-IntegerCache.low)],IntegerCache.low 和 IntegerCache.high又分别是多少?

    最关键的部分来了,IntegerCache这个类,Ctrl + 左键 进行跟踪:

    private static class IntegerCache {
        static final int low = -128;   // 缓存下界,值不可变
        static final int high;         // 缓存上界  
        static final Integer cache[];  //  cache缓存是一个存放Integer类型的数组
    
        static {
            // high值可以由属性进行配置
            int h = 127;   
    
            // getSavedProperty() 获取虚拟机启动时配置参数java.lang.Integer.IntegerCache.high的值,该值为自行设置的缓存上界,看下面代码
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) { // 判断调用者是否设置改参数
                try {
                    // 将配置的integerCacheHighPropValue值传给i,parseInt()将字符串参数作为有符号的十进制整数进行解析。
                    int i = parseInt(integerCacheHighPropValue);  // ⚘
                    // 取配置的integerCacheHighPropValue值与127中最大的赋值给i,保证了上线至少是127
                    i = Math.max(i, 127);
                    // h取 i 与 Integer.MAX_VALUE - (-low) -1的最小值,其中 Integer.MAX_VALUE = 2^31 -1 = 2147483647,16进制标识为0x7fffffff
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // 如果属性不能解析为int,则忽略它,为parseInt()抛出的NumberFormatException异常 ,try -catch了,因此程序依然往下走.
                }
            }
    
            high = h;  // 没有直接使用high值进行操作,而是先初始h值再赋值给high,没想通设计者的这样写的具体目的
    
            // 创建一个长度为(high - low) + 1的Integer数组,此处为什么写成(high - low) + 1?为什么不是high - low,下面的循环写成k <= cache.length?
            cache = new Integer[(high - low) + 1]; 
            int j = low;   // 此处则控制了,cache缓存数组的下界是不可变的,new Integer(j++)。
    
            // 原来上面的写法,不仅直观,而且数组下标从0开始,而数组长度从1开始,如果写成high - low,k <= cache.length则会出现下标越界。
            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的静态内部类,因此在Integer初始化时同时初始化IntegerCache及其所有属性。具体详情,看上面代码注释,便于查看。

    关于 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")
    参数的设置

    规范就是规范,设置java.lang.Integer.IntegerCache.high的值用于什么地方,对于该写法一目了然。而该值怎么设置?两种办法

    -XX:AutoBoxCacheMax=128 或者 -Djava.lang.Integer.IntegerCache.high=128 进行设置

    添加此设置后:第❺部分则为true

    Eclipse可以通过 右击执行类 ——> Run AS ——> Run configurations ——> Arguments选项卡 ——> VM arguments中添加。

    包装类 是否缓存
    Boolean 全部缓存
    Byte 全部缓存
    Character <= 127 缓存
    Short -128 — 127 缓存
    Long -128 — 127 缓存
    Float 没有缓存
    Doulbe 没有缓存

    再看 parseInt(String s)源码部分

    /**
     * 将字符串参数作为有符号的十进制整数进行解析。除了第一个字符可以是用来表示负值的 ASCII 减号 '-' ( '\u002D') 
     * 外,字符串中的字符都必须是十进制数字。返回得到的整数值,就好像将该参数和基数 10 作为参数赋予 
     * parseInt(java.lang.String, int) 方法一样。
     */
    public static int parseInt(String s) throws NumberFormatException { // ♆
        return parseInt(s,10);  // ⚘
    }
    

    内部使用 parseInt(String s,int radix)方法

    /**
     * 使用第二个参数指定的基数,将字符串参数解析为有符号的整数。除了第一个字符可以是用来
     * 表示负值的 ASCII 减号 '-' ( '\u002D’)外,字符串中的字符必须都是指定基数的数字
     *(通过 Character.digit(char, int) 是否返回一个负值确定)。返回得到的整数值。
     * 
     * 如果发生以下任意一种情况,则抛出一个 NumberFormatException 类型的异常:
     * 
     * ● 第一个参数为 null 或一个长度为零的字符串。
     * ● 基数小于 Character.MIN_RADIX 或者大于 Character.MAX_RADIX。
     * ● 假如字符串的长度超过 1,那么除了第一个字符可以是减号 '-' ('u002D’) 外,
     * \字符串中存在任意不是由指定基数的数字表示的字符。
     * ● 字符串表示的值不是 int 类型的值。
     * 
     * 示例:parseInt("11", 10) 返回 11
     *      parseInt("11", 22) 返回 23
     *
     * @param s - 包含要解析的整数表示形式的 String
     * @param radix - 解析 s 时使用的基数,即以几进制输出
     * @return 使用指定基数的字符串参数表示的整数
     *
     */
    public static int parseInt(String s, int radix) throws NumberFormatException { // ♆
        /*
         * 警告:因为valueOf方法使用了parseInt方法和IntegerCache对象,因此valueOf在parseInt初始化之前使用会出现异常
         */
    
         // 下面三个if用来判断参数是否合法。radix大小在2~36之间。
        if (s == null) {
            throw new NumberFormatException("null");
        }
    
        if (radix < Character.MIN_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " less than Character.MIN_RADIX");
        }
    
        if (radix > Character.MAX_RADIX) {
            throw new NumberFormatException("radix " + radix +
                                            " greater than Character.MAX_RADIX");
        }
    
        int result = 0;             // 解析结果
        boolean negative = false;   //是否是负数
        int i = 0, len = s.length();  //索引变量和字符串长度
        int limit = -Integer.MAX_VALUE;  // 最大值限制,加'-'号是为了后面的result < 做统一处理
        int multmin;    //基数下的最小值
        int digit;      //记录每一位的数字
    
        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // 所有的特殊符号和字母字符均小于'0',判断是否有'-'或'+'
                if (firstChar == '-') {  // 判断是否为'-'或'+'
                    negative = true;     // 标识所传为负数
                    limit = Integer.MIN_VALUE;
                } else if (firstChar != '+')  // 格式非法,既不为'-',也不为'+'则抛出异常
                    throw NumberFormatException.forInputString(s);
    
                if (len == 1) // 排除只为'-'或者'+'的字符串情况
                    throw NumberFormatException.forInputString(s);
                i++;
            }
            multmin = limit / radix; 
            while (i < len) {
                // 利用了Character类中的digit方法,作用是解析一个字符返回int值,基本就是'0'-'9'则返回 0-9
                digit = Character.digit(s.charAt(i++),radix); // ⚘
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                // result进行基数转换,放在result -= digit前面,如转换"123",则为-(((1*22)+2)*22+3)即 1*22^2 +2*22^1+3*22^0
                result *= radix;  
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;   // 将字符处理后返回的digit赋值给result
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        return negative ? result : -result;   // 这里进行上面加'-'统一处理的问题
    }
    

    再看 Character.digit(char ch, int radix) 源码

    public static int digit(char ch, int radix) {  // ♆
        return digit((int)ch, radix); // ⚘
    }
    

    内部调用Character.digit(int codePoint, int radix)源码

    /**
     * 返回使用指定基数的字符 ch 的数值
     *
     * 如果基数不在 MIN_RADIX <= radix <= MAX_RADIX 范围之内,
     * 或者 ch 的值是一个使用指定基数的无效数字,则返回 -1。
     * 如果以下条件中至少有一个为真,则字符是一个有效数字:
     * 
     * ● 方法 isDigit 为 true,且字符(或分解的单字符)的 Unicode 十进制数值小于指定的基数。在这种情况下,返回十进制数值
     * ● 字符为 'A' 到 'Z' 范围内的大写拉丁字母之一,且它的代码小于 radix + 'A' - 10。在这种情况下,返回 ch - 'A' + 10
     * ● 字符为 'a' 到 'z' 范围内的小写拉丁字母之一,且它的代码小于 radix + 'a' - 10。在这种情况下,返回 ch - 'a' + 10
     * 注:此方法无法处理增补字符。若要支持所有 Unicode 字符,包括增补字符,请使用 digit(int, int) 方法
     *
     * @param  要转换的字符(Unicode 代码点),一般由char强转的int,char '0'-'9' 对应 int 48-57
     * @param  基数
     * @return  使用指定基数的字符所表示的数值
     * @开始版本   1.5
     */
    public static int digit(int codePoint, int radix) {  // ♆
        return CharacterData.of(codePoint).digit(codePoint, radix);  // ⚘
    }
    

    再看CharacterData.of(int ch) 源码,根据位运算选取CharacterData的实现类

    char的值,如果在255以内,就调用CharacterDataLatin1来处理,再多一位二进制数字
    用CharacterData00,然后CharacterData01,CharacterData02

    static final CharacterData of(int ch) {  // ♆
        if (ch >>> 8 == 0) {     // fast-path
            return CharacterDataLatin1.instance;
        } else {
            switch(ch >>> 16) {  //plane 00-16
            case(0):
                return CharacterData00.instance;
            case(1):
                return CharacterData01.instance;
            case(2):
                return CharacterData02.instance;
            case(14):
                return CharacterData0E.instance;
            case(15):   // Private Use
            case(16):   // Private Use
                return CharacterDataPrivateUse.instance;
            default:
                return CharacterDataUndefined.instance;
            }
        }
    }
    

    以CharacterDataLatin1.digit(int ch, int radix) 为例,大概就是传的是 '0' - '9' 之间的字符,就返回相应的数字。

    这块有些复杂,不是科班出身,需要脑补计算机基础,有兴趣的可以自行百度!

     int digit(int ch, int radix) {  // ♆
        int value = -1;
        if (radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX) {
            int val = getProperties(ch);  // ⚘
            int kind = val & 0x1F;
            if (kind == Character.DECIMAL_DIGIT_NUMBER) {
                value = ch + ((val & 0x3E0) >> 5) & 0x1F;
            }
            else if ((val & 0xC00) == 0x00000C00) {
                // Java supradecimal digit
                value = (ch + ((val & 0x3E0) >> 5) & 0x1F) + 10;
            }
        }
        return (value < radix) ? value : -1;
    }
    
    
    int getProperties(int ch) { // ♆
        char offset = (char)ch;
        int props = A[offset];
        return props;
    }
    
    static final int A[] = new int[256];   // 2^8=256
    
    static final String A_DATA =  
        "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+  
        // 省略....  
        "\u061D\u7002"; 
    
    static {
        { 这个代码是GenerateCharacter自动创建;这块静态代码块里写构造代码块的具体目的?
            char[] data = A_DATA.toCharArray();
            assert (data.length == (256 * 2));
            int i = 0, j = 0;
            while (i < (256 * 2)) {
                int entry = data[i++] << 16;
                A[j++] = entry | data[i++];     // 这块 i 和 j 的关系 : i = j * 2
            }
        }
    
    } 
    
    static final String A_DATA =  
        "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+  
        // 省略....  
        "\u061D\u7002"; 
    
    

    可以将上面代码单独拿出来自行测试,看看运算过程:

    public static void main(String[] args) {
            System.out.println( (char)57);
            System.out.println(Character.digit(57,10));
    //      a();
        }
        
        public static  void a() {
            
            System.out.println(87 >>> 8);
            
            String A_DATA =  "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+
                    // 省略。。。
                    "\u061D\u7002";
            
            int A[] = new int[256];
            char[] data = A_DATA.toCharArray();
            assert (data.length == (256 * 2));
            int i = 0, j = 0;
            while (i < (256 * 2)) {
                int entry = data[i++] << 16;
                char a = data[i++] ;
                System.out.println(j+"-"+i+":   "+(entry | a));
                A[j++] = entry | a;
            }
            System.out.println((int)'1');
            System.out.println( A['1'] & 0x1F);
            System.out.println((int)'1'  + ((A['1']  & 0x3E0) >> 5) & 0x1F);
        }
    

    相关文章

      网友评论

        本文标题:Integer包装类源码阅读

        本文链接:https://www.haomeiwen.com/subject/wzruqftx.html