<11>Java集合——HashMap

这篇博客详细解析了HashMap在JDK7和JDK8中的实现差异。在JDK7中,HashMap初始化时创建长度为16的Entry数组,插入元素时通过哈希算法确定位置,遇到冲突则使用链表解决。而在JDK8中,首次put时才创建数组,数组类型为Node[],并且当链表长度超过8且数组长度大于64时,链表转为红黑树。此外,博客还介绍了HashMap的默认容量、加载因子、扩容条件等属性,并给出了使用for-each迭代HashMap的示例。

1.HashMap在jdk7中实现原理:

HashMap map = new HashMap():
------->在实例化以后,底层创建了长度是16的一维数组Entry[] table。
------->…可能已经执行过多次put…
------->map.put(key1,value1):
------->首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
------->如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
------->如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
------->如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
------->如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
--------------------->如果equals()返回false:此时key1-value1添加成功。----情况3
--------------------->如果equals()返回true:使用value1替换value2。

补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。

在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原的数据复制过来。


2.HashMap在jdk8中相较于jdk7在底层实现方面的不同:

  1. new HashMap():底层没创建一个长度为16的数组
  2. jdk 8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

3.HashMap底层典型属性的属性的说明:

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

4.迭代 HashMap

可以使用 for-each 来迭代 HashMap 中的元素。

如果只想获取 key,可以使用 keySet() 方法,然后可以通过 get(key) 获取对应的 value,如果你只想获取 value,可以使用 values() 方法。

代码示例:

 @Test
    public void test01(){
        // 创建 HashMap 对象 map
        HashMap<Integer, String> map = new HashMap<>();

        // 添加键值对
        map.put(1,"xx");
        map.put(2,"yy");
        map.put(3,"zz");

        // 输出 key 和 value
        for(Integer key : map.keySet()){
            System.out.println("Key:" +key + " value:" + map.get(key));
        }
        // 返回所有 value 值
        for (String value : map.values()) {
            // 输出每一个value
            System.out.print(value + " ");
        }
    }

实验结果:
在这里插入图片描述

你提出的问题非常关键且专业: > ❗ `Map<String, List<String>> equipmentMap = new HashMap<>()` 是**无序集合**,不能保证 `"AM01"` 先处理、`"AM02"` 后处理。 > 而你的排产逻辑中,设备是有物理或工艺上的先后顺序的(比如 AM01 → AM02),必须按指定顺序执行调度。 --- ### ✅ 问题本质 Java 中: - `HashMap`:不保证插入顺序 ❌ - `LinkedHashMap`:**保证插入顺序** ✅ - `TreeMap`:按键自然排序或自定义排序 ✅ 如果你用的是 `HashMap`,即使你写成: ```java equipmentMap.put("AM01", ...); equipmentMap.put("AM02", ...); ``` 也不能保证遍历时一定是 AM01 先、AM02 后 —— **这是未定义行为!** --- ## ✅ 正确解决方案:使用有序 Map 或显式排序 --- ### ✅ 方案一:使用 `LinkedHashMap` 显式控制插入顺序(推荐) ```java Map<String, List<String>> equipmentMap = new LinkedHashMap<>(); // ✅ 保持插入顺序 equipmentMap.put("AM01", Arrays.asList("AM01-B1", "AM01-B2")); equipmentMap.put("AM02", Arrays.asList("AM02-B1", "AM02-B2")); ``` ✅ 遍历时会严格按照插入顺序迭代:先 AM01,再 AM02。 --- ### ✅ 方案二:使用 `List<Map.Entry<String, List<String>>>` 显式定义顺序 更灵活,适合配置化场景: ```java List<Map.Entry<String, List<String>>> equipmentSequence = new ArrayList<>(); equipmentSequence.add(new AbstractMap.SimpleEntry<>("AM01", Arrays.asList("AM01-B1", "AM01-B2"))); equipmentSequence.add(new AbstractMap.SimpleEntry<>("AM02", Arrays.asList("AM02-B1", "AM02-B2"))); // 使用时: for (Map.Entry<String, List<String>> entry : equipmentSequence) { String equipment = entry.getKey(); List<String> lines = entry.getValue(); // 处理逻辑... } ``` 优点: - 完全控制顺序 - 可动态加载配置(如数据库、JSON) - 支持更多设备扩展 --- ### ✅ 方案三:使用 `TreeMap` 按名称排序(若命名规范) ```java Map<String, List<String>> equipmentMap = new TreeMap<>(); // 自然排序:AM01 < AM02 equipmentMap.put("AM02-B2", ...); // 插入乱序也没关系 equipmentMap.put("AM01-B1", ...); // 遍历时自动按 key 排序输出 ``` ⚠️ 注意:仅当设备命名有规律(AM01, AM02...)且你想按名字排序时才适用。 但如果业务上 AM01 必须在 AM02 前,而某天来了个 AM10,自然排序会变成 AM01→AM02→AM10→AM3?错! 👉 所以 **不推荐依赖字符串排序**,应显式定义顺序。 --- ## ✅ 修改后的完整代码段(推荐方案一) ```java // Step 1: 基础排序 orderList.sort( Comparator.comparing(PpMachiningOrderList::getDemandDate) .thenComparing(PpMachiningOrderList::getDohdiff2) ); // Step 2: 过滤出四个指定产线 List<String> targetLines = Arrays.asList("AM01-B1", "AM01-B2", "AM02-B1", "AM02-B2"); Map<String, List<PpMachiningOrderList>> filteredByLine = orderList.stream() .filter(o -> targetLines.contains(o.getLine())) .collect(Collectors.groupingBy(PpMachiningOrderList::getLine)); // Step 3: 按设备合并,使用 LinkedHashMap 保证处理顺序 Map<String, List<String>> equipmentMap = new LinkedHashMap<>(); // ✅ 有序 equipmentMap.put("AM01", Arrays.asList("AM01-B1", "AM01-B2")); // 先处理 equipmentMap.put("AM02", Arrays.asList("AM02-B1", "AM02-B2")); // 后处理 // 存放最终结果(扁平化的订单列表) List<PpMachiningOrderList> result = new ArrayList<>(); // 逐设备处理 for (String equipment : equipmentMap.keySet()) { List<String> lines = equipmentMap.get(equipment); Map<String, List<List<PpMachiningOrderList>>> groupsPerLine = new HashMap<>(); for (String line : lines) { List<PpMachiningOrderList> orders = filteredByLine.getOrDefault(line, Collections.emptyList()); List<List<PpMachiningOrderList>> allGroups = mergeList(orders, mergeDays); groupsPerLine.put(line, allGroups); } List<List<PpMachiningOrderList>> line1Groups = groupsPerLine.get(lines.get(0)); List<List<PpMachiningOrderList>> line2Groups = groupsPerLine.get(lines.get(1)); int i = 0; while (i < line1Groups.size() || i < line2Groups.size()) { List<PpMachiningOrderList> group1 = i < line1Groups.size() ? line1Groups.get(i) : null; List<PpMachiningOrderList> group2 = i < line2Groups.size() ? line2Groups.get(i) : null; // ...(原有配对、调整、截取逻辑)... // ✅ 添加当前组(保持设备间顺序) if (group1 != null) { result.add(new ArrayList<>(group1)); } if (group2 != null) { result.add(new ArrayList<>(group2)); } i++; } } return result.stream().flatMap(List::stream).collect(Collectors.toList()); ``` --- ### ✅ 为什么这能解决“顺序错误”? | 问题 | 解法 | |------|------| | `HashMap` 无序 | 改用 `LinkedHashMap` ✅ | | 设备处理顺序不确定 | 显式插入顺序决定处理顺序 ✅ | | 扩展性差 | 可封装为配置项(见下方建议) | --- ### ✅ 进阶建议:将设备顺序外部化(可配置) ```java // 来自配置文件或数据库 List<String> equipmentOrder = Arrays.asList("AM01", "AM02", "AM03"); Map<String, List<String>> equipmentMap = new LinkedHashMap<>(); for (String eq : equipmentOrder) { if ("AM01".equals(eq)) { equipmentMap.put(eq, Arrays.asList("AM01-B1", "AM01-B2")); } else if ("AM02".equals(eq)) { equipmentMap.put(eq, Arrays.asList("AM02-B1", "AM02-B2")); } // 更多设备... } ``` 这样就可以通过配置调整流程顺序,无需改代码。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值