HashMap1.7和1.8源码解析

news2024/11/17 1:50:16

1.HashMap

(1)数据结构

在JDK1.7中,HashMap中的数据结构是数组+单链表的组合;在JDK1.8中的HashMap存储结构是由数组、链表、红黑树这三种数据结构形成。

(2)JDK1.7中HashMap源码分析

(2.1)首先看一张图片:

这张图片非常清晰直观地表示了HashMap底层的数据结构,即数组+链表。 (2.2)实现原理 成员变量:

	/** 初始容量,默认16 */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /** 最大初始容量,2^30 */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /** 负载因子,默认0.75,负载因子越小,hash冲突机率越低 */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /** 初始化一个Entry的空数组 */
    static final Entry<?,?>[] EMPTY_TABLE = {};

    /** 将初始化好的空数组赋值给table,table数组是HashMap实际存储数据的地方,并不在EMPTY_TABLE数组中 */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

    /** HashMap实际存储的元素个数 */
    transient int size;

    /** 临界值(HashMap 实际能存储的大小),公式为(threshold = capacity * loadFactor) */
    int threshold;

    /** 负载因子 */
    final float loadFactor;

    /** HashMap的结构被修改的次数,用于迭代器 */
    transient int modCount;
复制代码

构造方法:

public HashMap(int initialCapacity, float loadFactor) {
        // 判断设置的容量和负载因子合不合理
        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);
        // 设置负载因子,临界值此时为容量大小,后面第一次put时由inflateTable(int toSize)方法计算设置
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);
        putAllForCreate(m);
    }
复制代码

put方法:

public V put(K key, V value) {  
    // 如果table引用指向成员变量EMPTY_TABLE,那么初始化HashMap(设置容量、临界值,新的Entry数组引用)
    if (table == EMPTY_TABLE) {//如果当前数组为空
        inflateTable(threshold);//延迟加载
    }
    // 若“key为null”,则将该键值对添加到table[0]处,遍历该链表,如果有key为null,则将value替换。没有就创建新Entry对象放在链表表头
    // 所以table[0]的位置上,永远最多存储1个Entry对象,形成不了链表。key为null的Entry存在这里 
    if (key == null)  
        return putForNullKey(value);  
    // 若“key不为null”,则计算该key的哈希值
    int hash = hash(key);  
    // 搜索指定hash值在对应table中的索引
    int i = indexFor(hash, table.length);  
    // 循环遍历table数组上位置为i的链表,链表有Entry对象组成,用于判断该位置上key是否已存在
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
        Object k;  
        // 哈希值相同并且对象相同
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
            // 如果这个key对应的键值对已经存在,就用新的value代替老的value,然后退出!
            V oldValue = e.value;  
            e.value = value;
            //调用value的回调函数,其实这个函数也为空实现  
            e.recordAccess(this);  
            return oldValue;  
        }  
    }  
    // 修改次数+1
    modCount++;
    // table数组中没有key对应的键值对,就将key-value添加到table[i]处 
    addEntry(hash, key, value, i);  
    return null;  
}
    /**
     * Inflates the table.
     */
  private void inflateTable(int toSize) {
       // Find a power of 2 >= toSize
       //一个很重要的方法,返回一个比toSize大或相等的2的n次幂的整数
       int capacity = roundUpToPowerOf2(toSize);

       threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
       table = new Entry[capacity];
       initHashSeedAsNeeded(capacity);
   }
    /**
     * 将一个数换算成2的n次幂
     * @param number
     * @return
     */
    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;
        // 理解 Integer.highestOneBit((number - 1) << 1)
        // 比如 number = 23,23 - 1 = 22,二进制是:10110
        // 22 左移一位(右边补1个0),结果是:101100
        // Integer.highestOneBit() 函数的作用是取左边最高一位,其余位取0,
        // 即:101100 -> 100000,换成十进制就是 32
    }
   
复制代码

要研究roundUpToPowerOf2方法,必须看看下面的方法,该方法返回一个比i小的或相等的2的整数次幂:

 public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1); 
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }
复制代码

将i的二进制表示右移,并与i自身做异或运算,例如10的二进制表示为0000 1010,10>>1的二进制表示为0000 0101,再异或运算得到:0000 1111,此时i=0000 1111。再进行后面的运算即可。最后的结果为8,方法验证成功。然后结合Integer.highestOneBit((number - 1) << 1) ,就可以得到一个比number大或相等的2的n次幂的整数。 总之,初始化数组的时候,HashMap的容量总是一个2的整数次幂。

计算Hash值的方法

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

        h ^= k.hashCode();

        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
复制代码

返回数组下标的方法:

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

h&(length-1)保证获取的index一定在数组范围内,举个例子,默认容量16,length-1=15,h=18,转换成二进制计算,最终计算出的index=2。有些版本的对于此处的计算会使用取模运算,也能保证index一定在数组范围内,不过位运算对计算机来说,性能更高一些(HashMap中有大量位运算)。使用 h & (length-1)是为了使得数组的下标肯定在[0,length-1]内。相当于求余,效率高。 HashCode是通过Key算出来的,Key的HashCode直接决定了这个K-V对放在数组的哪个位置上。这个缺点还是有的,如果多个K-V对放在同一个位置,会导致数组对应的链表很长很长,会影响到get的效率。 将K-V对象放入到链表中:

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);//当size超过临界阈值threshold,并且即将发生哈希冲突时进行扩容,新容量为旧容量的2倍
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);//扩容后重新计算插入的位置下标
        }

        //把元素放入HashMap的桶的对应位置
        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++;//元素个数+1  
    }  
复制代码

这里采用的是头插法 扩容操作

 //按新的容量扩容Hash表  
    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;//修改HashMap的底层数组  
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//修改阀值  
    }  
复制代码
//将老的表中的数据拷贝到新的结构中  
    void transfer(Entry[] newTable, boolean rehash) {  
        int newCapacity = newTable.length;//容量  
        for (Entry<K,V> e : table) { //遍历所有桶
            while(null != e) {  //遍历桶中所有元素(是一个链表)
                Entry<K,V> next = e.next;  
                if (rehash) {//如果是重新Hash,则需要重新计算hash值  
                    e.hash = null == e.key ? 0 : hash(e.key);  
                }  
                int i = indexFor(e.hash, newCapacity);//定位Hash桶  
                e.next = newTable[i];//元素连接到桶中,这里相当于单链表的插入,总是插入在最前面
                newTable[i] = e;//newTable[i]的值总是最新插入的值
                e = next;//继续下一个元素  
            }  
        }  
    }  
复制代码

JDK1.7的HashMap在多线程的扩容情况下(“transfer”方法)会出现问题,容易出现循环链表。

(3)JDK1.8中HashMap源码分析及改进

节点类:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
复制代码

成员变量

	//默认初始容量,即数组长度为16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //最大容量,2的30次方
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //负载因子为0.75,可以确保一个拉姆达为0.5的泊松分布,使得时间和空间复杂度最优
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //链表转为红黑树的阈值为8,节点类从Node改为TreeNode
    static final int TREEIFY_THRESHOLD = 8;
    //红黑树转为链表的阈值为6,节点类从TreeNode改为Node
    static final int UNTREEIFY_THRESHOLD = 6;
  	//Node[] table 数组长度转换为红黑树的阈值
    static final int MIN_TREEIFY_CAPACITY = 64;
复制代码

即使 hash 算法 和负载因子设计的再完美,也避免不了拉链过长的情况,一旦出现拉链过长,严重影响 HashMap 的性能,于是在 JDK1.8 中对数据结构做了进一步的优化,引入了红黑树。当链表长度太长(超过 TREEIFY_THRESHOLD = 8)时,当Node[] table 数组长度超过 64(MIN_TREEIFY_THRESHOLD = 64) 时,链表就转化为了红黑树,利用红黑树快速增删改查的特点提高 HashMap 的性能。

构造方法:

public HashMap(int initialCapacity, float loadFactor) {
        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;
        this.threshold = tableSizeFor(initialCapacity);
    }
复制代码

有几个说法,与JDK1.7中的有所区别:

  • 一般将数组中的每一个元素称作桶(segment),桶中连的链表或者红黑树中的每一个元素成为bin

  • capacity: 源码中没有将它作为属性,但是为了方便,引进了这个概念,是指HashMap中桶的数量。默认值为16。扩容是按照原容量的2倍进行扩。如果在构造函数中指定了Map的大小,那么进行put操作时,初始化后的容量为离传入值最近的2的整数幂,是通过tableSizeFor() 函数达到该目的。总之,容量都是2的幂。 设计成16的好处是可以使用按位与替代取模来提升hash的效率。

  • loadFactor: 译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。

  • threshold: threshold表示当HashMap的size大于threshold时会执行resize操作。threshold = capacity*loadFactor

  • DEFAULT_INITIAL_CAPACITY : 默认初始化容量 16。容量必须为2的次方。默认的hashmap大小为16.

  • MAXIMUM_CAPACITY :最大的容量大小2^30

  • DEFAULT_LOAD_FACTOR: 默认resize的因子。0.75,即实际数量超过总数DEFAULT_LOAD_FACTOR的数量即会发生resize动作。在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1

  • TREEIFY_THRESHOLD: 树化阈值 8。当单个segment的容量超过阈值时,将链表转化为红黑树。

  • UNTREEIFY_THRESHOLD :链表化阈值 6。当resize后或者删除操作后单个segment的容量低于阈值时,将红黑树转化为链表。

  • MIN_TREEIFY_CAPACITY :最小树化容量 64。当桶中的bin被树化时最小的hash表容量,低于该容量时不会树化。

HashMap扩容及其树化的具体过程

  • 如果在创建 HashMap 实例时没有给定capacity、loadFactor则默认值分别是16和0.75。
  • 随着put进HashMap的元素增加,不可避免会发生哈希冲突,当较多bin被映射到同一个桶时,会形成一个较长的链表;如果这个桶(链表)中bin的数量小于等于TREEIFY_THRESHOLD(8),显然不会转化成树形结构存储;
  • 如果这个桶中bin的数量大于了 TREEIFY_THRESHOLD(8) ,但是capacity小于MIN_TREEIFY_CAPACITY (64)则依然使用链表结构进行存储,此时会对HashMap进行扩容,扩容的目的是为了缩短链表的长度;
  • 如果capacity大于了MIN_TREEIFY_CAPACITY ,才有资格进行树化(当bin的个数大于8时)。

综上所述,HashMap的树化,要受到两个因素的制约,一个是capacity(数组的长度,桶的数量),另一个是bin的个数(链表的长度,桶的容量)。 当capacity<64,bin<8时,桶维持链表的数据结构; 当capacity<64,bin>=8时,数组会扩容; 当capacity>=64,bin>=8时,链表会转为红黑树,当bin<=6时,红黑树会转为链表。

hash 值的计算

  • 根据存入的key-value对中的key计算出对应的hash值,然后放入对应的桶中,所以好的hash值计算方法十分重要,可以大大避免哈希冲突。
  • HashMap是以hash操作作为散列依据。但是又与传统的hash存在着少许的优化。其hash值是key的hashcode与其hashcode右移16位的异或结果。在put方法中,将取出的hash值与当前的hashmap容量-1进行与运算。得到的就是位桶的下标。那么为何需要使用key.hashCode() ^ h>>>16的方式来计算hash值呢。其实从微观的角度来看,这种方法与直接去key的哈希值返回在功能实现上没有差别。但是由于最终获取下表是对二进制数组最后几位的与操作。所以直接取hash值会丢失高位的数据,从而增大冲突引起的可能。由于hash值是32位的二进制数。将高位的16位于低位的16位进行异或操作,即可将高位的信息存储到低位。因此该函数也叫做扰乱函数。目的就是减少冲突出现的可能性。而官方给出的测试报告也验证了这一点。直接使用key的hash算法与扰乱函数的hash算法冲突概率相差10%左右。
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    n = table.length;
    index = (n-1) & hash;
复制代码

根据以上可知,hashcode是一个32位的值,用高16位与低16位进行异或,原因在于求index是是用 (n-1) & hash ,如果hashmap的capcity很小的话,那么对于两个高位不同,低位相同的hashcode,可能最终会装入同一个桶中。那么会造成hash冲突,好的散列函数,应该尽量在计算hash时,把所有的位的信息都用上,这样才能尽可能避免冲突。这就是为什么用高16位与低16位进行异或的原因。 为什么capcity是2的幂? 因为 算index时用的是(n-1) & hash,这样就能保证n -1是全为1的二进制数,如果不全为1的话,存在某一位为0,那么0,1与0与的结果都是0,这样便有可能将两个hash不同的值最终装入同一个桶中,造成冲突。所以必须是2的幂。 在算index时,用位运算(n-1) & hash而不是模运算 hash % n的好处(在HashTable中依旧是取模运算)? 位运算消耗资源更少,更有效率 避免了hashcode为负数的情况

put 操作 put 操作的主要流程如下:

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //初始化时,map中还没有key-value
        if ((tab = table) == null || (n = tab.length) == 0)
            //利用resize生成对应的tab[]数组
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            //当前桶无元素
            tab[i] = newNode(hash, key, value, null);
        else {//桶内有元素
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //桶内第一个元素的key等于待放入的key,用
                e = p;
            else if (p instanceof TreeNode)
                //如果此时桶内已经树化
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//桶内还是一个链表,则插入链尾(尾插)
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //变成红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //检查是否应该扩容
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
复制代码

resize 扩容操作 resize扩容操作主要用在两处: 向一个空的HashMap中执行put操作时,会调用resize()进行初始化,要么默认初始化,capacity为16,要么根据传入的值进行初始化 put操作后,检查到size已经超过threshold,那么便会执行resize,进行扩容,如果此时capcity已经大于了最大值,那么便把threshold置为int最大值,否则,对capcity,threshold进行扩容操作。 发生了扩容操作,那么必须Map中的所有的数进行再散列,重新装入。 resize扩容操作主要用在两处: 向一个空的HashMap中执行put操作时,会调用resize()进行初始化,要么默认初始化,capacity为16,要么根据传入的值进行初始化 put操作后,检查到size已经超过threshold,那么便会执行resize,进行扩容,如果此时capcity已经大于了最大值,那么便把threshold置为int最大值,否则,对capcity,threshold进行扩容操作。 发生了扩容操作,那么必须Map中的所有的数进行再散列,重新装入。 具体扩容图如下:将一个原先capcity为16的扩容成32的: 在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变(因为任何数与0与都依旧是0),是1的话index变成“原索引+oldCap”。 例如:n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。 元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

2.总结

jdk 7 与 jdk 8 中关于HashMap的对比

  • 8时红黑树+链表+数组的形式,当桶内元素大于8时,便会树化 hash值的计算方式不同
  • 1.7 table在创建hashmap时分配空间,而1.8在put的时候分配,如果table为空,则为table分配空间。
  • 在发生冲突,插入链中时,7是头插法,8是尾插法。
  • 在resize操作中,7需要重新进行index的计算,而8不需要,通过判断相应的位是0还是1,要么依旧是原index,要么是oldCap + 原index

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/111760.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Portal门户常见宕机或性能低问题分析与七大解决之道

不论使用什么产品构建门户&#xff0c;只要是基于Java技术的&#xff0c;就很容易宕机或出现性能低的问题。因为Portal产品比较复杂&#xff0c;且集成的数据特别多&#xff0c;架构特别复杂&#xff0c;涉及到的数据通信特别多。宕机或性能低也通常是用户较为头疼的问题。经常…

excel文本函数应用:单元格中的数字和字母,如何判断?

文本字符是Excel中除了数字以外的另一种非常常用的数据类型&#xff0c;Excel也提供了大量的文本函数。利用这些函数我们可以用来判断字符串开头是否为数字、字符串是否同时包含了数字和英文、字符串是否包含了指定字符&#xff0c;可以用来转换英文字母的大小&#xff0c;可以…

Java基础之Stream的使用078

1. Stream 的使用 Stream 是什么&#xff1f; Stream 是数据渠道&#xff0c;用于操作数据源&#xff08;数组、集合等&#xff09;所生成的元素序列。 Java8两大最为重要的改变就是 Lambda表达式 与 Stream API&#xff0c;这两种改变的引入带来的是新的抽象方式 &#xff08…

目标检测之Fast RCNN概述

基本原理 Fast Rcnn主要步骤为 利用SR算法生成候选区域利用VGG16网络进行特征提取利用第一步生成的候选区域在特征图中得到对应的特征矩阵利用ROI pooling将特征矩阵缩放到相同大小并平展得到预测结果 相对于RCNN的优化 主要有三个改进 不再将每一个候选区域依次放入CNN网络…

【基于通道-空间注意的高分辨率锐化】

Channel–spatial attention-based pan-sharpening of very high-resolution satellite images &#xff08;基于通道-空间注意的很高分辨率卫星影像全色锐化&#xff09; 全色锐化处理旨在生成新的合成输出图像&#xff0c;其保留全色的空间细节和多光谱图像输入的光谱细节。…

【服务器端程序的演进过程】

目录 1 服务器端程序的演进过程 阶段一:静态服务器 阶段二:普通动态服务器 阶段三: 以用户共享内容为主的互联网生态 阶段四: 微服务时代(有高并发需求或特征的网站) 2 Java服务器项目分类 3 微服务概述 3.1 什么是微服务 3.2 为什么需要微服务 3.3 怎么搭建微服务项…

C#获取计算机详细的软件和硬件信息

利用System.Management提供的类可以用于读取本地计算机设备的各种数据&#xff0c;包括操作系统、软件、硬件的各种详细信息&#xff0c;内容很丰富。 System.Management的命名空间下&#xff0c;ManagementObjectSearcher类用于查询特定类型的设备&#xff0c;ManagementObjec…

转行做“程序员”很难?这里有几个建议...

“是什么&#xff1f;为什么&#xff1f;怎么样&#xff1f;”的灵魂三连问在我们生活中比比皆是&#xff0c;目的是为了清晰思考和看到事物的本质。对于编程学习也是一样&#xff0c;需要带着疑问从本质上去学习编。 本人是某985高校的本硕连读&#xff0c;非计算机科班出身&…

利器 | AppCrawler 自动遍历测试实践(三):动手实操与常见问题汇总

1080469 14.7 KB 上两篇文章介绍了自动遍历的测试需求、工具选择和 AppCrawler 的环境安装、启动及配置文件字段基本含义&#xff0c;这里将以实际案例更加细致的说明配置文件的用法和一些特殊场景的处理。 下面我们继续之前的例子&#xff0c;在雪球搜索框输入搜索内容后的页面…

CloudFlare系列--功能介绍与常用配置

原文网址&#xff1a;CloudFlare系列--功能与特性的介绍_IT利刃出鞘的博客-CSDN博客 简介 本文介绍CloudFlare的功能与常用的配置。 功能介绍 CloudFlare是世界最强的网络服务商。它可以提供如下服务&#xff1a; 防御DDoS攻击 世界最强防御DDos攻击的厂商。域名注册 世界最…

JavaWeb语法四:多线程案例

目录 1.单例模式 1.1&#xff1a;饿汉模式 1.2&#xff1a;懒汉模式 2.阻塞式队列 2.1:生产者消费者模型 2.2&#xff1a;阻塞队列的模拟实现 3.线程池 3.1&#xff1a;标准库中的线程池 3.2&#xff1a;模拟实现线程池 前言&#xff1a;前一篇我们讲了线程不安全的原因…

SAP UI5 里 FlexBox 的使用方法

ScrollContainer 的使用方式&#xff1a; ScrollContainer 是一个控件&#xff0c;可以在有限的屏幕区域内显示任意内容&#xff0c;并提供滚动以使所有内容都可访问。注意&#xff0c;为了避免影响用户使用体验&#xff0c;不要嵌套沿相同方向滚动的滚动区域。例如&#xff…

【LaTex】基础语法框架快速入门教程——Tex live+TexStudio简要安装及使用教程

0. 引言 LaTeX对于论文排版有着巨大的便利&#xff0c;并且对于参考文献的引用也十分方便&#xff0c;不会出现使用word引用参考文献一旦更改文献引用顺序&#xff0c;就必须全部改编号的情况。这里记录一下如何从0开始学习使用LaTeX书写论文。 1. 软件安装&环境配置 1.…

Qt中实例化一个函数变量时加不加括号的区别,以及括号中的this的使用

一、设计一个测试小程序 废话不多说&#xff0c;直接上代码。 main.h函数就不多说了&#xff0c;没改动。直接上mainwindow.h&#xff0c;也没改动。看mainwindow.cpp的内容。 #include "mainwindow.h" #include "ui_mainwindow.h" #include "test.…

机器学习算法基础——KNN算法

KNN (K-Nearest Neighbor)–K近邻分类算法 • 为了判断未知实例的类别&#xff0c;以所有已知类别的实例作为参照选择参数K • 计算未知实例与所有已知实例的距离 • 选择最近K个已知实例 • 根据少数服从多数的投票法则(majority-voting)&#xff0c;让未知实例归类为K个最邻…

Zerobot僵尸网络出现了新的漏洞利用和功能

©网络研究院 Zerobot DDoS僵尸网络已经获得了重大更新&#xff0c;扩展了其针对更多互联网连接设备和扩展网络的能力。 微软威胁情报中心 (MSTIC)正在以DEV-1061的名称跟踪持续的威胁&#xff0c;名称为未知、新兴或发展中的活动群集。 本月早些时候&#xff0c;Fort…

2022年山东省职业院校技能大赛中职组“网络安全”赛项规程

2022年山东省职业院校技能大赛中职组“网络安全”赛项规程一、赛项名称赛项名称&#xff1a;网络安全英文名称&#xff1a;Cyber Security赛项组别&#xff1a;中职组赛项类别:电子与信息类二、竞赛目的网络空间已经成为陆、海、空、天之后的第五大主权领域空间&#xff0c;习总…

SpringCloud 网关组件 Zuul-1.0 原理深度解析

为什么要使用网关&#xff1f; 在当下流行的微服务架构中&#xff0c;面对多端应用时我们往往会做前后端分离&#xff1a;如前端分成 APP 端、网页端、小程序端等&#xff0c;使用 Vue 等流行的前端框架交给前端团队负责实现&#xff1b;后端拆分成若干微服务&#xff0c;分别…

k8s使用glusterfs(静态供给、动态供给)、glusterfs的安装与使用

目录前言主机准备配置主机名、关闭防火墙、关闭selinux挂载磁盘安装glusterfs服务端glusterfs的端口分布式集群的结构组成glusterfs集群创建存储卷启动卷k8s使用glusterfs作为后端存储&#xff08;静态供给glusterfs存储&#xff09;恢复初始化环境安装Heketi 服务&#xff08;…

如何快速读懂开源代码?

文章目录**RUN起来****调试****把控关键数据结构和函数****从小的开始****关注一个模块****工具****一、阅读开源代码存在的一些误区**二、阅读代码的心态**三、阅读源码与**辅助材料**四、如何阅读开源代码****《gdb 高级调试实战教程》电子书下载链接&#xff1a;**1 下载 Ng…