• [jdk源码阅读系列]overflow-conscious code


    本文部分转载,原文链接

    overflow-conscious code_lijianqingfeng的专栏-CSDN博客  https://blog.csdn.net/lijianqingfeng/article/details/107912190

    背景

    在jdk源码中,会有很多考虑了溢出而编写的代码,这些代码前会有注释:"overflow-conscious code",说明下面这段代码是考虑了溢出的情况的。最经典的代码就是ArrayList里的grow方法(因为网上能搜到好多对于这个方法进行讨论的文章和问题,可能大家都在研究ArrayList源码),我是看ByteArrayOutputStream源码的时候考虑这个问题的,但也都是grow方法,内容几乎一样。

    代码如下(ArrayList.grow):

        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
     
        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
     
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    分析:

    这里说考虑了溢出的情况,是如何考虑了溢出呢?考虑了哪个变量的溢出呢?在溢出的情况下是怎么应对的呢?

    小朋友瞬间出现了上面这些问号,于是开始探究。

    代码功能:

    我觉得首先要知道这段代码的目的是什么,才能知道它需要对什么变量做溢出管理。

    简单来说,这段代码的功能是对ArrayList的存储进行扩容,扩大为原来的1.5倍。那么在计算扩展后的容量时就有可能会溢出。

    另一个,传入的minCapacity其实是有上下文信息的,肯定是在一个限定范围内,不然需要考虑的兼容情况会更复杂。(当然也是这个给我的分析过程产生了最大的困扰)

    神奇的补码

    在分析代码之前,先需要知道一些补码的知识,溢出与它是息息相关的。本文不细说补码的知识了,网上很多文章介绍原码、反码、补码以及为什么计算机要选择补码。

    补码在表示有符号数的时候,最高位用来当做符号位,0代表正数,1代表负数。

    java中的int用了32位,最高位为符号位,所以表示范围是[-2^{31}, 2^{31}-1],最小值为0x80000000,最大值为0x7fffffff。最大值加1就会变成最小值。其实,int的这些数字看起来很像是一个圆环,如下图所示:

    从0开始,逆时针增大,到最大值的时候,再加1就变为最小值,然后再逆时针增大到0。

    考虑溢出的代码含义

    有了上面的知识,我们看一下代码中到底真正代表什么含义。

    这里有一个数学问题:a<b  和   a-b<0代表相同的含义吗?答案是:在计算机中不同,因为数字用的是有限位的补码,也正是因此才会有考虑溢出的代码。(Stack Overflow上有一个相关问题:https://stackoverflow.com/questions/33147339/difference-between-if-a-b-0-and-if-a-b

            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);

    这时候我们看上面的代码,这个已经不代表newCapacity大于MAX_ARRAY_SIZE了。那么有没有统一的说法能代表它的含义呢?不知道,但基于上面的圆环,我给它赋予了一个含义。

    基于圆环,在逆时针上假设有两个点A、B,如果A领先B不超过半个圆,那么A-B>0,否则A-B<0

    那么,newCapacity - MAX_ARRAY_SIZE > 0  也就是newCapacity 在图中的左侧半圆上。对于这部分数字(大部分是负数),程序会给其赋值为合理的数字(hugeCapacity(minCapacity)计算得出)。

    同理,下边的代码代表当newCapacity在minCapacity右侧的半圆上(如果minCapacity,也就是newCapacity小于minCapacity),为newCapacity赋值为minCapacity。

    额外信息

    基于上面两个if条件,我们不知道到底是在做什么。那么就需要结合上下文去进行考虑了,我想作者也并没有想着把grow方法写成一个完全common的方法,也是在ArrayList这个类的上下文中根据场景去设计的,而且尽量考虑了性能(不然不会写的这么复杂难懂)。

    额外信息1、newCapacity是oldCapacity扩大1.5倍,而oldCapacity原本是在合理范围内,也就是0到MAX_ARRAY_SIZE范围内。那么newCapacity要么是正常范围内,要么最大就是在MAX_ARRAY_SIZE的基础上乘以1.5倍后的越界值,那么就是最多超过MAX_ARRAY_SIZE 四分之一圆(严格来说不到四分之一,是MAX_ARRAY_SIZE/2)。这种情况下-1、-2……这种较大的负数是不会出现的。

    额外信息2、minCapacity是根据需要加入的元素计算出来的最小需要容量,这个值有可能本身溢出而成为负值。

    片面结论:

    正常情况下,就是1.5倍扩容,或者扩容为需要的大小。

    1.5倍扩容溢出时,就会扩容为需要的大小或者最大可扩容值。

    如果需要扩容的大小溢出,要么扩容为1.5倍,要么报错。

    遗留困惑

    minCapacity如果溢出,但是能满足newCapacity - minCapacity < 0,也就是newCapacity在minCapacity的右侧半圆,即便newCapacity是正常的,也不会扩容,而是报错;但是minCapacity溢出很严重,到了-1这种很大的值,newCapacity即便是正常的,也会不满足newCapacity - minCapacity < 0,这时候就会做1.5倍扩容。这种行为并不统一吧?

    原文链接:

    overflow-conscious code_lijianqingfeng的专栏-CSDN博客  https://blog.csdn.net/lijianqingfeng/article/details/107912190

    补充ArrayList扩容步骤

    我们知道,如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译时编译器不会出现任何错误提示,但会出现运行时异常:数组索引越界异常 java.lang.ArrayIndexOutOfBoundsException:N,异常消息后的N就是程序员试图访问的数组索引。

    所以,ArrayList里的elementData数组需要确保动态扩容,然后利用 type[] Arrays.copyOf(type[] original, int newLength)方法,这个方法将会把original数组复制成一个新数组,其中newLength是新数组的长度。如果newLength小于original数组的长度,则新数组就是原数组的前面newLength个元素;如果newLength大于original原数组的长度,则新数组的前面的元素就是原数组的所有元素,后面补充0(数值类型)、false(布尔类型)或者NULL(引用类型)。

    MAX_VALUE的值是2147483647

    ArrayList中可以分配的数组的最大值,即MAX_ARRAY_SIZE是2147483639

        
        public static final int   MAX_VALUE = 0x7fffffff;
    
        /**
         * The maximum size of array to allocate.
         * Some VMs reserve some header words in an array.
         * Attempts to allocate larger arrays may result in
         * OutOfMemoryError: Requested array size exceeds VM limit
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    当添加一个元素时,首先确保数组容量。当前元素个数size+1为必须的最小容量值minCapacity

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }

    如果数组elementData.length值比minCapacity小,意味着数组必须需要扩容。不然,就会发生数组索引越界异常。

        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }

    grow(int minCapacity)方法对数组进行扩容。

    对照源码分析,首先基于elementData数组当前的长度扩容1.5倍,变成newCapacity。

    然后判断newCapacity是否满足所必须的最小值minCapacity,如果还不满足,则直接newCapacity = minCapacity;这里更多情况下应该是满足的,通常情况下,一定是1.5倍扩容后的值大于最低必须要分配的值minCapacity!

    如果newCapacity的值大于MAX_ARRAY_SIZE,则意味着newCapacity已经超过了默认最大容量,还差8就发生int值溢出。所以,只有根据hugeCapacity(minCapacity)方法,根据最小值来扩容,而不是之前1.5倍的扩容大小。所以判断必须的最小值minCapacity是否溢出,如果溢出就抛出溢出。然后根据minCapacity的大小继续在最大边界的地方尽可能的扩容。

    源码如下:

        /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }

    附:关于int最大值最小值的溢出测试

        public void test2() {
            int max = Integer.MAX_VALUE;
            System.out.println(max);//2147483647
            max++;
            System.out.println(max);//-2147483648
            int min = Integer.MIN_VALUE;
            System.out.println(min);//-2147483648
    
            int a = 0x80000001;//根据 Integer.MIN_VALUE = 0x80000000
            System.out.println(a);//-2147483647
    
            int c = a - 1;
            System.out.println(c);//-2147483648
            c = a - 2;
            System.out.println(c);//2147483647
    
            if (-3 - 5 > 0)
                System.out.println("aaaa");
    
            if (-3 - (-8) > 0)
                System.out.println("BBBB");//BBBB
    
        }

    参考链接:

    数组索引越界异常 ArrayIndexOutOfBoundsException_鲜衣怒马楼兰月-CSDN博客  https://blog.csdn.net/loulanyue_/article/details/93669970

    java中Arrays类的讲解_zhouning的博客-CSDN博客  https://blog.csdn.net/qq_41474648/article/details/105182817

  • 相关阅读:
    [转] 面向对象软件开发和过程(六)针对契约设计
    [转] 面向对象软件开发和过程(三)案例实战(下)
    条形码字体下载
    [转] 面向对象软件开发和过程(五)优化代码的组织
    JQuery动画效果
    实时监听文本框状态的方法
    JQuery操作元素的属性与样式及位置
    AJAX XMLHttpRequest对象
    JQuery常用方法技巧
    CSS弹出二级菜单
  • 原文地址:https://www.cnblogs.com/tongongV/p/13906973.html
Copyright © 2020-2023  润新知