从源码浅谈面试List 和 set 的区别

 

个人愚见,如有错误之处,望见谅。

 

List和Set区别
是否有序顺序存储无序
是否可重复可以重复不可以重复
存储结构数据或者链表使用Map来存储数据

 

    数据结构存储方式有两种:1. 数组 2. 链表。

 

    List 集合实现有多种方式,不同方式数据结构存储方式不一样,下面从源码的角度看一下不同实现的原理:

ArrayList 

1. 内部维护一个数组,每次添加相当于向数组中添加数据。

2. 初始化时容量为0,只有当第一次添加数据时才会将容量修改为10。每次扩容为原来大小的1.5倍,然后通过Arrays.copyOf(elementData, newCapacity)方法返回扩容后的数据。

3. ArrayList 添加和删除每次操作都会进行一次数组拷贝

4. fail-fast  在迭代的时候,首先会判断expectedModCount 和  modCount是否相等,如果不相等则抛出ConcurrentModificationException异常.其中expectedModCount等于迭代时的modCount值,当每次有人调用add,remove方法时,modeCount值就会改变,所以这时expectedModCount 和  modCount是不相等,抛出异常

见链接:Java中ArrayList最大容量为什么是Integer.MAX_VALUE-8?

public ArrayList() {
		//创建对象时,如果没有指定容量,则是一个空数组,容量为0
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确保容量足够
        elementData[size++] = e; //元素存放在数组中
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
	//如果数组为空,则初始化容量为10
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	//确定明确的容量
	ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	// 当需要的最小内存比原有容量大时扩容
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}
//真正的扩容操作
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
		//新容量 = 旧容量 + (旧容量/2) 1.5倍扩容
        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);
    }

取数据时,首先判断下表是否越界,然后返回数组中对应下标的数据。

public E get(int index) {
		//检查下标是否越界
		rangeCheck(index);
        return elementData(index);
    }

LinkedList 是由node节点组成的双向链表,没有初始化容量,不存在扩容概念,node节点的结构如下

  private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }


每当有新数据添加的时候,会将元素添加到链表的最后位置,并将元素的prev 指向前一个元素,前一个元素的next指向需要添加的元素

public E get(int index) {
		//检查下标是否越界
		rangeCheck(index);
        return elementData(index);
    }
public boolean add(E e) {
	linkLast(e);
	return true;
}
void linkLast(E e) {
	final Node<E> l = last;
	final Node<E> newNode = new Node<>(l, e, null);
	last = newNode;
	if (l == null)
		first = newNode;
	else
		l.next = newNode;//将原始节点的next属性指向新节点组成循环链表
	size++;//记录链表的长度
	modCount++;
}
​
Node<E> node(int index) {
    // assert isElementIndex(index);
    //个人提示:如果所取元素位置小于list大小的一般,就从前向后取,反之从后向前取
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

​

set集合暂时只介绍HashSet

HashSet 内部是一个map,通过HashMap来保证数据的唯一性,因此是非线程安全和无序的

public HashSet() {
        map = new HashMap<>();
    }

数据时没有get方法,只能通过迭代的方式获取数据map中所有的key

private static final Object PRESENT = new Object();

public boolean add(E e) {
		//存入map中
        return map.put(e, PRESENT)==null;
    }
//present 作用:map移除会返回value,如果底层用null的话无法分辨是否移除成功
public boolean remove(Object o) {
	return map.remove(o)==PRESENT;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值