HashMap实现原理及源码分析

2023-10-27

  哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出现在各类的面试题中,重要性可见一斑。本文会对java集合框架中的对应实现HashMap的实现原理进行讲解,然后会对JDK7的HashMap源码进行分析。

目录

  一、什么是哈希表

  二、HashMap实现原理

  三、为何HashMap的数组长度一定是2的次幂?

  四、重写equals方法需同时重写hashCode方法

  五、总结

一、什么是哈希表

  在讨论哈希表之前,我们先大概了解下其他数据结构在新增,查找等基础操作执行性能

  数组:采用一段连续的存储单元来存储数据。对于指定下标的查找,时间复杂度为O(1);通过给定值进行查找,需要遍历数组,逐一比对给定关键字和数组元素,时间复杂度为O(n),当然,对于有序数组,则可采用二分查找,插值查找,斐波那契查找等方式,可将查找复杂度提高为O(logn);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度也为O(n)

  线性链表:对于链表的新增,删除等操作(在找到指定操作位置后),仅需处理结点间的引用即可,时间复杂度为O(1),而查找操作需要遍历链表逐一进行比对,复杂度为O(n)

  二叉树:对一棵相对平衡的有序二叉树,对其进行插入,查找,删除等操作,平均复杂度均为O(logn)。

  哈希表:相比上述几种数据结构,在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),接下来我们就来看看哈希表是如何实现达到惊艳的常数阶O(1)的。

  我们知道,数据结构的物理存储结构只有两种:顺序存储结构链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也这两种物理组织形式),而在上面我们提到过,在数组中根据下标查找某个元素,一次定位就可以达到,哈希表利用了这种特性,哈希表的主干就是数组

  比如我们要新增或查找某个元素,我们通过把当前元素的关键字 通过某个函数映射到数组中的某个位置,通过数组下标一次定位就可完成操作。

        存储位置 = f(关键字)

  其中,这个函数f一般称为哈希函数,这个函数的设计好坏会直接影响到哈希表的优劣。举个例子,比如我们要在哈希表中执行插入操作:

  

  查找操作同理,先通过哈希函数计算出实际存储地址,然后从数组中对应地址取出即可。

  哈希冲突

  然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。前面我们提到过,哈希函数的设计至关重要,好的哈希函数会尽可能地保证 计算简单散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式,

二、HashMap实现原理

 HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。

//HashMap的主干数组,可以看到就是一个Entry数组,初始值为空数组{},主干数组的长度一定是2的次幂,至于为什么这么做,后面会有详细分析。
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

 Entry是HashMap中的一个静态内部类。代码如下

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
        int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        } 

 所以,HashMap的整体结构如下

  

  简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

其他几个重要字段

//实际存储的key-value键值对的个数
transient int size;
//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
int threshold;
//负载因子,代表了table的填充度有多少,默认是0.75
final float loadFactor;
//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
transient int modCount;

HashMap有4个构造器,其他构造器如果用户没有传入initialCapacity 和loadFactor这两个参数,会使用默认值

initialCapacity默认为16,loadFactory默认为0.75

我们看下其中一个

public HashMap(int initialCapacity, float loadFactor) {
     //此处对传入的初始容量进行校验,最大不能超过MAXIMUM_CAPACITY = 1<<30(230)
if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity;
      init();//init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现 }

  从上面这段代码我们可以看出,在常规构造器中,没有为数组table分配内存空间(有一个入参为指定Map的构造器例外),而是在执行put操作的时候才真正构建table数组

  OK,接下来我们来看看put操作的实现吧

    public V put(K key, V value) {
        //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16)
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
       //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀
        int i = indexFor(hash, table.length);//获取在table中的实际位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
            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++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败
        addEntry(hash, key, value, i);//新增一个entry
        return null;
    }    

 先来看看inflateTable这个方法

private void inflateTable(int toSize) {
        int capacity = roundUpToPowerOf2(toSize);//capacity一定是2的次幂
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//此处为threshold赋值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值,capaticy一定不会超过MAXIMUM_CAPACITY,除非loadFactor大于1
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

  inflateTable这个方法用于为主干数组table在内存中分配存储空间,通过roundUpToPowerOf2(toSize)可以确保capacity为大于或等于toSize的最接近toSize的二次幂,比如toSize=13,则capacity=16;to_size=16,capacity=16;to_size=17,capacity=32.

 private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }

roundUpToPowerOf2中的这段处理使得数组长度一定为2的次幂,Integer.highestOneBit是用来获取最左边的bit(其他bit位为0)所代表的数值.

hash函数

//这是一个神奇的函数,用了很多的异或,移位等运算,对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀
final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

以上hash函数计算出的值,通过indexFor进一步处理来获取实际的存储位置

  /**
     * 返回数组下标
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

h&(length-1)保证获取的index一定在数组范围内,举个例子,默认容量16,length-1=15,h=18,转换成二进制计算为

        1  0  0  1  0
    &   0  1  1  1  1
    __________________
        0  0  0  1  0    = 2

  最终计算出的index=2。有些版本的对于此处的计算会使用 取模运算,也能保证index一定在数组范围内,不过位运算对计算机来说,性能更高一些(HashMap中有大量位运算)

所以最终存储位置的确定流程是这样的:

再来看看addEntry的实现:

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//当size超过临界阈值threshold,并且即将发生哈希冲突时进行扩容
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

  通过以上代码能够得知,当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时,需要新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。

三、为何HashMap的数组长度一定是2的次幂?

我们来继续看上面提到的resize方法

 void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

如果数组进行扩容,数组长度发生变化,而存储位置 index = h&(length-1),index也可能会发生变化,需要重新计算index,我们先来看看transfer这个方法

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
     //for循环中的代码,逐个遍历链表,重新计算索引位置,将老数组数据复制到新数组中去(数组不存储实际数据,所以仅仅是拷贝引用而已)
for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity);
          //将当前entry的next链指向新的索引位置,newTable[i]有可能为空,有可能也是个entry链,如果是entry链,直接在链表头部插入。 e.next
= newTable[i]; newTable[i] = e; e = next; } } }

  这个方法将老数组中的数据逐个链表地遍历,扔到新的扩容后的数组中,我们的数组索引位置的计算是通过 对key值的hashcode进行hash扰乱运算后,再通过和 length-1进行位运算得到最终数组索引位置。

  hashMap的数组长度一定保持2的次幂,比如16的二进制表示为 10000,那么length-1就是15,二进制为01111,同理扩容后的数组长度为32,二进制表示为100000,length-1为31,二进制表示为011111。从下图可以我们也能看到这样会保证低位全为1,而扩容后只有一位差异,也就是多出了最左位的1,这样在通过 h&(length-1)的时候,只要h对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致(大大减少了之前已经散列良好的老数组的数据位置重新调换),个人理解。

  

 还有,数组长度保持2的次幂,length-1的低位都为1,会使得获得的数组索引index更加均匀,比如:

  我们看到,上面的&运算,高位是不会对结果产生影响的(hash函数采用各种位运算可能也是为了使得低位更加散列),我们只关注低位bit,如果低位全部为1,那么对于h低位部分来说,任何一位的变化都会对结果产生影响,也就是说,要得到index=21这个存储位置,h的低位只有这一种组合。这也是数组长度设计为必须为2的次幂的原因。

  如果不是2的次幂,也就是低位不是全为1此时,要使得index=21,h的低位部分不再具有唯一性了,哈希冲突的几率会变的更大,同时,index对应的这个bit位无论如何不会等于1了,而对应的那些数组位置也就被白白浪费了。

get方法

 public V get(Object key) {
     //如果key为null,则直接去table[0]处去检索即可。
if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }

get方法通过key值返回对应value,如果key为null,直接去table[0]处检索。我们再看一下getEntry这个方法

final Entry<K,V> getEntry(Object key) {
            
        if (size == 0) {
            return null;
        }
        //通过key的hashcode值计算hash值
        int hash = (key == null) ? 0 : hash(key);
        //indexFor (hash&length-1) 获取最终数组索引,然后遍历链表,通过equals方法比对找出对应记录
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && 
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }    

  可以看出,get方法的实现相对简单,key(hashcode)-->hash-->indexFor-->最终索引位置,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。要注意的是,有人觉得上面在定位到数组位置之后然后遍历链表的时候,e.hash == hash这个判断没必要,仅通过equals判断就可以。其实不然,试想一下,如果传入的key对象重写了equals方法却没有重写hashCode,而恰巧此对象定位到这个数组位置,如果仅仅用equals判断可能是相等的,但其hashCode和当前对象不一致,这种情况,根据Object的hashCode的约定,不能返回当前对象,而应该返回null,后面的例子会做出进一步解释。

四、重写equals方法需同时重写hashCode方法

  关于HashMap的源码分析就介绍到这儿了,最后我们再聊聊老生常谈的一个问题,各种资料上都会提到,“重写equals时也要同时覆盖hashcode”,我们举个小例子来看看,如果重写了equals而不重写hashcode会发生什么样的问题

/**
 * Created by chengxiao on 2016/11/15.
 */
public class MyTest {
    private static class Person{
        int idCard;
        String name;

        public Person(int idCard, String name) {
            this.idCard = idCard;
            this.name = name;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()){
                return false;
            }
            Person person = (Person) o;
            //两个对象是否等值,通过idCard来确定
            return this.idCard == person.idCard;
        }

    }
    public static void main(String []args){
        HashMap<Person,String> map = new HashMap<Person, String>();
        Person person = new Person(1234,"乔峰");
        //put到hashmap中去
        map.put(person,"天龙八部");
        //get取出,从逻辑上讲应该能输出“天龙八部”
        System.out.println("结果:"+map.get(new Person(1234,"萧峰")));
    }
}

实际输出结果:

结果:null

  如果我们已经对HashMap的原理有了一定了解,这个结果就不难理解了。尽管我们在进行get和put操作的时候,使用的key从逻辑上讲是等值的(通过equals比较是相等的),但由于没有重写hashCode方法,所以put操作时,key(hashcode1)-->hash-->indexFor-->最终索引位置 ,而通过key取出value的时候 key(hashcode1)-->hash-->indexFor-->最终索引位置,由于hashcode1不等于hashcode2,导致没有定位到一个数组位置而返回逻辑上错误的值null(也有可能碰巧定位到一个数组位置,但是也会判断其entry的hash值是否相等,上面get方法中有提到。)

  所以,在重写equals的方法的时候,必须注意重写hashCode方法,同时还要保证通过equals判断相等的两个对象,调用hashCode方法要返回同样的整数值。而如果equals判断不相等的两个对象,其hashCode可以相同(只不过会发生哈希冲突,应尽量避免)。

五、总结

  本文描述了HashMap的实现原理,并结合源码做了进一步的分析,也涉及到一些源码细节设计缘由,最后简单介绍了为什么重写equals的时候需要重写hashCode方法。希望本篇文章能帮助到大家,同时也欢迎讨论指正,谢谢支持!

转载于:https://www.cnblogs.com/chengxiao/p/6059914.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HashMap实现原理及源码分析 的相关文章

  • 指纹奇异点检测

    我正在尝试确定指纹的核心点和增量点 我正在使用庞加莱指数方法 但我无法成功检测到这一点 而且我不明白为什么 First I divide the image in 15x15 blocks then I calculate the x an
  • 如何使用 Java 创建多个模式连接?

    我必须使用两个数据库 DB2 Oracle 我在 DB2 数据库中有一个名为NAVID 我想使用 Java 为 Oracle 中的所有表创建相同的架构 public class automateExport static String va
  • 如何防止在 CXF Web 服务客户端中生成 JAXBElement

    我正在尝试使用 CXF 创建一个 Web 服务客户端来使用 WCF Web 服务 当我使用 wsdl2java 时 它生成具有 JAXBElement 类型而不是 String 的对象 我读到有关使用 jaxb bindings xml 文
  • Java中定义类型后同时初始化多个变量?

    这里需要一些语法方面的帮助 我正在尝试在定义类型后重新初始化多个变量 例如 int bonus sales x y 50 这工作正常 但是我想稍后在程序中将不同的值放入其中一些变量中 但我收到语法错误 bonus 25 x 38 sales
  • 以点作为分隔符分割字符串

    我想知道我是否要在一个字符串上分割字符串 正确的方式 我的代码是 String fn filename split return fn 0 我只需要字符串的第一部分 这就是我返回第一项的原因 我问这个是因为我在 API 中注意到 意味着任何
  • WebLogic 10 中的临时目录

    每当 WL 停止时 它都不会删除其临时目录 即 domains mydomain servers myserver tmp WL TEMP APP DOWNLOADS domains mydomain servers myserver tm
  • 从剪贴板获取图像 Awt 与 FX

    最近 我们的 Java FX 应用程序无法再从剪贴板读取图像 例如 用户在 Microsofts Paint 中选择图像的一部分并按复制 我不是在谈论复制的图像文件 它们工作得很好 我很确定它过去已经有效 但我仍然需要验证这一点 尽管如此
  • org.postgresql.util.PSQLException:协议错误。会话设置失败

    我知道这些类型的问题已经存在 但提供的解决方案对我不起作用 在我的应用程序中 没有版本不匹配的黑白驱动程序和 PostgreSQL 服务器 我还没有找到任何其他解决方案 我正在使用 PostgreSQL 服务器 9 4 和 postgres
  • 通过 JNI 从 Applet 调用 DLL

    我有一个 概念验证 的作品 它跨越了一些不熟悉的领域 我的任务是将 EFTPOS 机器连接到在内联网浏览器中作为小程序运行的应用程序 我暂时忽略了 EFTPOS dll 并用我选择的语言 Delphi 创建了一个简单的 JNI 修饰的 DL
  • 用于防止滥用的 Servlet 过滤器? (DoS、垃圾邮件等)

    我正在寻找一个 Servlet 过滤器库 它可以帮助我保护我们的 Web 服务免受未经授权的使用和 DDoS 攻击 我们的网络服务有 授权客户 因此理想情况下 过滤器将帮助检测未经授权或行为不当的客户 或检测使用同一帐户的多个人 此外 我们
  • 无法在 Mac OS X 上启动应用程序 我收到错误 LSOpenURLsWithRole() 应用程序失败,错误为 -10810

    问题 我正在尝试启动一个应用程序 遗传网络分析仪 http www genostar com category products gna 但它默默地失败了 使用时open gna app产生以下错误消息 LSOpenURLsWithRole
  • 如何使用 Spring MVC 和 Thymeleaf 添加静态文件

    我的问题是如何添加 CSS 和图像文件等静态文件 以便我可以使用它们 我正在使用 Spring MVC 和 Thymeleaf 我查看了有关此主题的各种帖子 但它们对我没有帮助 所以我才来问 根据这些帖子 我将 CSS 和图像文件放在res
  • 为什么 RMI 注册表忽略 java.rmi.server.codebase 属性

    我正在运行 java RMI 的 Hello World 示例 1 我在空文件夹中运行注册表 motta motta laptop tmp rmiregistry 2 我启动 HTTP 服务器以在运行时检索类 下载文件夹包含客户端 服务器的
  • setKeyListener 将覆盖 setInputType 并更改键盘

    大家好 我在两个设备之间遇到问题 在实践中使用InputType和KeyListener我正在操纵一个EditText让它从数字键盘接收逗号和数字 有关更多背景信息 请检查我之前的question https stackoverflow c
  • 如何使用 Hibernate Session.doWork(...) 进行保存点/嵌套事务?

    我正在使用 JavaEE JPA 托管事务与 Oracle DB 和 Hibernate 并且需要实现某种嵌套事务 据我所知 此类事情不受开箱即用的支持 但我应该能够为此目的使用保存点 正如建议的https stackoverflow co
  • 如何使用 SAX Java 解析器读取注释文本

    我只想使用 Java 中的 SAX 解析器读取 XML 文件中对象标记的注释 这是我的文件的摘要
  • BoneCP 和 Derby - 如何正确关闭

    I have BoneCP CONNECTION POOL CONNECTION POOL getConfig setJdbcUrl jdbc derby database shutdown true Connection connecti
  • 当我在 Java 中输入 IP 时无法连接到我的服务器

    好的 我正在尝试学习 Java 客户端 服务器的内容 并且正在浏览教程代码 如下所示 当我将 localhost 更改为我的 IP 时 它会停止工作 请帮忙 编辑 127 0 0 1 似乎也可以工作 但不是我的真实IP Copyright
  • C/C++ 通过 Android NDK 在 JNI 中看不到 Java 方法

    我正在尝试从使用 NDK 构建的 C 类文件调用 Java 方法 它不断抛出常见的 未找到非静态方法 错误并导致整个 Android 应用程序崩溃 下面的代码片段 有些东西可能不需要 但我按原样保留它们 因为焦点 问题在于refreshJN
  • java中的回调是什么[重复]

    这个问题在这里已经有答案了 可能的重复 什么是回调函数 https stackoverflow com questions 824234 what is a callback function 我已经阅读了回调的维基百科定义 但我仍然没有明

随机推荐

  • (二)Qt下多线程实现多个海康工业相机内触发采集回调取流显示

    系列文章目录 提示 这里是该系列文章的所有文章的目录 第一章 一 Qt下实现多个海康工业相机内触发采集回调取流显示 第二章 二 Qt下多线程实现多个海康工业相机内触发采集回调取流显示 文章目录 系列文章目录 前言 一 创建线程类 二 关联线
  • 【备忘录】英译汉接口

    import requests import random import json from hashlib import md5 Set your own appid appkey appid 2xxx appkey kxxx For l
  • 数字电路-二进制转BCD码

    BCD码实际上就是将原本的十进制数的每一位用一个4位二进制数表示 每一位0 9 二进制4位能够表达的数字范围是0 15 由此可见BCD码的一段与普通四位二进制来表示十进制位有6的进制差 所以这就是二进制转化为BCD码的关键所在 下面来讲讲主
  • TP5 一对多关联查询

    A表 car 购物车 goodid 商品ID sizeid 规格ID B表 commondity 商品表 id 对应goodid 商品名称 title C表 size 规格表 id对应sizeid 规格名称 text 在购物车页面 我们需要
  • CMake 编译时出现错误 coffe转换到 COFF 期间失败: 文件无效或损坏

    转 https blog csdn net feiyang2010jin article details 46576463 C Windows Microsoft NET Framework v4 0 30319 D Program Fil
  • 如何修复d3dcompiler_47.dll缺失?多种解决方法分享

    在使用Windows操作系统的过程中 有时候会遇到d3dcompiler 47 dll缺失的情况 这个问题可能会导致某些应用程序无法正常运行 因此需要及时解决 本文将介绍如何修复d3dcompiler 47 dll缺失的问题 一 什么是d3
  • 服务器怎么用u盘传文件进去_本机向windows服务器传输文件的三种方法

    闲来无事 在腾讯云上申请了一个免费的服务器 想将自己写的网页发布到服务器上 服务器的申请很简单 百度搜索 腾讯云 然后新人第一次注册能申请到免费一个月的云主机 虽然配置不怎么高 但是还是能用的 这是我申请到一个云主机 那么如何将我们本机的文
  • 关于在宸曜科技7160gc上的ubuntu16.04系统下计算资源浪费问题

    1 如果使用核显来显示会占用cpu的资源 即使此时hdmi的线插在显卡上 此时会影响使用cpu来执行程序的效率 最直接体现的是程序的数据发送频率会降低很多 2 如何确定是否是cpu显示还是显卡显示了 需要通过system setting里面
  • 南科大于仕琪团队招聘研究助理教授,博士后

    南方科技大学于仕琪副教授团队招聘研究助理教授 博士后 博士生和硕士生 团队特色 专注步态识别方向十余年 在步态识别方向具有较高的影响力 研究助理教授 研究方向 步态识别 行人视频合成 图像和视频中的人体建模 视频中人的行为分析及相关 要求
  • 分布式系统架构——CAP理论

    1 什么是CAP 在分布式系统中 任何存储系统 有状态服务 都会涉及到CAP定理 Consistency 一致性 简称C 在同一时刻所有节点是具有同样的数据副本 每个节点的数据要保证实时同步 Availability 可用性 简称A 对于一
  • Ubuntu14.04 :By not providing "FindEigen3.cmake" in CMAKE_MODULE_PATH

    Ubuntu14 04 By not providing FindEigen3 cmake in CMAKE MODULE PATH 今天下载了hector slam的源码想要跑一下 结果编译不能通过 并且一直出现如下错误 烦得一逼 研究了
  • 兼容性测试真实案例-餐饮APP

    兼容性测试真实案例 某餐饮APP 项目背景 APP是以电商加直播为载体 供应链为核心 辅以短视频和直播内容分销带货 以及积分商城体系 本项目主要对APP的商品列表及商品采购流程及涉及页面 针对不同品牌 操作系统 分辨率的机型 验证APP是否
  • jenkins和jdk安装教程(安装支持jdk8的最新版本)

    1 安装版本查看 Jenkins稳定版 2 进入清华镜像 下载对应版本的rpm包 清华镜像 3 已rpm安装过Jenkins 现在先卸载 1 卸载 rpm e jenkins 2 检查是否卸载成功 rpm ql jenkins 3 彻底删除
  • (大集合)AI工具和用法汇总—集合的集合

    AI 工具和用法汇总 汇集整理 by Staok 瞰百 源于相关资料在我这慢慢越积累越多 到了不得不梳理的程度 文中有许多内容作者还没有亲自尝试 所以很多内容只是罗列 但信息大源都已给出 授人以渔 欢迎 PR 补充细节内容 比如 好的教程
  • HTML+CSS简单复习

    目录 什么是HTML CSS VSCode编辑器 HTML基本结构和属性 HTML初始代码 HTML注释 标题与段落 文本修饰标签 图片标签 路径的引入 链接标签 锚点 特殊字符 列表标签 表格标签 表单标签 div和span CSS基础语
  • 【机器学习笔记4】逻辑回归模型

    目录 什么是逻辑回归 Sigmoid函数 决策边界 逻辑回归的损失函数 为什么平方误差模型不可行 对数损失函数 单个样例损失 整体损失函数 梯度下降算法 补充 F1 score评价指标 F1 Score简介 相关概念 F Score 示例及
  • 第一次实践一个多文件的代码

    收获 1 ifndef防止头文件重复包含 为了避免同一个头文件被包含 include 多次 C C 中有两种宏实现方式 一种是 ifndef方式 另一种是 pragma once方式 ifndef 标识符A 每一个头文件都要有自己独特的标识
  • 小程序通过webview实现本地任意文件上传

    微信小程序做文件上传的时候 只能选择相册的图片或者视频 没办法选择手机内存卡里的文件 比如 word pdf文件等等 下面可以通过 webview 的方式 借用 h5 的方式即可实现上面的功能 添加业务域名 webview里面打开的地址 首
  • 【pytorch】pytorch-yolov3拍照并保存,进行检测后遍历所有图片并显示图片

    参看摄像头参数 v4l2 ctl d dev video0 all 安装numpy sudo apt get install python numpy 下载onnx pip install onnx 效果图 拍照部分 url http 19
  • HashMap实现原理及源码分析

    哈希表 hash table 也叫散列表 是一种非常重要的数据结构 应用场景及其丰富 许多缓存技术 比如memcached 的核心其实就是在内存中维护一张大的哈希表 而HashMap的实现原理也常常出现在各类的面试题中 重要性可见一斑 本文