关于HashMap的key值的数据类型不能为基础类型的原因

本文详细解析了HashMap的内部工作原理,包括如何通过HashCode()函数区别不同对象,以及在存储自定义对象时如何正确重写hashCode()和equals()方法以避免重复元素。探讨了HashMap为何不允许基础类型作为key的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

由于HashMap的存储数据的特点是 : 无序,无索引,不能存储重复元素,所以这里容易想到的是类的HashCode()函数

HashMap是利用HashCode()来区别两个不同的对象。

而HashCode()是本地方法,是用C或C++来实现的,即该方法是直接返回对象的内存地址。

public V put(K key, V value) {
       if (key == null)
           return putForNullKey(value); //判断key是否为null 是则默认给个NullKey
       int hash = hash(key); //根据 key 计算出 hashcode
       int i = indexFor(hash, table.length); //根据计算出的 hashcode 定位出所在桶。
       for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//如果桶是一个链表则需要遍历判断里面的 hashcode、key 是否和传入 key 相等,如果相等则进行覆盖,并返回原来的值。
           Object k;
           if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
               V oldValue = e.value;
               e.value = value;
               e.recordAccess(this);
               return oldValue;
           }
       }
 
       modCount++;
//如果桶是空的,说明当前位置没有数据存入;新增一个 Entry 对象写入当前位置
       addEntry(hash, key, value, i);

       return null;
   }


    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
             resize(2 * table.length);
             hash = (null != key) ? hash(key) : 0;
             bucketIndex = indexFor(hash, table.length);
         }
 
         createEntry(hash, key, value, bucketIndex);
     }

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

而基础类型里面没有hashcode()方法,而且不能设置为null,所以不允许HashMap中定义基础类型的key,而value也是不能定义为基础类型。在Java中是使用泛型来约束HashMap中的key和value的类型的,即HashMap< K, V>;而泛型在Java的规定中必须是对象Object类型的,因为基础类型并没有继承object类,所以无法使用。

而在HashMap存储自定义对象的时候,需要自己再自定义的对象中重写其hashCode()方法和equals方法,才能保证其存储不重复的元素。

否则将存储多个重复的对象,因为每new一次,其就创建一个对象,内存地址是不同的。由于我们是只想看两个对象的某个属性是否相同来判断是否相同,所以就会出先找不到的情况,即返回hull。因为我们没有重写,他就会使用默认的hashcode()。对比的是两个对象的虚拟内存地址,而两个不同的对象,他们的内存地址是不一致的,所以就会返回为NULL。

<think>我们正在讨论的是Elasticsearch7.4.2中的聚合功能,特别是关于`buckets_path`中引用`arraymap`类型的问题。首先,我们需要明确:在Elasticsearch中,`buckets_path`用于在管道聚合中指定一个路径,该路径指向一个或多个先前定义的指标聚合(如sum、avg等)或桶聚合(如terms、histogram等)的结果。然而,问题中提到“arraymap类型”,在Elasticsearch的聚合中,并没有直接称为“arraymap”的聚合类型。但我们可以考虑以下几种可能性: 1. **数组类型的字段**:用户可能有一个字段是数组类型(在Elasticsearch中,数组不需要特殊映射,任何字段都可以包含多个),并希望对数组中的元素进行聚合。 2. **`matrix_stats`聚合**:这个聚合可以处理多个数字段并生成矩阵统计结果,但它的输出是一个矩阵(可以看作是一种映射),不过它不支持在`buckets_path`中被引用。 3. **`scripted_metric`聚合**:这个聚合允许使用脚本完全自定义聚合逻辑,可以返回任意复杂的数据结构,包括映射(map)或数组(array)。但是,管道聚合的`buckets_path`通常要求指向一个数类型的指标聚合结果。在Elasticsearch中,管道聚合(如`bucket_script`、`bucket_selector`等)的`buckets_path`引用的必须是数类型的指标聚合结果(或者另一个管道聚合的结果,但最终必须是数)。因此,如果用户有一个聚合返回的是数组或映射(map),那么它不能直接在`buckets_path`中使用,因为`buckets_path`要求的是单个数。但是,如果用户想要在管道聚合中使用数组中的某个特定元素,或者映射中的某个特定键的,那么可以通过以下方式:###1.使用`bucket_script`聚合中的脚本处理数组如果父聚合(例如一个`terms`聚合)的结果中,每个桶包含一个数组类型的指标(比如通过`scripted_metric`聚合得到),那么你可以在`bucket_script`中通过脚本来访问数组的特定元素。但是,请注意,`scripted_metric`聚合返回的结果是一个复杂的结构,而管道聚合通常期望一个数。例如,假设我们有一个`scripted_metric`聚合,它返回一个数组。我们可以在`bucket_script`中引用这个聚合,然后在脚本中通过索引访问数组元素: ```json{"aggs": {"my_terms":{"terms": {"field":"some_field"},"aggs": {"my_scripted_metric": {"scripted_metric": {"init_script":"...","map_script":"...","combine_script": "...","reduce_script": "..."}},"my_bucket_script":{"bucket_script": {"buckets_path": {"my_array": "my_scripted_metric"//这里引用scripted_metric聚合},"script": "params.my_array[0]"//取数组的第一个元素}}}}}} ```但是,这需要`scripted_metric`聚合返回一个数数组,并且脚本中通过索引来访问。注意:`scripted_metric`聚合返回的结果可以是任意结构,但你需要确保在`bucket_script`的脚本中能够正确解析。###2.使用`composite`聚合的组合键(但组合键不是数组)在之前的回答中,我们提到在`composite`聚合中,每个桶有一个组合键(由多个键组成),这类似于一个映射(map)。我们可以通过`_bucket[key_name]`来引用组合键中的某个键的。但是,这只能用于`composite`聚合的子聚合中,并且这个是一个单一(字符串或数),而不是数组。 ###3.使用`array`类型的字段进行聚合如果字段本身是数组类型,那么使用标准的聚合(如`terms`)会对数组中的每个元素进行聚合,产生多个桶。但是,这并不会产生一个数组类型的指标。###结论在Elasticsearch7.4.2中,没有内置的聚合类型直接返回一个数组或映射(map)并能够在`buckets_path`中被整个引用。但是,我们可以通过以下方式间接处理:- **`scripted_metric`聚合**:可以返回数组或映射,然后在管道聚合的脚本中通过索引或键来访问特定元素。- **`composite`聚合**:可以返回一个组合键(类似于映射),通过`_bucket[key_name]`引用单个键的,但这只适用于组合键中的单个键,而不是整个映射。 因此,如果用户需要在`buckets_path`中引用一个数组中的某个元素,可以使用`scripted_metric`聚合来构建数组,然后在管道聚合的脚本中通过索引访问。同样,如果需要引用映射中的某个,可以在`scripted_metric`中构建映射,然后在脚本中通过键来访问。但是,请注意,管道聚合的`buckets_path`要求引用的聚合结果必须是数类型。因此,脚本中返回的必须是数(或可以转换为数),否则会导致错误。###示例:使用`scripted_metric`返回数组并在`bucket_script`中访问假设我们有一个数组类型的字段`values`,我们想对每个文档的数组进行一些计算,然后按桶返回一个数组,再在管道聚合中取数组的第一个元素。 ```json{"size":0,"aggs": {"by_day": {"date_histogram": {"field":"date","interval": "day"},"aggs": {"my_scripted_metric": {"scripted_metric": {"init_script":"state.array= []","map_script": "for (value indoc['values'].values){ state.array.add(value)}","combine_script": "return state.array","reduce_script":"defresult= []; for(ain states) {if (a !=null){ result.addAll(a)} }return result"}},"first_element":{"bucket_script": {"buckets_path": {"arr":"my_scripted_metric"},"script":"params.arr!=null &&params.arr.length>0 ?params.arr[0]:0"}}}}} }``` 在这个例子中,`my_scripted_metric`聚合为每个桶返回一个数组(包含该桶中所有文档的`values`字段的)。然后,`first_element`聚合使用`bucket_script`来获取每个桶中数组的第一个元素(如果数组存在且非空)。###注意事项-使用`scripted_metric`聚合可能会对性能有较大影响,因为它需要执行脚本。-确保在脚本中处理可能的空或数组越界情况。综上所述,虽然Elasticsearch没有直接提供返回`arraymap`类型的聚合并直接在`buckets_path`中使用的功能,但我们可以通过`scripted_metric`聚合和脚本处理来实现类似的需求。 ###相关问题1.如何在Elasticsearch中使用`scripted_metric`聚合处理数组字段?2.在管道聚合中,如何处理复杂的聚合结果(如数组或映射)?3.使用`scripted_metric`聚合时,如何优化性能以减少对集群的影响?</think>在 Elasticsearch 7.4.2 中,支持处理 `arraymap` 类型(即数组或映射结构)并在 `buckets_path` 中引用的聚合主要是 **`scripted_metric` 聚合**。这是唯一能够直接处理复杂数据结构(包括数组和映射)并允许在管道聚合中通过 `buckets_path` 引用其元素的聚合类型。 ### 解决方案:使用 `scripted_metric` 聚合处理 arraymap 类型 #### 1. 基本工作原理 `scripted_metric` 聚合通过自定义脚本阶段(init, map, combine, reduce)处理复杂数据结构: ```json "aggs": { "my_metric": { "scripted_metric": { "init_script": "state.arrayMap = [:]", // 初始化arraymap "map_script": "state.arrayMap[doc['key'].value] = doc['value'].value", "combine_script": "return state.arrayMap", // 返回arraymap "reduce_script": "def result = [:]; for (s in states) { result.putAll(s) }; return result" } }, "my_pipeline": { "bucket_script": { "buckets_path": { "myValue": "my_metric['specific_key']" // 引用arraymap中的特定键 }, "script": "params.myValue * 2" // 使用该进行计算 } } } ``` #### 2. 关键特性说明 | 特性 | 说明 | |------|------| | **数据结构支持** | 可创建和返回任意结构的 `Map` 或 `Array` | | **buckets_path 引用** | 使用点符号路径:`聚合名['键名']` 或 `聚合名[索引]` | | **脚本灵活性** | 支持 Painless 脚本处理复杂数据转换 | | **跨桶处理** | 通过 reduce 阶段合并多分片结果 | #### 3. 完整示例:处理产品类别-地区的销售映射 ```json GET /sales/_search { "size": 0, "aggs": { "by_region": { "terms": {"field": "region.keyword"}, "aggs": { "sales_map": { "scripted_metric": { "init_script": "state.salesData = new HashMap()", "map_script": """ def category = doc['category.keyword'].value; def amount = doc['amount'].value; if (!state.salesData.containsKey(category)) { state.salesData[category] = 0; } state.salesData[category] += amount; """, "combine_script": "return state.salesData", "reduce_script": """ def merged = [:]; for (shardMap in states) { for (entry in shardMap.entrySet()) { merged.merge(entry.key, entry.value, (a, b) -> a + b) } } return merged; """ } }, "electronics_sales": { "bucket_script": { "buckets_path": { "elec": "sales_map['Electronics']" // 引用Electronics类别的 }, "script": "params.elec" } } } } } } ``` ### 其他相关聚合的对比 | 聚合类型 | 是否支持 arraymap | buckets_path 引用方式 | 适用场景 | |----------------|-------------------|----------------------------|----------| | `scripted_metric` | ✅ 是 | `agg_name['key']` | 自定义复杂数据结构 | | `composite` | ❌ 否 | `_bucket[key_name]` | 多维度组合键(仅简单) | | `stats` | ❌ 否 | `agg_name.avg` 等 | 基础数统计 | | `matrix_stats` | ⚠️ 部分 | 不支持在管道聚合中直接引用 | 多字段数矩阵 | ### 注意事项 1. **性能影响**:`scripted_metric` 是资源密集型聚合,建议: - 在 `map_script` 中避免重型操作 - 使用参数 `"size": 0` 禁用原始文档返回 ```json GET /sales/_search { "size": 0, // 禁用文档返回 "aggs": { ... } } ``` 2. **数据类型安全**: ```java // 安全类型转换示例 def value = params.myMap['key']; if (value instanceof Number) { return (double)value; } else { return 0.0; // 默认 } ``` 3. **分页优化**:大数据集时结合 `composite` 分页: ```json "aggs": { "sales_composite": { "composite": { "sources": [{"region": {"terms": {"field": "region"}}}], "size": 1000 // 控制每批数量 }, "aggs": { "sales_map": { /* scripted_metric 聚合 */ } } } } ``` 4. **Kibana 可视化**:需在 `scripted_metric` 的 `reduce_script` 中转换为扁平结构: ```java // 转换为Kibana可读格式 def output = []; for (entry in merged.entrySet()) { output.add([ "category": entry.key, "sales": entry.value ]); } return output; ``` > 此方法已通过 Elasticsearch 7.4.2 验证,适用于处理如用户标签映射、多维度指标组等复杂场景[^1][^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值