? 在Java世界中,有几种常用的集合。在实际使用过程中,可以被研发人员轻松的使用,但在数据结构底层中隐含着诸多学问。
只有了解了这些概念,才能说的上是真正掌握了知识。趁着自己年轻,不负大好时光,扩宽自己的知识视野。
? 另外,在Redis中也涉及到了Java集合的数据结构,原理近乎一致。
? Collection下属的子接口包含:List、Set、Queue(队列)、Deque(双端队列)
? 再细分:List(有序可重复)——>ArrayList(基于数组,但可以自动扩容)
? ——>LinkedList(基于双向链表)
? 1、初始容量为10,扩容是按照前一次容量大小的1.5倍进行扩容。
? 2、扩容后,将原来的数据导入新的ArrayList<>中。
? 1、ArrayList是基于数组的结构,所以在处理获取某一位置上的元素或者对某一位置进行赋值性能很高。
? 2、LinkedList是基于双向链表的结构,在处理插入和删除操作时效率高。只需要修改链表某一位置前后的指针结构就能完成此操作。
? 这两种集合都不是线程安全的。所以在多线程的环境下需要注意。可以使用同步代码块的方式,利用Collections.synchronizedList
? 使用Collections.synchronizedList
方法来创建一个线程安全的ArrayList
List<> synchronizedList = Collections.synchronizedList(new ArrayList<>());
? 或者使用Collections.synchronizedList
包装LinkedList
:
List<> synchronizedList = Collections.synchronizedList(new LinkedList<>());
? 这样创建的列表会在每个方法上都进行同步,确保线程安全。但请注意,这可能会导致性能开销,特别是在高并发环境中。但这样在高 并发的场景下会导致性能急剧下降。
? Collections是集合的一个工具类,源自于java.util包下。而CollectionUtil是由hutool提供。
Collection<String> collection = new ArrayList<>();
boolean isEmpty = CollectionUtil.isEmpty(collection);
Collection<String> collection = new ArrayList<>();
boolean isNotEmpty = CollectionUtil.isNotEmpty(collection);
Collection<String> collection = new ArrayList<>(Arrays.asList("apple", "banana", "orange"));
Collection<String> toRemove = Arrays.asList("banana", "orange");
CollectionUtil.removeAll(collection, toRemove);
Collection<String> collection = new ArrayList<>(Arrays.asList("apple", "banana", "orange"));
Collection<String> toRetain = Arrays.asList("banana", "orange");
CollectionUtil.retainAll(collection, toRetain);
Collection<String> collection = Arrays.asList("apple", "banana", "orange");
String result = CollectionUtil.join(collection, ",");
Collection<String> collection = Arrays.asList("apple", "banana", "orange");
String[] array = CollectionUtil.toArray(collection, String.class);
Collection<String> collection = Arrays.asList("apple", "banana", "orange");
String element = CollectionUtil.get(collection, 1); // 返回 "banana"
ArrayList<String> list = new ArrayList<>();
//末尾添加
list.add("A");
list.add("B");
list.add("C");
//指定位置添加
list.add(1, "E");
String element = list.get(0);
//方法一、增强for循环
for (String begin : list) {
System.out.println(item);
}
//方法二、迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
int size = list.size();
String[] array = list.toArray(new String[0]);
//这种写法和下面的意思是一致的,0不代表空间大小是0,而意味着创建一个与原ArrayList相同大小空间的数组。
String[] array = list.toArray(new String[list.size()]);
? 可以使用clone()方法,但这中复制是一种浅复制。
? 浅复制相当于在内存中新创建一个指针来指向由来的区域。本质上就是两个指针指向一块区域,学过数据结构应该了解这部分。
? 深复制相当于完全复制了一份新的。与原来的毫无关联。
? HashMap集合:(基于Key-Value键值对的集合)HashMap的键具有唯一性。
? HashMap是线程不安全的,如果想使用线程安全的需要采用JUC下的ConcurrentHashMap。
? 简单讲讲ConcurrentHashMap,在JDK1.7之前,这个实际上是有多个HashMap组成,利用分段锁,将每个细小的HashMap分成段(Segment),没有将整个ConcurrentHashMap锁住。采用Segment段+HashEntry(类似链表)的结构。
? 而在JDK1.8之后,采用Node(相对于HashEntry多了红黑树) + CAS(节点级别) + Synchronized的结构。
? CAS的含义是:为了保证数据的原子性,避免在多线程的环境下,其他的线程不能修改着个值。
? 线程A,假设内存中的值是10,期望值是10,更新值为11。可以将10->11。
? 线程B,期望值是10,此时内存为11,更新值是12。无法更新。
? 线程C,期望值是11,此时内存是11,更新值为12,可以将11->12.
?
? HashMap底层数据结构:在JDK1.7之前是采用数组+链表的结构,而在JDK1.8之后采用数组+链表+红黑树的结构来解决哈希冲突。
? HashMap提供了两个阈值,控制采用链表还是红黑树数据结构。**TREEIFY_THRESHOLD
**默认值为8,当桶中达到这个值会转变为红黑树。
? **UNTREEIFY_THRESHOLD
**默认值为6,当桶中的小于等于这个值会将红黑树转化为链表。
? 数组中,每个位置叫做哈希槽。你可以想象到有16块砖头,连续并排的排列在桌子上。这每一块砖头都代表着一个槽位。
? 现在有了槽,就可以向里面放入元素了。之前提到了HashMap是Key-Value的结构。
? 第一步:根据Key的hashcode()方法计算得到hash值。
? 第二步:再由计算得到的hash值&(数组长度-1)=槽位。
? 第三步:如果此时槽位里没有元素,直接放入就ok。
? 第四步:如果有多个Key计算之后得到的槽位是一致的,首先调用equals()方法计算槽中是否已经存在要插入的key,如果已经存在, 则将value替换。如果没有元素的equals()是相同的,这时候需要解决hash冲突,而可以选择拉链法。向每一个砖头下面继续悬挂重 物,再将value放入其中。
? 当达到一定的数组容量之后,就会触发扩容,在下面会讲到。
?
? 取出元素的步骤也相对容易,基本与放入元素没什么区别:
? 第一步:由查询的键值的hashcode()方法计算出hash值。
? 第二步:再由计算得到的hash值&(数组长度-1)=槽位,计算出所存放的位置。
? 第三步:如果槽位里没有元素,说明HashMap中没有存放次元素,返回null。
? 第四步:因为HashMap中key的唯一性,所以在槽中有元素的情况下,遍历链表或者红黑树。调用key的equals()方法找到该元素,将 value取出来。
? 首先,在初始化HashMap时需要指定容量大小,可以自主定义转载因子,默认是0.75。这个值是经过科学计算得到的。
? 假设初始容量为16(默认值),在集合容量超过了16*0.75。就会触发扩容机制,扩容的大小是初始容量的2倍,也就是32。之后将 原始HashMap中的元素转移至新的HashMap中。
? 在此,初始容量大小的设定也是有讲究的,为了充分的考虑HashMap的性能,在二进制中,2的N次幂只有首位的是1,剩下的位全 都是0。这样在解决哈希冲突时,就可以避免用除法取余数这种低效的方式处理,采用位运算的方式.
? 举个例子:
? 假设由key调用hashcode()方法计算出的hash值为188,二进制为1011 1100。采用hash&(length-1)得到12,二进制为0000 1100
? 因为length为16,二进制为0001 0000。减一后,15的二进制为0000 1111。做&运算,高位全都是0,只保留低位。
? 采用取余的算法,也是hash%length=12。但从效率上讲不如位运算,所以在选择初始容量大小时,尽量采取2的N次幂大小。
Map<String, Object> map = new HashMap<>(16);
?
插入元素:
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("one", 1);
hashMap.put("two", 2);
hashMap.put("three", 3);
获取元素:
int value = hashMap.get("two");
System.out.println("Value of 'two': " + value);
遍历元素:
遍历键:
for (String key : hashMap.keySet()) {
System.out.println("Key: " + key);
}
遍历值:
for (int val : hashMap.values()) {
System.out.println("Value: " + val);
}
遍历键值对:
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
判断是否包含某个键或值:
boolean containsKey = hashMap.containsKey("two");
boolean containsValue = hashMap.containsValue(3);
获取键值对数量:
int size = hashMap.size();
删除元素:
hashMap.remove("two");
清空 HashMap:
hashMap.clear();
判断是否为空:
boolean isEmpty = hashMap.isEmpty();
替换元素值:
hashMap.replace("one", 10);
获取默认值:
int defaultValue = hashMap.getOrDefault("four", 0);
? 因为HashMap实现了Serializable接口,所以可以被序列化或者反序列化。在实际使用中通常是以JSON格式。在举的例子中使用 Alibaba的FastJson工具。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.util.HashMap;
public class SerializeHashMapToFastjson {
public static void main(String[] args) {
// 创建一个HashMap
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("One", 1);
hashMap.put("Two", 2);
hashMap.put("Three", 3);
try {
// 将HashMap对象序列化为JSON字符串
String jsonString = JSON.toJSONString(hashMap);
System.out.println("Serialized HashMap to JSON: " + jsonString);
// 将JSON字符串反序列化为HashMap对象
HashMap<String, Integer> deserializedHashMap = JSON.parseObject(jsonString, new TypeReference<HashMap<String, Integer>>() {});
System.out.println("Deserialized HashMap from JSON: " + deserializedHashMap);
} catch (Exception e) {
e.printStackTrace();
}
}
}
?