天天看点

JDK源码分析之Integer

常用数据类型大家都会用,JDK也为了保持一切皆对象的原则,将8大常用数据类型都封装成了对应的对象,本次先来分析JDK怎么封装的Integer。

首先看Integer的继承关系

是继承Number实现Comparable的,所以Integer也能用compareTo方法进行比较。

Integer 有一个私有属性 value 

/**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;
           

可以看出这个value是一个final型的,这也解释了为什么我们new 两个同样数字的Integer对象为什么会不是一个对象,当然,也有特殊情况,==比较时会出现相同对象,这是因为Integer类内会给缓存一部分数据,这在下面再具体分析。

接着我们看Integer的构造方法:

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

可以发现这两个构造方法是比较简单的,第一个是调用的parseInt方法转化成了Integer对象,关于parseInt方法稍会介绍。

下面分析下Integer是怎么缓存一部分数据的,这就要归功于Integer内部类IntegerCache了。

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

        static {
            // 默认缓存的最大值是127
            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);  //取配置的和127的大值
                    // 
                    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的数据
                cache[k] = new Integer(j++);

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

        private IntegerCache() {}
    }
           

代码里面有注释,还是比较好理解的,分析==比较之前先说下,自动拆箱自动装箱。比如Integer i= 23;JDK会自动将23给我们装箱成Integer对象,当然这是JDK5之后才有的,debug可以看到JDK给我们装箱时是调用的valueOf方法。下面看下这个方法。

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

这个方法可以很好理解,JDK在装箱前会先判断i是否在缓存cache中,如果在的话就直接拿缓存中的数据,如果不在的话才new一个对象。所以当我们这样写Integer i = 23;

Integer m = 23;  i== m ,会返回TRUE,这个才是根本原因。

下面说下parseInt(String str);这个方法

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

发现调用的是parseInt(String s,int radix);这个方法

public static int parseInt(String s, int radix) throws NumberFormatException{
        //对输入的字符进行判断非空或者是否大于最大或者小于最小,如果是的话,抛异常
        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;
        int multmin;
        int digit;

        if (len > 0) {
            char firstChar = s.charAt(0);
            if (firstChar < '0') { // 如果firstChar < '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) {
                // 这个方法可以先简单的理解成将string型的转换成int型的稍后介绍这个方法
                digit = Character.digit(s.charAt(i++),radix);
                if (digit < 0) {
                    throw NumberFormatException.forInputString(s);
                }
                if (result < multmin) {
                    throw NumberFormatException.forInputString(s);
                }
                result *= radix; //先乘一个radix
                if (result < limit + digit) {
                    throw NumberFormatException.forInputString(s);
                }
                result -= digit;  //再减一个digit得到对应基数的数字
            }
        } else {
            throw NumberFormatException.forInputString(s);
        }
        return negative ? result : -result;  //最终result是一个相对应基数的int 型的数
    }
           

发现上个方法主要是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);方法
 }
           
public static int digit(int codePoint, int radix) {
        return CharacterData.of(codePoint).digit(codePoint, radix);//调用CharacterData.of(int codePoint).digjt(int codePoint,int radix);
}
           

我们查看下CharacterData的of方法:

static final CharacterData of(int ch) {
        if (ch >>> 8 == 0) {     // 逻辑右移,根据值的大小选择不同的实例此处我们看下第一个
            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.java部分源码

int digit(int ch, int radix) {
        int value = -1;
        if (radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX) {  //判断基数是否在最大和最小范围内
            int val = getProperties(ch);  //调用此方法 此方法是得到字符的ASCII值
            int kind = val & 0x1F;  //value & 11111  这里与过后只可能会是后面的几种  2: 小写字符 1:大写字符 9:数字 20~30 标点 12空格
            if (kind == Character.DECIMAL_DIGIT_NUMBER) {  //kind == 9  数字
                value = ch + ((val & 0x3E0) >> 5) & 0x1F;  //value = ch + ((val & 1111100000) >> 5 ) & 11111  关于这一块,本人也是正在学习中,目前没太明白
            }
            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];  //将A数组进行
        return props;
}
           

截止到现在parseInt(String s);方法应该是完了。感觉JDK封装的挺厉害的,还是应该看源码学习比较好。下面看Integer.toString(int i);方法。

public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }
           

此处判断数字的size时感觉非常好。我们看下stringSize的方法。直接就可以得到int类型的数有几位

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

    // Requires positive x
    static int stringSize(int x) {
        for (int i=0; ; i++)
            if (x <= sizeTable[i])
                return i+1;
    }
           
static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

        // 每次生成两个
        while (i >= 65536) {  
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));
            i = q;
            buf [--charPos] = DigitOnes[r];
            buf [--charPos] = DigitTens[r];
        }

        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        for (;;) {
            q = (i * 52429) >>> (16+3); //这里用52429 好像是因为这个数进行乘除精确度最高
            r = i - ((q << 3) + (q << 1));  // r = i-(q*10) ...
            buf [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
            buf [--charPos] = sign;
        }
    }
           

今天就先到这里吧!!!有时间继续。。。