我们都知道,在Java开发中,永远离不开集合,有些在学习阶段的人会问,我不就用一个ArrayList就可以了吗?
我想说的是,不,因为由于在学习阶段所接触到的数据,都是很少很少的,所以每次请求都会跑一遍数据库,得到数据之后进行操作。
但是在真正的工作环境中,数据都是百万千万级别的,就比如我们经常使用的淘宝京东,数据量我们可想而知,如果每次进行页面跳转或者操作数据之后重新获取一遍,那对于服务器和数据库的压力是非常大的。
所以我们要用到这些集合,因为集合保存在内存中,查询速度是很快的,对于数据库和服务器的压力也会大大减小。
所以本章介绍我们常用的3种集合,分别是List、Set和Map
在此之前,先看一下UML图,这是很常见很经典的一张图,同时也是一张很老的图了,因为还有一个CopyOnWriteArrayList类实现List接口,从图中可以看出,List接口和Set接口都继承Collection接口,唯独Map是分离出来的,面试中可能会本问到
1.List
特点:元素有序,且可重复
遍历:可通过下标,foreach,迭代器遍历
扩容:初始容量10,负载因子0.5,扩容增量0.5倍
新容量 = 原容量 + 原容量 * 0.5
实现类:
1.1 ArrayList简单数据结构,超出容量自动扩容,动态数组
内部实现是基于基础的对象数组的
随机访问快,因为是用数组的方式,而且也保存在内存中
不适合随机增加或删除,因为如果是在数组的中间添加或者删除数据,会打乱之后数据的顺序,比如删除其中一个数据之后,后面的数据的下标都会 -1, 所以不适合
线程不安全
删除时的注意点:
1)下标循环删除方式1
for(int i = 0; i这种方式是可以删除元素的,但是如果要删除的元素挨在一起,那么就会出问题,因为当在ArrayListt中,删除一个元素之后,下面的元素会挤上去,就比如一个数组的第一位和第二位是你要删除的,循环的下标为0,1,当你删除掉第一个时,第二个就会跑到第一个的位置,但是第二次循环已经从第三位开始了,虽然这时的下标还是1,但是位置已经改变了,所以这种方式不可取
2) 下标循环删除方式2
for(int i = 0; i < list.size(); i++) { if(list.get(i) == 3) list.remove(i--); }这种方式不管元素是否挨在一起,都能正常删除
3)下标循环倒序删除
for(int i = list.size() - 1; i >= 0; i--) { if(list.get(i) == 3) list.remove(i); }这种方式也是可取的,因为是从数组的最后一个开始删除,就不会出现移位删不到的情况
4) foreach循环删除
for(Integer e: list) { if(e == 3) { list.remove(e); } }这种方式看上去好像是没有问题的,但是最大的问题就是,foreach循环完全不能用,如果你要删除的元素在倒数第二位,那么刚好是可以运行的,如果不是的话,会直接报错
5) 删除的参数是基本型的整数
list.remove(i)如果remove方法中传入的是个数值类型,那么就会根据下标删除对应元素,如果是对象类型,那么list就会删除其保存的那个对象
1.2 LinkedList1.3 VectorLinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部进行操作
线程不安全
LinkedList可被用作堆栈(stack)【包括了push,pop方法】,队列(queue)或双向队列(deque)
以双向链表实现,链表无容量限制,允许元素为null,线程不安全
适合做随机的增加或删除
1.4 CopyOnWriteArrayList线程安全
并行性能慢,不建议使用,就是当有一个线程访问时,会把整个集合锁死,很影响效率,现在一般不用Vector了
这就是之前所说新加的一个实现类
写时复制
线程安全
适合于读多,写少的场景
写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
比Vector性能高
最终一致性
实现了List接口,使用方式与ArrayList类似
2.Set
特点:无序,不重复
遍历:foreach,迭代器
扩容: 初始容量16,负载因子0.75,扩容增量1倍
实现类:
2.1 HashSet2.2 TreeSet它存储唯一元素并允许空值,依据对象的hashcode来确定该元素是否存在
由HashMap支持
不保持插入顺序
非线程安全
性能参数:初始容量,负载因子(默认值: 初始容量16,负载因子0.75)
注意点:
1)set不能通过下标删除,因为是无序的,但是可以根据元素删除。
2)HashSet实际上是根据Map实现的,HashSet刚好只用到了Map中的“键”
是一个包含有序的且没有重复元素的集合
作用是提供有序的Set集合,自然排序或者根据提供的Comparator进行排序
TreeSet是基于TreeMap实现的
自定义比较器:
1)通过构造函数传入比较器
TreeSettset = new TreeSet (new Comparator () { @Override public int compare(Integer o1, Integer o2) { // TODO Auto-generated method stub return o2 - o1; } }); 2)实现排序接口
public class Student implements Comparable3.Map{ private Integer sid; private String name; private int age; //构造函数,getter,setter,hashCode,equals等方法省略 @Override public int compareTo(Student o) { // TODO Auto-generated method stub return o.getAge() - this.getAge(); } } 特点:无序,键值对,键不能重复,值可以重复,键重复则覆盖,没有继承Collection接口
扩容:初始容量16,负载因子0.75,扩容增量1倍
遍历:先获取所有键的Set集合,再遍历(通过键获取值),取出保存所有Entry(保存键值对的实体)的Set,再遍历此Set即可
实现类:
3.1 HashMap线程不安全,最常用,速度快
内部采用数组来存放数据
基本原理:
put执行过程:在调用PUT方法时,会对传入的key进行哈希运算得到一个hashcode,然后再通过逻辑与操作得到一个数组下标,最后将key+value存在这个数组下标处。确定了key+value该存的位置之后,对于不同的参数可能会得到相同的HashCode,也就是会发生哈希冲突,反应到HashMap中就是,当PUT两个不同的key时可能会得到相同的HashCode从而得到相同的数组下标,其实在HashMap中就算key所对应的HashCode不一样,那么也有可能在经过逻辑与操作之后得到相同的数组下标,那么这时HashMap是如何处理的呢?对,就是链表
Table数组中的的Node
流程图中绿色标出的部分为JDK8新增的处理逻辑,目的是在Table[i]中的Node节点数量大于8时,通过红黑树提升查找速度。
3.2 HashTable3.3 ConcurrentHashMap线程安全,不太常用
3.4 TreeMap线程安全,比HashTable性能高
3.5 LinkedHashMapkey值按一定的顺序排序
添加或获取元素时性能较HashMap慢,因为需求维护内部的红黑树,用于保证key值的顺序
继承HashMap
LinkedHashMap是有序的,且默认为插入顺序,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了
示例:
MaplinkedHashMap = new LinkedHashMap<>(); linkedHashMap.put("name1", "josan1"); linkedHashMap.put("name2", "josan2"); linkedHashMap.put("name3", "josan3"); Set > set = linkedHashMap.entrySet(); Iterator > iterator = set.iterator(); while(iterator.hasNext()) { Entry entry = iterator.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); System.out.println("key:" + key + ",value:" + value); } 总结:所以,在不同的情况下需要使用不同的集合框架,要看当时功能需求,这样才能把功能写的更好



