从事Java开发工作阶段的知识分享
集合相关知识汇总
前言
凑闲暇之余整理了所有关于集合的知识,会不断更新,帮助自己学习归纳,也希望能够帮助到有需要的人。
总结内容整合复制了各种网络资料,仅供学习
因为也是在不断学习的阶段,如果有不对的地方还希望各位大佬指点。
一、常用的集合类
主要分为三类
Map: HashMap、LinkedHashMap、TreeMap
Set:HashSet、LinkedHashSet、TreeSet
List:ArrayList、LinkedList
二、知识汇总
1.ArrayList和LinkedList内部实现大致是怎样的?他们之间的区别和优缺点?
-
ArrayList:内部使用数组的形式实现了存储,利用数组的下标进行元素的访问,因此对元素的随机访问速度非常快。因为是数组,所以ArrayList在初始化的时候,有初始⼤小10,插入新元素的时候,会判断是否需要扩容,扩容的步长是0.5倍原容量,扩 容方式是利用数组的复制,因此有一定的开销。
-
LinkedList:内部使用双向链表的结构实现存储,LinkedList有一个内部类作为存放元素的单元,⾥面有三个属性,用来存放元 素本身以及前后2个单元的引用,另外LinkedList内部还有一个header属性,用来标识起始位置,LinkedList的第一个单元和最后⼀ 个单元都会指向header,因此形成了一个双向的链表结构。
-
ArrayList查找较快,插入、删除较慢,LinkedList查找较慢,插入、删除较快。
2.Hashmap实现原理理?如何保证HashMap线程安全?
a. HashMap简单说就是它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度, 但遍历顺序却是不确定的。
b. HashMap基于哈希表,底层结构由数组来实现,添加到集合中的元素以“key–value”形式保存到数组中,在数组中key-- value被包装成一个实体来处理—也就是上面Map接口中的Entry。
c. 在HashMap中,Entry[]保存了集合中所有的键值对,当我们需要快速存储、获取、删除集合中的元素时,HashMap会根据hash算法来获得“键值对”在数组中存在的位置,以来实现对应的操作方法。
d. HashMap底层是采⽤数组来维护的.Entry静态内部类的数组
/**
* The TABLE, resized AS NECESSARY. Length MUST ALWAYS be A power of two.
*/
TRANSIENT Entry[] TABLE;
STATIC CLASS Entry<K,V> implements MAP.Entry<K,V> {
FINAL K key;
V VALUE;
Entry<K,V> next;
FINAL int HASH;
……
}
e. HashMap添加元素:将准备增加到map中的对象与该位置上的对象进⾏比较(equals方法),如果相同,那么就将该位置上的那个对象(Entry类型)的value值替换掉,否则沿着该Entry的链继续重复上述过程,如果到链的最后任然没有找到与此对象相同的对象,那么这个 时候就会被增加到数组中,将数组中该位置上的那个Entry对象链到该对象的后面(先hashcode计算位置,如果找到相同位置便替换值,找不到则重复hashcode计算,直到最后在添加到hashmap最后面)
f. HashMap是基于哈希表的Map接口的非同步实现,允许null键值,但不保证映射的顺序;底层使用数组实现,数组中的每项是一个链表;存储时根据key的hash算法来决定其存储位置;数组扩容需要重新计算扩容后每个元素在数组中的位置很耗性能;
g. ConcurrentHashMap是HashMap的线程安全实现,允许多个修改操作同时进行(使用了锁分离技术),它使用了多个锁来控制对hash表的不同段进行的修改,每个段其实就是一个小的hashtable,它们有⾃己的锁。使⽤用了多个子hash表(段Segment),允许多个读操作并发进行,读操作并不不需要锁,因为它的HashEntry几乎是不可变的:
3. HashMap与HashTable的区别
1.线程安全上,hashtable是同步的线程安全;hashmap是非同步的线程不安全,可接受null的值和value(hashtable不允许);
2.对单线程来说,hashTable效率低
3、线程安全的类:vector(比arrayList多了同步机制,效率低不建议使⽤用)、stack(堆栈类,先进后出)、hashtable(比hashmap
多了同步机制)、enumeration(枚举类)
4. 集合删除:
注意: List底层为数组,删除时数组元素下标会被改变
- 跌代器调用.next()方法时,会检测是否有被修改过
- 如果要删除集合中的元素一定要用跌代器的remove()方法.
5. hashmap在jdk1.8中的改动
-
Jdk1.8以前是进行四次扰动计算,可能从速度功效各⽅面考虑,jdk1.8变成扰动一次,低16位和高16位进行异或计算。取模的时候考虑取模运算的速度比较慢,改用与操作优化效率,很巧妙,hash table就没设计的这么好。
-
JDK1.8⾥对hashmap最⼤的改变是引入了红黑树,这一点在hash不均匀并且元素个数很多的情况时,对hashmap的性能提升非常⼤。Hashmap的底层实现是使用一个entry数组存储,默认初始⼤小16,不过jdk8换了名字叫node,可能是因为引入了树,叫node更合适吧,另外我也不喜欢entry这个名字,不能望文生义,我在刚学的时候还以为是什么神秘的东⻄呢,其实就是个键值对象而已。Node里有next引用指向下一个节点,因为hashmap解决冲突的思路是拉链法。
-
另外变化比较大的还有扩容机制,也就是resize方法。
6. Java8实现List转map、分组、过滤等
- Java8的新特性,可以使一些数据处理更简洁高效
- 首先,准备一个Fruit类
public class Fruit {
private Integer id;
private String name;
private BigDecimal money;
private Integer num;
}
- 然后,添加一些数据
List<Fruit> fruitList = new ArrayList<>();//存放fruit对象集合
Fruit fruit1 = new Fruit(1,"Apple1",new BigDecimal("3.25"),10);
Fruit fruit12 = new Fruit(1,"Apple2",new BigDecimal("1.35"),20);
Fruit fruit2 = new Fruit(2,"banana",new BigDecimal("2.89"),30);
Fruit fruit3 = new Fruit(3,"orange",new BigDecimal("9.99"),40);
fruitList.add(fruit1);
fruitList.add(fruit12);
fruitList.add(fruit2);
fruitList.add(fruit3);
1. 分组
/**
* 分组
* List里面的对象元素,以某个属性来分组,例如,以id分组,将id相同的放在一起:
* List 以ID分组 Map<Integer,List<Fruit>>
*/
Map<Integer, List<Fruit>> groupBy = fruitList.stream().collect(Collectors.groupingBy(Fruit::getId));
System.err.println("groupBy:"+groupBy);
2. List转Map
/**
* 2、List转Map
* id为key,fruit对象为value,可以这么做:
*
* List -> Map
* 需要注意的是:
* toMap 如果集合对象有重复的key,会报错Duplicate key ....
* fruit1,fruit12的id都为1。
* 可以用 (k1,k2)->k1 来设置,如果有重复的key,则保留key1,舍弃key2
*/
Map<Integer, Fruit> fruitMap = fruitList.stream().collect(Collectors.toMap(Fruit::getId, a -> a,(k1,k2)->k1));
3. 过滤Filter
/**
* 过滤Filter
*
* 从集合中过滤出来符合条件的元素:
*/
List<Fruit> filterList = fruitList.stream().filter(a -> a.getName().equals("banana")).collect(Collectors.toList());
System.err.println("filterList:"+filterList);
4. 求和
/**
* 求和
*
* 将集合中的数据按照某个属性求和:
* 计算 总金额
*/
BigDecimal totalMoney = fruitList.stream().map(Fruit::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
System.err.println("totalMoney:"+totalMoney);
5. 去重
import static java.util.Comparator.comparingLong;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
/**
* 去重
* 根据id去重
*/
List<Fruit> unique = fruitList.stream().collect(
collectingAndThen(
toCollection(() -> new TreeSet<>(comparingLong(Fruit::getId))), ArrayList::new)
);