您的位置:首页 > 编程语言 > Java开发

[置顶] JAVA源码阅读——–Integer类

2018-01-19 00:37 381 查看

JAVA源码阅读——–Integer类

类定义

Integer类被final关键词修饰,不可被继承

Integer类继承Number类并实现其方法,可返回其他基本类型数据,例如longValue()返回long类型数据

Integer类实现了comparable接口,可以利用CompareTo方法与其他Integer对象比较

私有属性

用于存储Integer的实际值
private final int value;
用于Integer的序列化和反序列化
Native private static final long serialVersionUID = 1360826667806852920L;


公有属性

最小值
Native public static final int   MIN_VALUE = 0x80000000;
最大值
@Native public static final int   MAX_VALUE = 0x7fffffff;

Integer类表示的原始类型  int
public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

Integer表示数使用的二进制位数目
@Native public static final int SIZE = 32;

Integer表示数占多少字节
public static final int BYTES = SIZE / Byte.SIZE;


计算机中表达带符号的数字时使用最高位表示符号,0是正数,1是负数。Integer使用32bit来表示数。

正数表示范围:
0000 0000 0000 0000 0000 0000 0000 0000
0111 1111 1111 1111 1111 1111 1111 1111
既是0~2^31-1

负数表示范围:
1000 0000 0000 0000 0000 0000 0000 0001
~
1111 1111 1111 1111 1111 1111 1111 1111
即是-2^31-1~-1

1000 0000 0000 0000 0000 0000 0000 0000
较为特殊,-0既是最小值2^31。由于负数采用补码存储,在此不多做解释。

构造方法

构造一个Integer对象,并指定值为value
public Integer(int value) {
this.value = value;
}

构造一个Integer对象,并将字符串s尝试装换为数,否则抛出NumberFormatException异常,创建对象失败
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}


Integer缓存池

Integer缓存池
缓存-128到127间的数,包括-128个127。缓存在第一次使用时被初始化,可以通过设置-XX:AutoBoxCacheMax=<size>
选项改变Integer缓存范围
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
//获取配置java.lang.Integer.IntegerCache.high的值,如果为null
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//保证最小的缓存数量是-128-127
i = Math.max(i, 127);
//防止缓存范围超过int的最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
//如果转换虚拟机参数失败,则忽略该值,使用默认范围-128-127
}
}
high = h;
//初始化缓存池并创建Integer对象放入缓存池
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() {}
}


default方法

/***
*该方法用于将一个无符号整数转换为2进制或8进制或16进制的字符序列,转换的方式就像我们在学习进制转换时学的:
*2进制转为8进制,3位一换;2进制转换为16进制,4为一换
*例如我有一个无符号整形数  18  其二进制表示是 10010
*那么转换为8进制,从后往前,3位为一组将其转换为相应进制  10010应该这样划分 10,010  那么就是22,22就是18的8进制表示
*同样的转换为16进制,10010划分为 1,0010 每组分别转换为16进制即是 12
*java的Integer里面的formartUnsignedInt也是利用了这样的思想
*1.计算radix,简单来说就是进制 。wiki:https://en.wikipedia.org/wiki/Radix
*2.计算掩码,这个码计算下来就是一堆连续的1,掩码的作用就是为了计算数值,例如我们10010转换为16进制的时候,我们
*要转换的进制是16进制,那么radix=16,我们的掩码就是15,二进制表示为1111,18我们划分以后应该是1,0010。
*我们计算0010的值是多少其实就是用10010 & 1111 = 0010 = 2,相当于屏蔽我前面的1,只是计算后面的0010就是这个样子的。
*3. 进行转换。计算开始是从数的最后一位开始,我们考虑的时候都直接转换为2进制思考会好一些。
*算法计算过程是每次计算shift位与mask相于 然后得到一个10进制的结果从字典表中取出对应字符,这个shift是偏移量,
*取值有1,3,4,也就是对应我说的3位划分,4位划分,1位划分就是2进制嘛!
*然后每次计算完毕后不断的将我们要转换的数(val)移动shfit位,相当于要去计算下一组划分,
*直到val变为0或者charPos
*
*/

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;
}

/**
*返回数x所在的范围大小
*/
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;
}


私有方法

/**
* 将整数转换为无符号数
* 1.integer最大位数为32位,计算val最左边的1到最右边的长度,也就是计算数val的二进制实际长度,例如18二进制是
* 10010,实际就是5位,而不是32位长度,32位长度指的是存储范围
* 2.计算将val转换为2,8或16进制的字符序列长度,例如18转换为16进制后为12,需要2位的字符存储空间
* 3.调用formatUnsignedInt将数val转换为字符序列
*/
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);
}


公有方法

这下面几个函数都是调用了toUnsignedString0进行求解,没啥讲的
public static String toString(int i, int radix);
public static String toUnsignedString(int i, int radix);
public static String toHexString(int i);
public static String toOctalString(int i);
public static String toBinaryString(int i);


/**

*getChars方法呢是主要将数i转换为一个字符串的方法,该方法有三部分组成

* 第二部分是在数i>65535的时候每次取数i的后两位转换为字符

* 第三部分是在数i<65535时每次取一位转换为字符串

*/

static void getChars(int i, int index, char[] buf) {
int q, r;
int charPos = index;
char sign = 0;

if (i < 0) {
sign = '-';
i = -i;
}
//代码段2
// Generate two digits per iteration
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];
}
//代码段3
// Fall thru to fast mode for smaller numbers
// assert(i <= 65536, i);
for (;;) {
q = (i * 52429) >>> (16+3);
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;
}
}


看上面的代码会出现一些不明白的问题,

问题1:为什么是65535呢?别的数不行么?

问题2:代码段3中的q = (i * 52429) >>> (16+3);

我开始看代码的时候也是一头雾水,直到看到这篇文章有了眉目:https://www.cnblogs.com/vinozly/p/5173477.html

首先需要明确

1.移位效率高于乘除

2.乘法效率高于除法

第三部分中,i >>> n 相当于 i/2^n,2^19=524288,(i*52429)/524288 相当于i*0.1 = i / 10。如何确定65535,52429,16+3这三个数呢,资料中的作者给出了解答。我们设第一个数标号为num1(已确定值为65535),第二个数为num2(已确定为52429),第三个数为num3(已确定为19)。

根据 q = ( i * num2 )/num3=i*0.1 也就意味着 num2/2^num3=0.1 => num2=2^num3/10,最终是num2=2^num3+1,

其加一的目的我猜测是为了调整计算机中整数除法会舍去小数部分向下取整导致误差。根据对num3的取值确定了多组num2和num3

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 结果直接从作者文中取的。

其次 (i*num2) >>> num3 必须保证数据的不溢出,i和num2的乘积有一定的范围,i< num1,所以num1与num2有一定的制约关系。

作者认为取65535,52429,16+3的原因在于:

1.52429/524288=0.10000038146972656精度足够高。

2.下一个精度较高的num2和num3的组合是419431和22。2^31/2^22 = 2^9 = 512。512这个数字实在是太小了。65536正好是2^16,一个整数占4个字节。65536正好占了2个字节,选定这样一个数字有利于CPU访问数据。

/**
* 将radix进制的字符串转换为int
* 总之呢就是  判断每一位无异常后执行 result = result * radix + i,i就是该某一位字符转换为数的结果
*/
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
//前面做一些关于进制的有效性判断
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') { // 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++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}

/**
* 获取数val在二进制下高位到第一个1之间有几个零
*
* 看代码很明显就是位运算,左移过来右移过去,但是实际上是怎么判断的呢?我找一个数进行说明
* 例如一个数其二进制表示为0000 0000 0000 0000 0000 0000 0000 00001
* 第一步运算先往右移动16位,结果为0,也就检测完(16-32]之间不存在1,将数左移16位检测[1-16]区间0的个数
* i变为  0000 0000 0000 0001 0000 0000 0000 0000
* 第二步运算右移24位,结果为0,说明(8-16]区间不存在1,这次继续将i左移8位,检测[1-8]区间的0的个数
*  i变成 0000 0001 0000 0000 0000 0000 0000 0000
* 第三步运算右移28位,结果为0,说明(4-8]区间不存在1,继续左移4位,检测[1-4]区间个数
* i变为 0001 0000 0000 0000 0000 0000 0000 00000
* 第四步运算右移30,结果为0,说明(2-4]区间不存在1,继续左移2位,检查[1-2]区间
* 第5步只需要判断第31位是否为0,结果就呼之欲出。如果为0,说明第32位必然是1,因为特殊条件已经排除
* (第32位为0的情况也就是i==0),如果为1,也就是说这个数i最后判断下来是不存在最左边的零的,即(n=1) n-=1
* 结果为0
* 我们考虑这样做的好处是什么?判断一个数最左边有多少个0的最简单算法就是从最左边往右一位一位的检测过去,一旦
* 遇到1便结束。这样的时间复杂度最坏是O(32) 也就是需要32次判断,但是java源码中的方法只需要1次特殊判断和
* 5次移位判断,总的为6次。再考虑平时使用数的大小,一般都不会太大,所以JAVA的判断左端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的个数
* 思想和着上面的一样,不过这次就是反着来。先判断[1-16)区间的,然后是[16-24),[24,28),[28,30),[30-31]
* /

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);
}

/**
* 求32位二进制数中1的个数
* 0x55555555 = 0b0101 0101 0101 0101 0101 0101 0101 0101
* 0x33333333 = 0b0011 0011 0011 0011 0011 0011 0011 0011
* 0x0f0f0f0f = 0b0000 1111 0000 1111 0000 1111 0000 1111
*
* 解析请参考http://blog.csdn.net/cor_twi/article/details/53720640
* 该方法是一个2分的思想,32的拆解为两个子问题,前16位中1的个数有多少,后16位中1的个数有多少个
* 然后子问题再次分解为4个子问题,8位中含有多少个1,直到拆解到2个中有多少个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;
}

/**
*循环左移
* i = 1100 0000 0000 0000 0000 0000 0000 1100
* distance = 1
* 变换结果
* 1000 0000 0000 0000 0000 0000 0000 0001 1001
* i<< distance 左移好理解,但是关于无符号右移且为负数位呢?
* 结果就是会将高位的部分的distance位放到低位的distance位
* 假设某种存储类型字长8bit,1100 0000 >>> -2 = 0000 0011
*只要将左移结果和无符号右移负数位的结果相或就可以得到循环左移的结果
*/

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

/**
*循环右移
* << -distance位会将低位的distance位放到高位,与>>> -distance位相反。
* 关于 >> -distance位我测试以后的结果是无论 i是多少都会变成全1
*
*/
public static int rotateRight(int i,int distance){
return ( i>>>distance | i << -distance );
}

/**
* bit 位反转
* 我先说下我的理解 bit位反转就是对称交换,找到一个中心轴,把对应的位交换。一定的是中心轴左边的会置换到中心轴右边
* 中心轴右边的也会交换到中心轴左边
* 假设一个长度为8的序列B,序列应该是B[1...8],序列存储值为A[1],A[2],A[3],A[4],A[5],A[6],A[7],A[8]
* 反转的关系应该是f(i,n)=swap(B[i],B[n-i+1])(i< = n/2 ,n>0),我们的
* 目标序列应该变成 A[8],A[7],A[6],A[5],A[4],A[3],A[2],A[1]
* 如果我们先将中心轴两边的数据交换 swap(B[1..4],B[5..6]) ,
* 序列变为 A[5],A[6],A[7],A[8],A[1],A[2],A[3],A[4]
* 我们看对比目标和现在的bit序列,应该是swap(B[1],B[4]),swap(B[2],B[3])左边才能够达到目标
* swap(B[5],B[8]),swap(B[6],B[7])右边才能到达目标序列,对于目前的序列,左右的操作都是一样的,
* 都是swap(B[i],B[n-i+1]),我们似乎有可以照着8位的做法,
* 先将中心轴左右两边的区别B[5..8],B[1,4]在分别划出新的中心轴然后交换,序列将会变为
* A[7],A[8],A[5],A[6],A[3],A[4],A[1],A[2],然后剩下只需要swap(B[1],B[2]),swap(B[3],B[4])..
* swap(B[7],B[8])就能达到目标序列
* 其实整套做法就是分治
* reverse方法呢就是反着计算回去,先两位交换,然后四位中前后交换,8位前后交换,最后16位交换就可以达到效果
* *
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;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}

/**
* 返回数i是正数还是负数
* 正数返回1,0返回0,负数返回-1
* 负数可以通过符号位轻松的和正数与0区分开,但是0和正数符号为都是0,如何区分呢?
* java 源码采取的措施是将i取负数,如果i>0呢,符号位将会变为1,而i=0,符号为0,i<0,符号会变为0,由此可以判断正数和0
*/

/*
* 按字节翻转
* 例如   0xaabbccdd 翻转后结果为 0xddccbbaa
* 这个就不解释了,反正看得懂
*/
public static int signum(int i) {
// HD, Section 2-7
return (i >> 31) | (-i >>> 31);
}


还有部分源码没有加入,原因就是那部分代码直接调用了上述的代码,只是参数有变化。Integer类基本的都在上面了

第一次写源码分析,没有去考虑效率问题,以后会慢慢改进写作的方式以及描述

参考资料:

https://www.cnblogs.com/vinozly/p/5173477.html

http://blog.csdn.net/cor_twi/article/details/53720640
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: