You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
publicstaticintparseInt(Strings, intradix)
throwsNumberFormatException
{
/* * 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) {
thrownewNumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
thrownewNumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
thrownewNumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
intresult = 0;
booleannegative = false;
inti = 0, len = s.length();
intlimit = -Integer.MAX_VALUE;
intmultmin;
intdigit;
if (len > 0) {
charfirstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} elseif (firstChar != '+')
throwNumberFormatException.forInputString(s);
if (len == 1) // Cannot have lone "+" or "-"throwNumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUEdigit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throwNumberFormatException.forInputString(s);
}
if (result < multmin) {
throwNumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throwNumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throwNumberFormatException.forInputString(s);
}
returnnegative ? result : -result;
}
Uh oh!
There was an error while loading. Please reload this page.
Integer.parseInt
应该算是Java
中比较常见常用的方法了。本文准备在自己实现的基础之上,逐步分析Java
源码的实现。自己动手的第一版
字符串转数字,其原理其实并不复杂,基本上没有太多开发经验的同学也可以写出如下一个简单的实现
这是一版完全从数学原理角度的实现,基本上没有任何的优化,所以就不多说了。这里需要注意是
while
循环里的第一行,即字符到数字的转换,利用的是当前字符在ascii
码表中距离0
的距离来计算的,所以一个潜在的限制就是只能处理二、八、十进制。自己动手的第二版——不使用指数运算
在第一版中,有一个计算量比较大的地方,就是其中的指数运算,在第二版中,我们通过调整处理的方向,把指数运算优化为乘法运算
相比于第一版从低位开始处理,在第二版中,我们从高位开始处理,每次先乘以基数,相当于把
result
左移,然后在加上新增的低位数,这样就避免了开销比较大的指数运算。考虑高于十进制的情况
上面基本上完成了字符串转数字的核心模块,接下来就考虑遗留的进制限制的问题。首先分析这个问题的根源,是因为我们利用了
ascii
码表中各个数字距离0
的距离来将字符转为了整数,从如下ascii
码表中我们看出,字母距离0
之间的距离,是不能直接转换的,因为中间隔了 7 个其他字符,所以需要一些特殊处理。这里还有一个潜在的坑,就是字母在使用的之前需要转成大写,因为我们刚才的分析中,针对的是大写字母,小写字母的距离又是另一回事儿了,所以要全部转为大写再转换,新的代码如下
对输入的检查——进制不符
上面我们并没有对用户的输入做任何的检查,假设有如下用户输入,
我们应该是报错的,因为用户输入的进制基数和字符串是不符的,但是我们目前的程序却会输出
20
, 这显然是不对的,因为我们在程序内部并没有在处理的过程中检查每一个字符转换之后和进制基数的关系。改进版本如下对第一位可能的符号位的处理
到目前为止,我们都是把输入当作无符号的数也就是正数来处理的,这显然是有问题的,因为输入的第一位可能是一个符号位,不能直接当数值位来处理。所以要先判断第一位。
一些其他问题
到目前为止,一个基本可用的字符串转整型的方法就完成了,但是它还是不完美的,还有以下一些问题需要考虑:
2147483648
即越界数字的处理;为什么要特殊处理
2147483648
呢?因为它其实是越界了,超过了int
的最大值,而我们目前的程序 会返回-2147483648
。 针对这个情况,Java
源码(见后)的处理方式是先按照负数去处理,因为负数的最大值是-2147483648
,而正数最大值是2147483647
。对于其他更大的数字,则需要在转换的过程中逐步判断,发现大于int
的最大值就抛出异常。我们目前的思考,都是针对输入的字符进行的,但是因为输入的进制基数
radix
也是用户输入的,也是需要进行判断处理的,不能默认一定是正确的。比如因为英文字母只有 26 个,所以我们这里的最多也能处理到 36 进制,所以就不允许输入大于这个值的进制基数了。我们上面的算法中,利用了字符在
ascii
码表中与0
的距离来进行转换,但是这种转换缺乏对待转换字符的校验,所以会出现以下问题:这个问题的出现是因为
:
应该是一个非法的字符,而我们却把它当作一个正常的输入用与0
的距离来进行转换了,所以这里需要对输入的每一个字符都判断。Java 源码关于 Integer.parseInt 的实现
把上面的问题全部考虑到,基本上就是
Java
的源码的实现了,所以我就不在继续myParseInt
了,这里就直接祭出Java
的源码了源码的部分分析
上面的源码中,大部分逻辑都是比较简单易懂的,比较核心的字符转数字,也是在其他类(
digit = Character.digit(s.charAt(i++),radix);
)中实现的。但是其中关于越界问题的检测逻辑我当初就没有一眼看明白。其中的
limit
就是最大值,之前我一直没搞明白multmin
是做什么的,直到写这个博客,才明白过来,这是用来检测要转换的字符串是否越界用的。准确的说,上面我截取的这部分代码,都是做这个事儿的。那么为什么要这么做呢?这其实是必须的,因为result
虽然在上一次的转换中没有越界,但是很有可能在本次的转换中因为与radix
做乘积而越界,所以这里提前判断一下它和multmin
的大小,后面的判断也是同理。这里需要注意的是Java
的实现中是先按照负数来处理的,所以这里的不等式的方向与处理正数恰好是不一样的。参考资料
https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-int-
https://stackoverflow.com/questions/40167218/why-does-the-integer-paseint-algorithm-calculate-negative-result-finally-in-su
https://stackoverflow.com/questions/1410555/how-does-integer-parseintstring-actually-work
The text was updated successfully, but these errors were encountered: