美文网首页
Java 集合详解

Java 集合详解

作者: 潜心之力 | 来源:发表于2020-04-16 22:33 被阅读0次
一、Set 集合

Set继承Collection,集合里的元素不重复,新增元素与原有元素相同时将覆盖原元素。HashSet和TreeSet是其实现类,HashSet内部封装HashMap,通过HashMap的key来存储值,内部存储的元素是无序的,TreeSet内部封装TreeMap,通过TreeMap的key来存储值,TreeMap的本质是红黑树。内部存储的元素是有序的。HashSet 的执行效率高于TreeSet,HashSet采用散列算法对集合操作的时间复杂度为O(1),TreeSet对集合操作的时间复杂度位O(log n)。

Set<String> hashSet = new HashSet<>();
hashSet.add("a"); -> 插入元素
hashSet.remove("a"); -> 移除元素

Set<String> treeSet = new TreeSet<>();
treeSet.add("a"); -> 插入元素
treeSet.remove("a"); -> 移除元素

Iterator<String> iterator = hashSet.iterator(); -> 迭代器
while (iterator.hasNext()){
    String string = iterator.next();
    System.out.println(string);
}
二、List 集合

List继承Collection,集合里的元素有序可重复。
ArrayList和LInkList是其实现类,ArrayList内部封装数组,默认大小为10,随着元素增多容器也随之增大,每次扩容为原来大小的1.5倍,数组的特点是初始化后分配固定大小的内存空间,数组里的元素在内存空间中是连续存放的,查询元素效率快,添加删除元素效率慢,LInkLIst内部封装双向链表,链表的特点是元素在内存中以散列的形式存放,内存地址不连续,元素内置指针,通过指针找到相邻的元素,添加删除元素效率快,查询元素效率慢,随着元素增多链表不断地向两端伸张。

List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
list1.add(1); -> 集合尾部添加元素([1])
list1.add(0,2); -> 集合指定位置添加元素,原位置的元素统一后移([2,1])
boolean b = list2.addAll(Arrays.asList(2,3)); -> 集合里批量添加元素,求并集([2,3])
list2.addAll(1,Arrays.asList(2, 3)); -> 集合里指定位置批量添加元素([2,2,3,3])
list1.set(0,3); -> 集合里指定位置替换元素([3,1])
list1.remove(1); -> 集合里移除指定位置的元素([2])
list1.remove((Integer) 1); -> 集合里移除指定的元素([2])
list1.removeAll(list2); -> 集合里批量删除元素,求差集([1])
list1.retainAll(list2); -> 保留两个集合里都存在的元素,求交集([2])
boolean b = list1.contains(1); -> 集合里是否包含某个对象
boolean b = list1.contains(list2); -> 集合里是否包含某个集合
Integet value = list1.get(0); -> 通过索引获取集合里的元素(2)
int index = list2.indexOf(2); -> 返回元素在集合里出现第一次的索引
int index = list2.lastIndexOf(2); -> 返回元素在集合里出现最后一次的索引
int length = list2.size(); -> 返回集合的长度
Object[] array = list2.toArray(); -> 集合转数组
list1.clear(); -> 清空集合里的元素

LinkedList<Integer> list1 = new LinkedList<>();
LinkedList<Integer> list2 = new LinkedList<>();
list1.add(1); -> 集合尾部添加元素([1])
list1.addFirst(2); -> 集合头部添加元素([2,1])
list1.addLast(3); -> 集合尾部添加元素([2,1,3])
list1.add(1, 4); -> 集合指定位置添加元素,原位置的元素统一后移([2,4,1,3])
list2.addAll(list1); -> 集合批量添加元素([2,4,1,3])
list2.addAll(0,list1); -> 集合指定位置批量添加元素([2,4,1,3])
Integer value = list1.get(0); -> 通过索引获取集合里的元素(2)
Integer value = list1.getFirst(); -> 获取集合头部元素(2)
Integer value = list1.getLast(); -> 获取集合尾部元素(3)
list1.set(0,5); -> 集合里指定位置替换元素([5,4,1,3])
list1.remove(); -> 默认移除集合里的头部元素([4,1,3])
list1.remove(0); -> 通过索引移除集合里的元素([1,3])
list1.remove((Integer)1); -> 移除集合里的指定元素([3])
list1.removeFirst(); -> 移除集合里的头部元素
list1.removeLast(); -> 移除集合里的尾部元素
list1.removeAll(list2); -> 集合里批量删除元素,求差集
Object[] array = list1.toArray(); -> 集合转变数组
list1.clear(); -> 清空数组

private static <T> List<T> page(List<T> list, int page, int size) { -> 分页原理
    int start = page * size; -> page从0开始
    int end = Math.min((start + size), list.size());
    return list.subList(start, end); -> start从0开始,end从1开始
}
三、Map 集合
  • HashMap:默认的初始化容量为16,负载因子为0.75,当集合的长度达到容量大小*负载因子的积时进行扩容,扩容后的大小为原来的2倍,集合的容量大小始终是2的n次幂,这也就说明自定义集合大小的时候,容量会选取大于或等于自定义数值的2次幂数值作为大小,JDK7前底层的实现是数组+链表,每次添加元素都放在链表的头部,JDK8后底层的实现改为数组+链表+红黑树,每次添加元素都放在链表的尾部,链表的长度大于等于8转为红黑树,长度小于等于6恢复为链表,7是临界点,避免链表因为频繁添加删除数据而导致链表和红黑树之间的频繁转换损耗了极大的性能。链表的平均查询时间复杂度为O(n)/2,红黑树的查询时间复杂度为O(logn),当n大于3时,红黑树的查询效率才会比链表高,这也是链表达到指定长度转换成红黑树的原因,线程不安全。LinkHashMap是HashMap的子类,其实现了写入和读取的有序,原理重写了Entry类加入before和after节点形成双向链表,相当于HashMap和LinkList的组合体。
  • HashTable:底层的实现同样是数组+链表,线程安全,使用synchronized关键字修饰方法,key和value都不允许为空。
  • TreeMap:底层的实现基于红黑树,红黑树是特殊的二叉查找树,特点是它始终保持着平衡的状态,根节点是黑色,红色节点的父节点和子节点都是黑色,节点的值比左孩子大比右孩子小,添加或移除元素的时候,通过自身的左旋和右旋来保持平衡,查询的时间复杂度为O(logn),线程不安全。

使用Map的时候,为什么更偏向使用String类型作为key,使用其他类型会带来哪些影响?
String类型声明的是常量,在JVM的编译期已经把这些常量放入常量池,运行期这些常量不会发生改变,使用其他类型声明的是变量,变量在运行期才确定具体的值,而且可以对变量的值进行修改,这样会导致变量修改前作为key值把数据存入map中,修改变量后,从map中通过key值取出数据时,value值为null的情况,修改前和修改后的变量显然是equals()不等的,这就无法获取之前存入map中的数据,综上所述,key值必须为不可改变的,所以String类型作为key值的使用频率较为高,当然也可以使用其它类型,不过前提是使用final修饰,确保这个变量不可发生变化。

map的put()过程是怎样的?

数组里的每一个位置称之为桶,那首先是定位桶的位置,通过key值调用Object类的hashcode()方法获取自身的hashcode,再通过map中的hash()方法对hashcode进行二次hash,目的是增强hash的分布性,hash函数取得再好也会有重复,这也是桶中的元素为什么使用链表,就是使元素更为均匀地分布在不同的桶中,减少单桶长链发生的可能性,如果key值为null,则放入第一个桶中,如果key值不为null,则通过二次hashcode除以数组的长度求余数得出桶的索引,如果桶里不为空,则通过递归将元素放入链表中,如果桶里为空,则判断数组是否需要扩容,如果需要扩容,则将原来的数组容量扩大两倍,取出原来桶中的所有元素并重新计算新桶的位置并添加元素,所以扩容是一个性能开销比较大的过程,如果不需要扩容,则通过递归将元素放入链表中。

map的get()过程是怎样的?

同样是先定位桶的位置,通过二次hashcode获取桶的索引,桶中存放的是JDK7称为Entry,JDK8称为Node的对象,其内部包含二次hash的hashcode、key值、value值、next对象指针,遍历桶中的链表的对象,如果get()方法的入参equals()该对象的key值,则返回value值,跳出循环,否则继续遍历直到完成后返回null。

如何减少map扩容带来的性能开销?

每次扩容的性能开销都很大,减少开销等同于减少扩容的次数,如果我们能在使用前明确map的具体用途,如存储量的大小、可能扩容的次数等,在创建map的时候调用带参的构造方法,自定义初始化容量和负载因子的大小,达到预期的目的。

HashMap<String, Object> map = new HashMap<>();
map.put("key","value"); -> 存入键值对
String value = (String) map.get("key"); -> 取值
map.remove("key"); -> 删除键值对
Set<String> keys = map.keySet(); -> 获取key的集合
Collection<Object> values = map.values(); -> 获取value的集合
int length = map.size(); -> 集合长度
map.clear(); -> 清空集合

Set<Map.Entry<String,Object>> entrySet = map.entrySet();
Iterator<Map.Entry<String,Object>>  iterator = entrySet.iterator();
while (iterator.hasNext()){ -> 遍历
    Map.Entry<String,Object> entry = iterator.next();
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
    System.out.println(entry.hashCode());
    iterator.remove(); -> 移除当前遍历中的元素
}
四、Collections 工具类
  • Java接口返回数据时,常会因权限限制、查询条件不足等原因逻辑校验不通过并返回一个空集合,建议使用Collections工具类来构建,其内部已创建静态不可变的空集合,减轻每次返回空集合时新创建空集合对Java内存堆的开销。
Set set = Collections.EMPTY_SET;
List list = Collections.EMPTY_LIST;
Map map = Collections.EMPTY_MAP;

List<Integer> list = Arrays.asList(2,3,1); -> 创建集合
Collections.sort(list); -> 集合里的元素从小到大排序(1,2,3)
Collections.reverse(list); -> 集合里的元素反转排序(1,3,2)
Collections.shuffle(list); -> 集合里的元素随机排序(2,1,3)
Collections.fill(list,4); -> 替换集合里的所有元素(4,4,4)
Collections.copy(list,Arrays.asList(3,2)); -> 按索引替换集合里的元素(3,2,1)
Collections.rotate(list,1); -> 集合元素移位[正右负左](1,2,3)
Collections.swap(list,0,2); -> 集合里指定索引的两个元素换位(1,2,3)
Collections.replaceAll(list,1,4); -> 替换集合中所有的指定元素(2,3,4)
Collections.binarySearch(list,2); -> 返回指定元素在集合里的索引(0)
Collections.frequency(list,1); -> 返回指定元素在集合里出现的次数(1)
Integer min = Collections.min(list); -> 返回集合里的最小值(1)
Integer max = Collections.max(list); -> 返回集合里的最大值(3)
五、Arrays 工具类
int[] array = new int[]{2, 3, 1}; -> 创建数组
Arrays.toString(array); -> 数组转字符串([2,3,1])
Arrays.sort(array); -> 数组里的元素从小到大排序(1,2,3)
Arrays.sort(array,0,3); -> 数组里的元素指定范围排序(1,2,3)
Arrays.fill(array,4); -> 替换数组里的所有元素(4,4,4)
Arrays.fill(array,0,3,4); -> 替换数组里指定范围的元素(4,4,4)
int[] copyArray = Arrays.copyOf(array,3); -> 复制数组(2,3,1)
int[] copyArray = Arrays.copyOfRange(array,0,3); -> 复制数组的指定范围(2,3,1)
boolean b = Arrays.equals(array,copeArray); -> 判断两个数组是否相等
int index = Arrays.binarySearch(array,1); -> 返回二叉查找元素的索引(-1)
Object[] array = Arrays.stream(array).map(Double::parseDouble).toArray(); -> 转换数组类型

相关文章

网友评论

      本文标题:Java 集合详解

      本文链接:https://www.haomeiwen.com/subject/fimumhtx.html