2.1 常见的数据结构
数组是最常用的数据结构,数组的特点是
长度固定,数组的大小固定后就
无法扩容了,数组
只能存储一种类型的数据,添加和删除
操作慢因为要移动其他的元素

数组地址值的计算:

数组的首地址 + 当前下标 * 数组中元素类型的大小
为什么数组索引是从0开始的?假如从1开始不行么?

- 在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素数据,寻址公式是:数组的首地址+索引 乘以 存储数据的类型大小
- 如果数组的索引从1开始,寻址公式中,就需要增加一次减法操作,对于CPU来说就多了一次指令,性能不高。
为什么数组的查找速度快?
数组元素的访问是通过
下标或
索引来访问的,计算机通过数组的
首地址和
寻址公式能够很快的找到想要访问的元素
为什么数组插入和删除慢
数组是一段
连续的内存空间,因此为了
保证数组的连续性会使得数组的
插入和
删除效率变得很低
栈是一种基于
先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的
特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候先栈顶开始弹出数据(最后一个数据被第一个读出来)
队列是一种基于
先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的
特殊线性表,它按照先进先出的原则存储数据,先进入的数据,在读取数据时先被读取出来。
链表是一种物理存储单元上
非连续、非顺序的存储结构、其物理结构不能只表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表的指针链接次序实现的。链表由一系列的结节(链表中的每一个元素被称为结点)组成,结点可以在运行时动态生成。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等
树是我们计算机中非常重要的一种数据结构,同时使用树这种结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等。由二叉树、平衡树、红黑树、B树、B+树。
散列表(
哈希表),是根据关键码和值(key和value)直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
堆是计算机学科中一类特殊的数据结构的统称,堆通常可以被看作是一个完全二叉树的数组对象。
图的定义:图是由一组顶点的一组能够将两个顶点相连的边组成
2.2 集合和数组的区别(了解)
区别:
- 数组长度固定 集合长度可变
- 数组中存储的是同一种数据类型的元素,可以存储基本类型或引用类型
- 集合存储的都是对象,而且对象的数据结构类型可以不一致。在开发中一般当对象较多的时候使用集合来存储对象
2.2.5 ArrayList底层的实现原理是什么?
底层数据结构
ArrayList底层是用
动态的数组实现的
初始容量
ArrayList
初始容量为0,当
第一次添加数据的时候才会
初始化容量为10
扩容逻辑
ArrayList在进行扩容的时候是原来容量的
1.5倍,每次扩容都需要
拷贝数组
添加逻辑
- 确保数组已使用长度(size)加1之后足够存下下一个数据
- 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩张(原来的1.5倍)
- 确保新增的数组有地方存储之后,则将新元素添加到位于size的位置上
- 返回添加成功布尔值
2.2.6ArrayList list=new ArrayList(10)中的list扩容几次
该语句只是声明和实例了一个 ArrayList,指定了容量为 10,未扩容
2.2.7如何实现数组和List之间的转换
1.数组转List:使用Arrays.asList(T[])
String[] strs = {"aaa","bbb","ccc"};
List<String> list = Arrays.asList(strs);
2.List转数组:使用List的toArray方法
List<String> list = new ArrayList<String>();
String[] array = list.toArray(new String[list.size()]);
3.用
Arrays.asList转List后,如果修改了数组内容,list受到影响吗
list会受到影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终
指向的都是同一个内存地址
相当于数组转集合只是 数据类型的转换,两者元素指向的地址还是相同的
4.List用toArray转数组后,如果修改了List内容,数组受影响吗
list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响
集合转数组 底层是拷贝了一个
新的数组 两者元素指向的位置不相同 所以
不影响
2.2.8 ArrayList和LinkedList的区别是什么?
1.数据结构底层
ArrayList的底层是:
数组
LinkedList的底层是:
双向链表
2.操作数据效率
- ArrayList按照小标查询O(1) 内存是连续的,按照寻址公式,LinkedList不支持下标查询
- 查找(位置索引):查找都需要遍历,时间复杂度都是o(n)
- 新增和删除
- ArrayList尾部插入和删除,时间复杂度都是O(1);其他部分增删需要挪动数组,时间复杂度是O(n)
- LinkedList首节点增删时间复杂度都是o(1),其他都需要遍历链表,时间复杂度是O(n)
3.内存空间占用
- ArrayList的底层是数组,内存连续,节约时间
- LinkedList的底层是双向链表需要存储数据,和两个指针,更占用内存
4.线程安全
- ArrayList和LinkedList都是线程不安全的
- 如果需要线程安全,有以下两种方案
- 在方法内使用,局部变量是线程安全的
- 使用线程安全的ArrayList和LinkedList

2.3List 和 Map、Set 的区别(必会)
- List和Set是单例集合、Map是双列集合
- List中存储的数据是有顺序的、并且值允许重复;
- Map中存储的数据是无序的,它的key是不允许重复的,但是值允许重复
- Set中存储的是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的哈希值来决定的,即位置是固定的(Set集合是根据哈希值来进行数据存储的,所以位置是固定的,但是这个位置不是用户可以控制的,所以对于用户来说set的元素还是无序的)
2.4 List 和 Map、Set 的实现类(必会)

Connection接口:
List:有序、可重复
- ArrayList:
- 底层数据结构是数组,查询快,增删慢
- 线程不安全、效率高
- Vector
- 底层数据结构是数组,查询快,增删慢
- 线程安全,但是效率低,已经舍弃
- LinkedList
- 底层结构是双向链表,查询慢,增删快
- 线程不安全,效率高
Set:无序,唯一
- HashSet
- 底层数据结构是哈希表。(无序,唯一)
- 如何来保证元素唯一性?
- 依赖两个方法:hashCode()和equals()
- LinkedHashSet
- 底层数据结构是链表和哈希表(FIFO插入有序,唯一)
- 由链表保证元素有序
- 由哈希表保证元素唯一
- 底层数据结构是链表和哈希表(FIFO插入有序,唯一)
- TreeSet
- 底层数据结构是红黑树。(唯一,有序)
- 如何保证元素排序的呢?
- 自然排序
- 比较器排序
- 如何保证元素唯一性的呢?
- 根据比较的返回值是否是0来决定
- 如何保证元素排序的呢?
- 底层数据结构是红黑树。(唯一,有序)
Map接口:
HashMap:基于
hash表的Map接口实现,
非线程安全,
高效,
支持null值和null建,线程不安全
LinkedHashMap:线程
不安全,是
HashMap的
一个子类,
有序:保存了记录的插入顺序
HashTable:
线程安全,
低效,
不支持null值和null建
TreeMap:能够把它保存的记录根据键排序,默认是键值的
升序排序,
线程不安全
2.5Hashmap 的底层原理(高薪常问)
JDK1.8之前的实现方式
数组+链表
JDK1.8之后:数组+链表 或者由
数组+链表或者
数组+红黑树 实现,目的是提高查找效率
- 节点数>=8 数组+红黑树
- 节点数<=6 数组+单向链表
HashMap结构

- JDK8数组+链表 OR 数组+红黑树 实现,当链表中的元素超过8个以后,会将链表转换为红黑树,当红黑树节点小于等于 6时又会退化为链表。
- 当new HashMap():底层没有创建数组,首次调用put()方式时,底层创建长度为16的数组,jdk8底层的数组是:Node[](节点数组)存储引用关系,而非Entry[](存储键值对),用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值分配到新的数组,所以扩容的操作非常消耗性能
- 默认的负载因子大小为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍
- 在我们java中任何对象都有hashcode,hash算法就是通过hashcode与自己进行向右位移16的异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机。
map.put(k,v)实现原理
- 首先将k,v封装到Node对象当中(节点)。
- 先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的小标。
- 小标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖;如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。
map.get(k)实现原理
- 先调用k的hashcode()方法得到k的哈希值,然后通过哈希算法转换成数组的小标
- 在通过数组小标快速定位到某个位置。
- 重点理解:如果这个位置上面什么也没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数k和单向链表上的每一个节点k进行equals,如果所有equals方法都返回false,则get方法返回null。如果某个节点返回true,就代表这个节点就是我们要找的节点,返回这个节点的value
Hash冲突
不同对象经过
哈希函数得到的
哈希值是一致的,这就会产生hash冲突。当单线链表达到一定长度后效率会非常低
注意:链表长度
大于8的时候,就会将
链表转换为红黑树,提高查询效率
2.6 Hashmap 和 hashtable ConcurrentHashMap 区别(高薪常问)
HashMap和HashTable的区别:
- HashMap是非线程安全的,HashTable是线程安全的。
- HashMap的键和值都允许有null值存在,而HashTable则不行。
- HashMap线程不安全,HashTable线程安全,但是因为线程安全的原因,HashMap效率更高。
- HashTable是同步的,HashMap不是。因此,HashMap更适合于单线程环境,而HashTable适合于多线程环境,一般现在是不建议使用HashTable,
- ①是因为HashTable是遗留类,内部实现很多没有优化,并且冗余。
- ②即使在多线程环境下,现在也有同步的ConcurrentHashMap替代。
HashTable和ConcurrentHashMap区别:
HashTable使用的是
Synchronized关键字修饰,
ConcurrentHashMap是
JDK1.7使用了
锁分段技术来保证线程安全的。
JDK1.8ConcurrentHashMap取消了Segment分段锁,
采用CAS和synchronized来保证并发安全。
ConcurrentHashMap底层是数组+链表/红黑树
HashTable底层是数组加链表
ConcurrentHashMap中的
synchronized只锁定当前
链表或红黑树的首节点,只要hash不冲突,就不会产生并发,效率有提示N倍
为什么ConcurrentHashMap
优于HashTable?
- ConcurrentHashMap 在JDK1.8以后采用的是CAS 乐观锁和synchronized悲观锁,而HashTable采用的是Synchronize悲观锁。其中ConcurrentHashMap的synchronized
- 乐观锁的范围小于悲观锁,尤其是读多写少的场景下,并发性能优于悲观锁。