CVTE/顺丰/依图科技/去哪面试真题

​​​​​

  1. 说一下单例模式的各种写法,手写一种线程安全的

  2. 第一种(懒汉,线程不安全):

     

    Java代码  收藏代码

  3. public class Singleton {  
  4.     private static Singleton instance;  
  5.     private Singleton (){}  
  6.   
  7.     public static Singleton getInstance() {  
  8.     if (instance == null) {  
  9.         instance = new Singleton();  
  10.     }  
  11.     return instance;  
  12.     }  
  13. }  
  14.  

     这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。

    第二种(懒汉,线程安全):

     

    Java代码  收藏代码

  15. public class Singleton {  
  16.     private static Singleton instance;  
  17.     private Singleton (){}  
  18.     public static synchronized Singleton getInstance() {  
  19.     if (instance == null) {  
  20.  

     这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

    第三种(饿汉):

     

    Java代码  收藏代码

  21. public class Singleton {  
  22.     private static Singleton instance = new Singleton();  
  23.     private Singleton (){}  
  24.     public static Singleton getInstance() {  
  25.     return instance;  
  26.     }  
  27. }  
  28.  

     这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

    第四种(饿汉,变种):

     

    Java代码  收藏代码

  29. public class Singleton {  
  30.     private Singleton instance = null;  
  31.     static {  
  32.     instance = new Singleton();  
  33.     }  
  34.     private Singleton (){}  
  35.     public static Singleton getInstance() {  
  36.     return this.instance;  
  37.     }  
  38. }  
  39.  

     表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。

  40.         instance = new Singleton();  
  41.     }  
  42.     return instance;  
  43.     }  
  44. }  
  45. ArrayList如何扩容?(常考)

  46. ArrayList是一个可动态添加删除数据的集合,底层数据结构是数组。当添加数据的容量大于底层数组容量时则会产出扩容,即通过生成数组来实现。它的主要核心就是扩容机制(当插入时所需要的长度超过数组原本的长度时则需要扩容)。本文主要抓ArrayList的重点分析。

    接下来抱着这几个问题来分析一下ArrayList的源码

    1.ArrayList怎么判断是否需要扩容的?

    2.ArrayList是怎么扩容的,怎么操作数组实现的?

    3.ArrayList在扩容做了什么优化?

    4.ArrayList为什么是线程不安全的?

    5.ArrayList为什么增删慢(源码角度看)?

    6.ArrayList的缩容机制是怎样的?

    ArrayList类与成员变量:
    //继承自AbstractList
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
     
    //默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
     
    //默认元素集合
    private static final Object[] EMPTY_ELEMENTDATA = {};
     
    //无参构造实例默认元素集合
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
     
    //存放元素的数组
    transient Object[] elementData;
     
    //记录ArrayList中存储的元素的个数
    private int size;
    构造函数:
    //可指定大小的构造函数
    public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
     
    //默认构造函数
    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
     
    //传入一个为Collection的对象
    public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    三个构造函数目的都是为初始化elementData,一般我们使用中间默认的即可。如需使用其它的可按情况使用,比如当我们知道需要使用的容量那就用第一个指定容量大小,避免频繁扩容。

    插入add():
    //从尾部插入数据
    public boolean add(E e) {
            //判断是否扩容进行扩容
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //在数组后面添加元素e
            elementData[size++] = e;
            return true;
        }
     
    ensureCapacityInternal方法为了判断是否扩容,跟进去

    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);
        }
    这里我们就可以解决上面说的第1个问题了。

    根据所需容量是否大于现数组容量来进行扩容,调用grow方法,继续跟进去

    grow方法是ArrayList扩容的核心方法:
    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            
            //增加1.5倍的长度
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            
            //当增加1.5倍后还是不够所需要长度,则直接用所需长度来扩容
            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);
        }
    通过oldCapacity + (oldCapacity >> 1)(右移左移运算的思路:先转二进制计算后再转回十进制)来实现扩容1.5倍的长度,再通过Arrays.copyOf实现扩容,这里就解决了第2个问题。

    第3个问题的解决也在这里,我们可以看到,它是先扩容1.5倍,当增加后还是不够用,则直接使用所需要的长度作为数组的长度。为什么呢?这里ArrayList是做了优化,它先扩1.5倍,如果这次够用下次再来扩容可能就可以不用扩容。如果每次都用所需要的长度来扩容,那么以后每次增加元素都会进行一次扩容操作,当增加后还是不够用的时候,ArrayList无法知道到底给你多少容量才合适,所以就直接使用你所需的长度。扩容的时候底层是需要操作数组的(Arrays.copyOf),经常扩容会消耗性能的。

    我们看到ArrayList里面的方法都没有添加锁,即当多个线程同时调用的话会会引发不可预知的错误。这是第4个问题的。

    删除remove方法:
    public boolean remove(Object o) {
            //如果传入元素为null,则循环判断元素为null的进行删除
            if (o == null) {
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index);    //删除
                        return true;
                    }
            } else {           //循环数组进行判断删除
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) {
                        fastRemove(index);    //删除
                        return true;
                    }
            }
            return false;
        }
    循环整个数组,判断是否相等进行删除。因为ArrayList 允许空值,所以源码这里进行了多一次的判断是否为null的情况。可以看到核心删除方法是fastRemove。

    private void fastRemove(int index) {
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
        }
    这个核心方法的思路很简单,定位到当前需要删除的元素位置,然后把后面元素的位置+1顶上来一个位置,,然后把最后一个位置设置为null,size同时减1,实现删除。就好比一根三节棍,把中间那截给删了,原来后面的就顶上来跟前面的接在了一起,成了两节棍。

    注:其实从这里就可以解决第5个问题,在它删除元素时候后面的元素需要移动上去,采用了System.arraycopy复制,当移动多了自然性能就低了,这就是删除慢的原因。为什么插入慢呢,我想大家都明白了,删除的时候后面得向前移动,那插入的时候同理得向后移动。(通过指定位置插入元素的情况,因为ArrayList可以从尾部,也可以指定位置插入)。

    思考一个问题,当我们需要向ArrayList增加大量元素时它会扩容成一个大数组,当我们不需要那么多元素的时候把它删了,那数组虽然元素被删了它还是会在内存中占了空间,好比一个大盆子装一个小鸡蛋。ArrayList没有自动来处理这个问题,它提供了一个方法trimToSize()方法。我们可以手动调用此方法触发ArrayList 的缩容机制。这样就可以释放多余的空间,提高空间利用率。

    public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    size是记录元素数据,当size(元素数量)小于此数组的长度(elementData.length),说明数组有空闲空间位置没有被使用,那就把数组转换成长度当前元素数量的长度。这就是第6个问题ArrayList的缩容

  47. 怎么解决你这个系统高并发的问题?

    我说可以用负载均衡来平衡流量,扩大服务器规模,面试官说你数据库服务器不要处理嘛,我赶紧补了一句可以用缓存

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值