1.集合和数组的区别:
集合:大小可以动态扩展,可以存储不同的数据类型
数组:大小固定,只能存储相同的数据类型,是java语言内置的数据类型,执行效率和类型检查都是最快的
数组转成集合:
Arrays.asList(数组);
集合转成数组:集合.toArray();
2.集合
集合的最顶层是两个接口:Collection 和 Map
Collection下面的子接口:List(有序、可重复)、Set(无序、唯一)、Queue
Map下面的子接口不怎么用到,就不写了,主要研究实现类:HashMap、HashTable、TreeMap、CurrentHashMap
List的实现类有:ArrayList、LinkedList、Vector
Set的实现类有:TreeSet、HashSet、LinkedHashSet
2.1 ArrayList:
是实现List的动态数组,每个ArrayList实例都有一个容量,用来存储列表元素的数组的大小。默认容量为10。
随着容量的不断增加,效率会变低,因为每次添加元素的同时,ArrayList都会检查是否需要进行扩容操作,如果进行扩容操作会导致数据向新数组的重新拷贝,循环这样一直操作不利于代码性能。解决方法:通过预先设置容量list.ensureCapacity(N);
ArrayList是线程不安全的,那为什么是不安全的呢?源码来分析:
首先ArrayList用一个Object的数组用来保存所有的元素,以及一个size变量用来保存当前数组保存了多少元素
transient Object[] elementData;
private int size;
线程不安全的地方在于add()的时候,add内部源码如下:
public boolean add(){
/**
* 添加一个元素时,做了如下两步操作
* 1.判断列表的capacity容量是否足够,是否需要扩容
* 2.真正将元素放在列表的元素数组里面
*/
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add实际做了两个步骤:
1.判断elementData数组容量是否满足需求
2.在elementData对应位置上设置值
发生线程不安全的情况主要在elementData[size++] = e这行,
此行可以解析成elementData[size] = e; size = size + 1;
多线程操作的话就会出现同一个索引位置值覆盖的情况。
怎么解决ArrayList的不安全?
使用CopyOnWriteArrayList 代替线程不安全的ArrayList()
2.2 LinkedList
是通过双向链表实现的。双向链表就是通过Node类来体现的。增加删除快,查询慢
2.3 HashMap
底层数据结构是 数组+链表或者红黑树,当链表长度超过8的时候转成红黑树
采用这种数据结构的原因:数组查询效率高,添加删除效率低;链表查询效率低,添加删除效率高
存储过程:
HashMap<String,String> map = new HashMap<String,String>();
map.put("刘德华","张惠妹");
map.put("张学友","大S");
计算出键“刘德华”的hashcode,该值用来定位要将这个元素存放到数组中的什么位置,数组默认长度为16
刘德华的hashcode为20977295 数组长度为 16则要存储在数组索引为 20977295%16=1的地方
可以分两种情况:
1. 数组索引为1的地方是空的,这种情况很简单,直接将元素放进去就好了。
2. 已经有元素占据了索引为1的位置,这种情况下我们需要判断一下该位置的元素和当前元素是否相等,使用equals来比较。
如果两者相等则直接覆盖,如果不等则在原元素下面使用链表的结构存储该元素
HashMap中有两个重要的参数:初始容量大小和加载因子,初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容.
在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.