数组和链表的区别:
-
动态扩容:
数组的缺点是大小固定,一经声明就要占用整块连续的内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致内存不足(OOM);如果声明的数组过小,则可能会出现不够用的情况。这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常耗时。而链表本身没有大小的限制,所以天然支持动态扩容
补充:【ArrayList 容器支持动态扩容,当空间不足时,就会申请一个更大的空间,将原数组拷贝过去 】
-
内存的使用:
链表中的每个结点都需要消耗和外的存储空间去存储一份指向下一个结点的指针,所以内存消耗会翻倍。而且对链表进行频繁的插入、删除操作,还会导致频繁的内存申请和释放(Java 语言会导致频繁的GC),相对而言数组的插入需要申请内存,但数组的删除可以利用 jvm 垃圾标记清除算法来减少垃圾回收次数,减少耗时。数组中删除数据时,并不是真正的删除,而是标记一下,等数组空间不够用时,我们在执行删除操作(数据的移动),这样可以减少因删除操作导致的数据搬移。这种思想在 JVM 垃圾回收算法的标记清楚算法中也有体现,第一遍先标记垃圾对象,第二遍再清除垃圾对象【这种垃圾回收算法,容易产生内存碎片,导致出现内存空间充足,但是无法放置大对象的诡异现象】
-
访问效率
因为数组在实现上使用连续的内存空间,可以借助 CPU 的缓存机制(加载某个下标的时候可以把其后的几个下标元素也加载到 CPU 缓存),预读数组中的数据,所以访问效率更高。而链表在内存中不是连续存储,所以对 CPU 缓存并不友好CPU 缓存存在的意义:为了弥补内存访问速度过慢与 CPU 执行速度快之间的差异而引入。CPU 从内存读取数据的时候,会先把数据加载到 CPU 缓存中,而 CPU 每次从内存读取数据并不是只读取那个特定要访问的地址,而是读取一个数块()并保存到 CPU 缓存中,然后下次访问内存数据的时候就会先从 CPU 缓存中开始查找,如果找到就不需要再从内存找了。
-
其他
链表适合插入、删除,时间复杂度为O(1),数组适合查找。数组支持随机访问,根据下标随机访问的时间复杂度为O(1) 。【有序的数组,二分查找时间复杂度为O(logn),所以查找的时间度为O(1)这种表述是不对的】
其他
- 假设数组的长度为 n , 将一个数据插入到数组的第 K 个位置
- 如果在数组的切莫插入元素,就不需要移动数据看,这时时间复杂度未 O(1)
- 如果在数组的开头插入元素,所有元素依次往后移动一位,所以最坏时间复杂度未O(n)
- 因为在每个位置插入元素的概率是一样的,所以平均情况时间复杂度为(1+2+…+n)/n = O(n)
- 如果数组中存储的元素没有任何规律,在这种情况下,可以直接将第 K 位的数据移动到数组元素的最后,把新元素直接放入第 k 个位置。时间复杂度变为 O(1)
- 删除操作,如果将多次删除操作集中在一起执行,效率会提高很多【JVM 标记清除垃圾回收算法的核心思想】