算法与数据结构面试题(23)-将字符串转化为整形

题目


输入一个表示整数的字符串,把该字符串转换成整数并输出。 例如输入字符串"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相减,但是相减的前提是它不能越界。所以有个预先判断。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值