题目
输入一个表示整数的字符串,把该字符串转换成整数并输出。 例如输入字符串"345",则输出整数 345。
个人理解
这个面试题不太 涉及算法和数据结构,主要考察你的编程习惯,有没有意识去做一些验证,对输入的校验。小问题验证大方面。乘机我们来看看java api中对这个问题的处理
代码
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
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;
}
先了解下面的知识,对理解上面的代码有好处。
在Java中>、>>、>>>三者的区别
在java中:
>表示大于,如:if(a>b)...结果是boolean类型
>>表示右移,如:int i=15; i>>2的结果是3,移出的部分将被抛弃。
转为二进制的形式可能更好理解,0000 1111(15)右移2位的结果是0000 0011(3),0001 1010(18)右移3位的结果是0000 0011(3)。
j>>>i 与 j/(int)(Math.pow(2,i))的结果相同,其中i和j是整形。
我们一步一步的debug, 来看看处理代码
1.进入
public static void main(String[] args){
//10进制
Integer.parseInt("235",10);
}
上面是入口函数。
传入的字符串为"235"。进入方法体后,第一步是判断是否为null。如果为空,抛出异常。然后是判断基数是否在可允许范围内[2-32],我们传入的是10。在可允许内
2.初始化操作
result:我们需要的结果
negative:是否为负数。默认为false
i:游标,指向字符串当前的索引,遍历字符串中字符。
len:字符串大小
limit:整形的最大值的负值。比整形的最小值-2147483648大1.
multmin:
digit:保存字符经过转化后的整形值
3.判断字符串第一个字符是否是符号位(+,-)
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++;
}
如果第一个字符为'-',将negative的值设置为true,将limit设置为整形最小值:Integer.MIN_VALUE。
如果第一个字符不为‘-’,然后再附加判断一下是否为加号,如果不是加号,抛出异常
再然后判断一下字符串的大小是否为1,如果为1,不符合要求,因为只有符号位肯定是不复制整数的定义的。
最后字符串遍历的游标自增1。表示遍历要从第二个字符开始,因为第一个字符我们已经判断是符号位。
4.遍历字符串
首先我们将multmin的值赋上值,赋上值有啥作用?,它为啥要除以基准值。暂时还不清楚。先放着吧。
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;
}
首先了解第一个语句块
digit = Character.digit(s.charAt(i++),radix);
可以看见字符'2'的ascii码为50,我们对照一下的确是50。
上面的方法中,首先将字符转换成ascii码,然后再调用覆盖函数digit。
这个时候会发现传进来的是50,也就是‘2’的ascii码值。进入该函数后,首先通过CharacterData.of方法来得到该字符属于哪个编码集范围内。
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;
}
}
}
首先ch>>>8位运算。意思是ch/2的8次方法。主要是判断是否可以用拉丁字符范围内的值表示。很显然我们可以用拉丁字符编码集中字符范围来表示。所以方法返回。
进入CharacterDataLatin1类的digit方法
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;
}
首先判断基准值是不是符合要求,这个已经比对过了,无需再说了。然后进入getProperties方法。
int getProperties(int ch) {
char offset = (char)ch;
int props = A[offset];
return props;
}
返回了一个很大的数,为unicode值;暂且理解为unicode码转化为10进制后的数,然后与16进制数0x1F做 位与运算。
为何要做位与运算,先来看看位于运算是什么?
& 是按二进制的按位与,即 1 & 1 = 1 1 & 0 = 0 3 & 1 → 11(二进制) & 1 = 1
上面的0x1F表示的是二进制数为:00011111。如果按照上面的运算法则,那么只有后5位为1的位才能被保留下来。
10进制数402667017转化为二进制数为
11000000000000011011000001001
那么后5位为01001&11111 = 01001;所以我们知道其实就是取后5位的数二进制码,其他位置全部改为0。得到的10进制数就是9。
if (kind == Character.DECIMAL_DIGIT_NUMBER)
根据这句代码我们知道,和0x1F位于是要计算出其所处的类别。9代表的是Unicode 规范中的常规类别“Nd”。然后再做如下的位运算
value = ch + ((val & 0x3E0) >> 5) & 0x1F;
0x3E0 = 10 1111 0000 &10 0000 1001 = 10 0000 0000 然后向右位移5位得到10 000由于优先级+大于&。所以先进行相加运算50+16 = 66 (1000010)
1000010&11111 = 10 =,10进制就为2;所以最后的value = 2;
所以方法返回到Integer.parseInt中,得到digit = 2;
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;
}
然后是2个if判断。很容易理解:不能为负数,因为此时计算的位数没有符号可言。
后面一个判断result<multmin是什么意思呢。因为result即将*10。如果result<multmin,那么再*10的话肯定超出最小的范围。这个地方为什么不能直接先乘后比较,先乘后比较就没有意义了,如果超出了范围,已经报错了,后面的判断也就不会执行。接下来的一句的判断同样是这个道理,因为result即将与digit相减,但是相减的前提是它不能越界。所以有个预先判断。