数据结构-线性表


线性表:顾名思义,就是想一条线一样的结构。
大致分为两种:
一种物理地址连续的线性表,就是数据存储的时候是一个挨着一个的;
一种链表结构的线性表,物理地址不连续,但是也是线性表。

顺序表

数组

用过数组的都知道,遍历还好,如果需要扩容,插入,删除这些操作的时候,要一直考虑容量,下标位置等,很麻烦。所以java工程师就出了一个ArrayList这个集合,下面说说这个集合。

ArrayList

ArrayList是线性表,但是底层是数组。
ArrayList集合,查看源码就知道,其实存储方式还是数组,但是ArrayList在使用的时候就比数组方便太多了。
创建ArrayList集合的时候不需要给指定容量,默认是10的容量,但是如果你什么数据都不存,那就是个空的数组。
你在往ArrayList里面添加集合的时候,默认就是往数组末尾添加,如果是中间插入,会自动把这个位置到末尾的数据自动向后移动一位,并不需要你考虑数组中元素的移动问题。
还有ArrayList会自动扩容,如果容量满了,你在add的时候,就会扩容到原本容量的1.5倍。
所以从使用的角度看,ArrayList就是线性表结构,使用者只需要关心下标,其他的元素位移,容量大小等都不需要考虑了。ArrayList比单纯的数组要好用太多了。
源码分析在java基础复习3里面有,这里就不再写一遍了。
链接: Collection集合详细版.

面试点:
1.ArrayList的大小如何自动增加的?
答案:ArrayList源码中写的,默认初始化容量为10,但是如果没有添加元素,其实是个空数组,在第一次添加的时候会初始化这个容量。然后在添加的时候会判断当前集合容量,如果容量满了,就会创建一个新的数组,一般情况扩容为原来的1.5倍,特殊情况是扩容小于最小扩容的值,或者大于最大扩容的值,这时扩容的值就是最小值或者最大值。源码就是这样设计的。

2.什么情况下你会使用ArrayList?
其实面试官是想了解你是否知道ArrayList的特点,优点和缺点。
答案:一般如果数据不需要大量增删,只是查看的话会用ArrayList。因为ArrayList底层是用数组实现的,所以ArrayList查询速度比较快,向数组中间插入或者删除比较慢,但是如果只是往结尾添加,不影响速度。但是如果能大概确定容量,最好是给个初始容量,毕竟在扩容的时候还是需要arraycopy的,arraycopy还是比较消耗性能的。

3.在索引中ArrayList的增加或者删除某个对象的运行过程?这个运行过程效率很低吗?为什么?
这个其实就是问ArrayList的增删运行机制了。
答案:增加的话,就是add方法,如果不传入添加到哪个索引位置,默认是往结尾添加,如果指定位置往中间插入了,那就需要把这个节点位置包含这个节点的往后挪一个位置,把插入的元素放到这个位置。删除的话就是删除这个节点所在元素,然后把这个节点后面的元素都向前移动一个位置。
通过这个描述,应该就知道在插入和删除中间元素的时候,挺麻烦的,所以效率就很低。
注意:在遍历的时候不可以添加元素,会报错ConcurrentModificationException并发修改异常,所以一定要注意,但是用迭代器删除是可以的。

4.ArrayList如何顺序删除节点?
答案:使用iterator迭代器,因为这个迭代器已经设计好了在遍历是删除的方法。如果使用fori的方式,处理不好就容易引发索引越界异常。

5.ArrayList的遍历方法?
答案:1.fori-普通for循环,2.foreach-增强for循环,3.iterator-迭代器。
如果需要使用下标,就需要fori方式。如果需要在遍历时删除,就要使用迭代器了。增强for循环,就看情况了。如果遍历不需要下标,同时又觉得迭代器太麻烦,可是使用增强for循环去写了。

链表

下面的定义摘抄自百度百科。
定义:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

单链表

定义摘抄自百度百科。
定义:单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
这个定义如果不理解,就可以理解成,一个链表结构中的每个节点对象,都持有下一个节点对象的引用。
一个节点只是有下一个节点的引用,那就是单向链表,在HashMap的源码中,就是数组结构加上单向链表的结构,HashMap的结构就是散列表结构。

双链表

定义摘抄自百度百科。
定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
个节点有上个节点的引用,也有下一个节点的引用,那这个就是双向链表结构,LinkedList就是双向链表的结构。我在之前的java复习的Conllction集合详解里面详细分析了LinkedList的源码。
链接: Collection集合详细版.

插入一个元素,画个图吧。
LinkedList的add插入数据
如上图,开始的引用关系是
a.next = b;
b.prev = a;
这个时候来了一个c,假设b的位置是index,这时候add(index,c);这个c要插入到a和b中间。
源码总第一步是先根据add方法中的index,判断index是否小于整个集合size的一半大小,如果满足就从头循环,如果不满足,就从后面循环。这样循环走完之后,就得到b节点了。
这时候根据b节点,b.prev就得到a节点了。这样就有了a和b连个节点。
这时候创建c节点,构造参数就是c元素,a节点和b节点。这样创建出c节点的同时,上图的第一步和第二步就同时完成了。但是第三第四没完成。
然后就把b节点的b.prev = c;这样就完成了第三步。
最后在把a节点的a.next = c;这样就完成了添加工作。
然后还有两个参数要修改一下。一个是数组大小,size++。
还有一个是modCount,这个参数也要加一,modCount++。
以上就是源码的实现过程,只不过我用大白话说了一遍。

这个modCount为啥要加一呢,这个就涉及到多线程修改数组或者在迭代器遍历循环过程中添加元素引发的异常有关系了。
那么modCount这个值都在什么时候会改变呢,只要是添加和删除的时候,都会modCount++。
在迭代器遍历过程中,有remove方法对吧,但是没有add方法。不知道你们有没有试过,在迭代器遍历过程中插入元素,会抛出异常ConcurrentModificationException,这个叫并发修改异常。
也就是迭代器在迭代的时候会记录modCount这个值,在遍历过程中,每次都会判断这个值是否和开始遍历的时候相等,如果不相等了,就证明这个数组被修改过了,就抛出并发修改异常,告诉你这个集合元素被修改过了,可能和原来的集合不一样了。

面试相关:
如果问的话,应该也是和ArrayList对比,LinkedList和ArrayList有什么区别,都有什么优缺点。
比较全面点说,
存储方式不同,ArrayList是地址连续存储,LinkedList是地址不连续的存储。
增删和查询处理方式不同,ArrayList插入和从中间删除,需要移动这个位置以后的所有元素的存储地址,查询可以根据物理地址计算偏移量直接定位到查找的元素。LinkedList插入和删除,只需要设置prev和next的引用关系,查询的话只能从头遍历查询或者从尾遍历查询。
共同点,就是这两个都是有序的,有序是指有添加顺序,具体是指添加是什么顺序,遍历取出来的还是什么顺序,并不会根据数据内容进行排序。
优缺点:
从上面的存储方式和增删查询方式的不同,可以知道,ArrayList集合查询速度快,但是插入数据比较消耗时间,注意如果只是往末尾插入数据,影响不大,因为往末尾插入基本不需要动其他元素。LinkedList集合是插入速度比较快,因为它需要变动的元素很少,但是查询就比较慢了,因为每次查询都是遍历,虽然顶多遍历数组一半大小,但是还是不如ArrayList直接定位查找快。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值