基于jdk1.8的Java源码详解 Integer

public final class Integer extends Number implements Comparable<Integer>

Integer 由final修饰了,所以该类不能够被继承,同时 Integer 继承了Number类,因此可以将Integer转换成 int 、double、float、long、byte和short类型的数据,另外,也实现了comparable接口,因此Integer类也可以进行自然排序。

构造方法只有两个:

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

我们主要看第二个构造方法,传入一个字符串,然后调用parseInt方法,接下来进入parseInt的源码:

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的数值范围 -2的31次方到2的31次方减一
int limit = -Integer.MAX_VALUE;
//最小基数
int multmin;
//十进制数字
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
//第一个字符小于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++;
}
/**
* 最小基数,主要防止 result *= radix; 这个操作时数据过大
* 导致数据丢失的问题,因为所以带符号32位int类型整数为-2147483648~2147483647
*/
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
//转换十进制,这里获取的是radix进制下相对应的10进制数字,如:
//Character.digit('a',16),则返回 10;
//若输入字符不在进制的范围之内,则返回 -1:
//Character.digit('t',16),返回 -1,Character.digit('a',10),返回 -1
digit = Character.digit(s.charAt(i++),radix);
//返回-1说明字符非法
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;
}

这个方法中最核心的步骤是1、result *= radix; 2、result -= digit; 经过这两个步骤将字符串转换成数值类型。大概流程是这样的:

  1. 假如字符串"1234" 转换成int类型,result 的初始值为0,radix默认为10;
  2. 首先截取字符串的第一个字符1(这里忽略各种检查),经过第一步计算 result = 0*10 = 0;第二部计算 result = 0 - 1 = -1;
  3. 第一遍循环结束后,result 的值 变成了 -1
  4. 截取第二个字符 2 ,result = -1 * 10 = -10,result = -10 - 2 = -12;
  5. 截取第三个字符 3 ,result = -12 * 10 = -120,result = -120 - 3 = -123;
  6. 截取第四个字符 4 ,result = -123 * 10 = -1230 ,result = -1230-4 = -1234;
  7. 循环结束,此时result的值为 -1234,完成字符串向整数型的转换,返回是取反即可。

面我将从一个面试题引出问题,然后通过阅读源码来解决这个问题。

public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
Integer i5 = Integer.valueOf(100);
Integer i6 = Integer.valueOf(100);
Integer i7 = new Integer(100);
System.out.println("i1 == i2 的结果是:" + (i1 == i2));
System.out.println("i3 == i4 的结果是:" + (i3 == i4));
System.out.println("i5 == i6 的结果是:" + (i5 == i6));
System.out.println("i1 == i5 的结果是:" + (i1 == i5));
System.out.println("i1 == i7 的结果是:" + (i1 == i7));
}

运行结果为:

i1 == i2 的结果是:true
i3 == i4 的结果是:false
i5 == i6 的结果是:true
i1 == i5 的结果是:true
i1 == i7 的结果是:false

我们先来看第一和第二条结果,同样是比较两个相同数值,为什么会有不同的结果呢?接下我将通过源码来解释原因。

首先,我们通过编译获取到class文件的字节码

从图中我们可以看到,在执行 Integer i1 = 100 这条命令的时候,编译器会调用Integer中的静态方法 valueOf,接下来我们看看 valueOf方法是怎么实现的吧。

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

这个代码看起来很简单,Integer 中有一个静态内部类 IntegerCache,调用该方法时首先会判断该值是否在缓存的范围内,如果在则直接将缓存中的数值返回,否则返回一个新对象。看到这里我们似乎已经知道了上面的问题的答案了,接下来继续看静态内部类吧

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
//缓存范围最小(也是默认范围)为 (-128)~ 127,如果配置java.lang.Integer.IntegerCache.high
//high 的值可从配置文件中读取
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//获取配置文件和127之间的最大值
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;
//将数字缓存起来默认 -128 ~ 127
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() {}
}

我们知道内部类只有在所在类实例化时才会被实例化,而且只会实例化一次,缓存操作是在静态代码块中完成,也就是说在类被实例化的时候数据就已经被缓存好了,接下使用的时候可以直接使用缓存的数据。

现在我们回归到上面的问题,结果1中两个数据均为 100,在缓存的范围中,因此i1和i2都指向的是同一个内存地址,因此返回true。结果2中 两个数都是200,超出了缓存的范围,所以直接new 出了两个对象,因此他们的内存地址不一致,返回结果为false;另外,使用valueOf 和 使用 = 操作符赋值时一样的,所以结果3和结果4返回结果为 true,结果5中 是直接使用new关键字创建对象,所以他们的内存地址肯定不一致,结果为false。

那么,现在问题又来了,那我怎么判断两个整数的大小呢?继续看源码

/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int intValue() {
return value;
}

是的,没错,比较两个数值大小时可以使用equals方法来比较,源码中value的类型为 int型,intValue返回的也是value,因此可以判断两个数的大小。

public static void main(String[] args) {
Integer i1 = 200;
Integer i2 = 200;
System.out.println("i1 == i2 的结果是:" + i1.equals(i2)); //true
}

补充:equals 与 == 的区别:

equals 比较的是两个数值的大小,== 有两种情况,如果比较的是 基本数据类型,则 == 跟equals一样都是比较的大小,如果是引用类型或数组,则比较是内存地址。

getChars方法:

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

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

// Generate two digits per iteration
//每次循环获取后两位数
while (i >= 65536) {
q = i / 100;
// really: r = i - (q * 100);
//使用位移运算的效率高于乘法运算,r为后两位数
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 (;;) {
//相当于i*(52429/524288)=i*0.10000038146972656=i*0.1=i/10
//这里选 52429 和 2的19次方相除,得到的结果精度更加高,更加接近于 i/10的结果
//之所以要这样转换,是因为在计算机运算中位移的效率 > 乘法效率 > 除法效率
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;
}
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java并发之AtomicInteger源码分析

    问题 (1)什么是原子操作? (2)原子操作和数据库的ACID有啥关系? (3)AtomicInteger是怎么实现原子操作的? (4)AtomicInteger是有什么缺点? 简介 AtomicInteger是java并发包下面提供的原子类,主要操作的是int类型的整型,通过调用底层Unsafe的CAS等方法实现原子操作. 还记得Unsafe吗?点击链接直达[java Unsafe详细解析] 原子操作 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何

  • Java实现简易版联网坦克对战小游戏(附源码)

    介绍 通过本项目能够更直观地理解应用层和运输层网络协议, 以及继承封装多态的运用. 网络部分是本文叙述的重点, 你将看到如何使用Java建立TCP和UDP连接并交换报文, 你还将看到如何自己定义一个简单的应用层协议来让自己应用进行网络通信. 获取源码 (本地下载) 基础版本 游戏的原理, 图形界面(非重点) 多张图片快速连续地播放, 图片中的东西就能动起来形成视频, 对视频中动起来的东西进行操作就变成游戏了. 在一个坦克对战游戏中, 改变一辆坦克每一帧的位置, 当多帧连续播放的时候, 视觉上就有

  • java集合 ArrayDeque源码详细分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的两端都可以进出元素,故而得名双端队列. ArrayDeque是一种以数组方式实现的双端队列,它是非线程安全的. 继承体系 通过继承体系可以看,ArrayDeque实现了Deque接口,Deque接口继承自Queue接口,它是对Queue的一种增强. public interface Deque<E>

  • Java源码解析Integer方法解读

    toUnsignedString方法解读 看到Integer中有这样的一个方法把int转为Unsigned类型的字符串,但是有几个点不是很清楚,经过查询资料弄懂了,解读如下: /** * Convert the integer to an unsigned number. */ private static String toUnsignedString(int i, int shift) { char[] buf = new char[32]; int charPos = 32; int ra

  • 深入学习java ThreadLocal的源码知识

    简介 ThreadLocal是每个线程自己维护的一个存储对象的数据结构,线程间互不影响实现线程封闭.一般我们通过ThreadLocal对象的get/set方法存取对象. 源码分析 ThreadLocal的set方法源码如下 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 根据当前线程获得ThreadLocalMap对象 if (map != null)

  • 基于jdk1.8的Java源码详解 Integer

    public final class Integer extends Number implements Comparable<Integer> Integer 由final修饰了,所以该类不能够被继承,同时 Integer 继承了Number类,因此可以将Integer转换成 int .double.float.long.byte和short类型的数据,另外,也实现了comparable接口,因此Integer类也可以进行自然排序. 构造方法只有两个: public Integer(int

  • Java并发编程之ConcurrentLinkedQueue源码详解

    一.ConcurrentLinkedQueue介绍 并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式: 方式1:加锁,这种实现方式就是我们常说的阻塞队列. 方式2:使用循环CAS算法实现,这种方式实现队列称之为非阻塞队列. 从点到面, 下面我们来看下非阻塞队列经典实现类:ConcurrentLinkedQueue (JDK1.8版) ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全的队列.当我们添加一个元素的时候,它会添加到队列的尾部,当我们

  • Android实现屏幕锁定源码详解

    最近有朋友问屏幕锁定的问题,自己也在学习,网上找了下也没太详细的例子,看的资料书上也没有有关屏幕锁定程序的介绍,下个小决心,自己照着官方文档学习下,现在做好了,废话不多说,先发下截图,看下效果,需要注意的地方会加注释,有问题的朋友可以直接留言,我们共同学习交流,共同提高进步!直接看效果图: 一:未设置密码时进入系统设置的效果图如下: 二:设置密码方式预览: 三:密码解密效果图 四:九宫格解密时的效果图 下面来简单的看下源码吧,此处讲下,这个小DEMO也是临时学习下的,有讲的不明白的地方请朋友直接

  • Java8中AbstractExecutorService与FutureTask源码详解

    目录 前言 一.AbstractExecutorService 1.定义 2.submit 3.invokeAll 4.invokeAny 二.FutureTask 1.定义 2.构造方法 3.get 4.run/ runAndReset 5. cancel 三.ExecutorCompletionService 1.定义 2.submit 3.take/ poll 总结 前言 本篇博客重点讲解ThreadPoolExecutor的三个基础设施类AbstractExecutorService.F

  • python目标检测SSD算法预测部分源码详解

    目录 学习前言 什么是SSD算法 ssd_vgg_300主体的源码 学习前言 ……学习了很多有关目标检测的概念呀,咕噜咕噜,可是要怎么才能进行预测呢,我看了好久的SSD源码,将其中的预测部分提取了出来,训练部分我还没看懂 什么是SSD算法 SSD是一种非常优秀的one-stage方法,one-stage算法就是目标检测和分类是同时完成的,其主要思路是均匀地在图片的不同位置进行密集抽样,抽样时可以采用不同尺度和长宽比,然后利用CNN提取特征后直接进行分类与回归,整个过程只需要一步,所以其优势是速度

  • Django Rest Framework实现身份认证源码详解

    目录 一.Django框架 二.身份认证的两种实现方式: 三.身份认证源码解析流程 一.Django框架 Django确实是一个很强大,用起来很爽的一个框架,在Rest Framework中已经将身份认证全都封装好了,用的时候直接导入authentication.py这个模块就好了.这个模块中5个认证类.但是我们在开发中很少用自带的认证类,而是根据项目实际需要去自己实现认证类.下面是内置的认证类 BaseAuthentication(object):所有的认证相关的类都继承自这个类,我们写的认证

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

  • Android开发数据结构算法ArrayList源码详解

    目录 简介 ArrayList源码讲解 初始化 扩容 增加元素 一个元素 一堆元素 删除元素 一个元素 一堆元素 修改元素 查询元素 总结 ArrayList优点 ArrayList的缺点 简介 ArrayList是List接口的一个实现类,它是一个集合容器,我们通常会通过指定泛型来存储同一类数据,ArrayList默认容器大小为10,自身可以自动扩容,当容量不足时,扩大为原来的1.5倍,和上篇文章的Vector的最大区别应该就是线程安全了,ArrayList不能保证线程安全,但我们也可以通过其

  • Spring AOP底层源码详解

    ProxyFactory的工作原理 ProxyFactory是一个代理对象生产工厂,在生成代理对象之前需要对代理工厂进行配置.ProxyFactory在生成代理对象之前需要决定到底是使用JDK动态代理还是CGLIB技术. // config就是ProxyFactory对象 // optimize为true,或proxyTargetClass为true,或用户没有给ProxyFactory对象添加interface if (config.isOptimize() || config.isProxy

  • python目标检测SSD算法训练部分源码详解

    目录 学习前言 讲解构架 模型训练的流程 1.设置参数 2.读取数据集 3.建立ssd网络. 4.预处理数据集 5.框的编码 6.计算loss值 7.训练模型并保存 开始训练 学习前言 ……又看了很久的SSD算法,今天讲解一下训练部分的代码.预测部分的代码可以参照https://blog.csdn.net/weixin_44791964/article/details/102496765 讲解构架 本次教程的讲解主要是对训练部分的代码进行讲解,该部分讲解主要是对训练函数的执行过程与执行思路进行详

随机推荐