主頁 >  其他 > hashMap底層原始碼淺析

hashMap底層原始碼淺析

2020-12-27 11:56:43 其他

hashmap是我們經常使用的一個工具類,那么知道它的一些原理和特性嗎?

特性

  1. HashMap是一種基于散列演算法實作的快速查找的鍵值對結構,底層實作是鏈表陣列,
  2. 允許空鍵和空值(但空鍵只有一個,且放在第一位)
  3. 元素是無序的(這里的無序是指的插入和讀取的順序不一致)
  4. JDK 8 后又加了底層加上了紅黑樹優化過長的鏈表以及并行遍歷,

概述

HashMap可以分析的地方很多,網上也有許多文章,本文僅從以下幾個方面進行分析:

  1. 基礎變數
  2. 插入(動態擴容,延遲插入,紅黑樹轉換,可以說的地方很多)
  3. 并行遍歷(jdk8的新特性,快速失敗,柵欄機制)

需要了解的基礎成員變數

變數名中文注釋
size此映射中包含的鍵-值映射的數目,
DEFAULT_INITIAL_CAPACITY默認容量 16
MAXIMUM_CAPACITY允許的最大容量
loadFactor負載因子
threshold當前 HashMap 所能容納鍵值對數量的最大值,超過這個值,則需擴容

這些變數有什么用? 答案是擴容

上面有提到HashMap中兩個非常關鍵的變數,容量和加載因子
如果一個map的size大于臨界值時,HashMap會自動擴容,將當前容量擴一倍出來,并重新計算每個物件的位置并存放,
加載因子,是hashmap在擴容時需要的,表示Hsah表中元素的填滿的程度,待++hashmap中存放的物件數量大于等于map容量*加載因子時++,hashmap會自動擴容,將map的容量 ++擴大一倍++ ,并將之前的物件重新進行hash尋址,并存放到新的地址中,

其中threshold就是臨界值,當實際K-V個數超過threshold時,HashMap會將容量擴容,threshold=容量*加載因子,


這些變數如何傳入:
HashMap有四個構造方法

  • public HashMap() 默認構造方法,默認初始容量為16,加載因子為0.75f
  • public HashMap(int initialCapacity) 指定初始容量的構造方法,加載因子為默認的0.75f
  • public HashMap(int initialCapacity, float loadFactor) 指定初始容量和默認加載因子的初始方法
  • public HashMap(Map<? extends K, ? extends V> m) 以子map為入參,構造新的hashmap

擴容:threshold=容量*加載因子,

插入

網上的講解也有許多,就簡單說一下

hashMap有兩個概念對于插入很重要,一個叫做bucket(桶),一個叫做node/entry(元素)

桶與元素他們的關系是怎樣的:
對于 HashMap 及其子類而言,它們采用 Hash 演算法來決定集合中元素的存盤位置,當系統開始初始化 HashMap 時,系統會創建一個長度為 capacity 的 Entry 陣列,這個陣列里可以存盤元素的位置被稱為“桶(bucket)”,每個 bucket 都有其指定索引,系統可以根據其索引快速訪問該 bucket 里存盤的元素,一旦插入時key的hahs相同(也叫做hash尋找沖突,或者哈希碰撞),就會把兩個hash值相同的元素存盤在一個桶里面,
總而言之,就是一個hashMap包含許多桶,一個桶里面包含了許多元素


接下來看一下插入的api:
hashmap的插入api非常簡單,就是一個put

/**
 * 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 {@code key}, or
 *         {@code null} if there was no mapping for {@code key}.
 *         (A {@code null} return can also indicate that the map
 *         previously associated {@code null} with {@code key}.)
 */
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

從這里可以看到他是呼叫的putVal方法,在通過斷點進入

/**
     * Implements Map.put and related methods.
     *
     * @param hash hash for key hash值
     * @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.    如果為false,表示表處于創建模式,
     * @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;
        if ((tab = table) == null || (n = tab.length) == 0)
            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))))
                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;
    }

原始碼過長,我將結合網上的博客以及自己的理解將原始碼抽取并且翻譯了成容易理解的偽代碼 https://blog.csdn.net/hsee2006/article/details/104784557/

putVal:總流程  
{
    //宣告了一個區域變數 tab,區域變數 Node 型別的資料 p,int 型別 n,i
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 初始化桶陣列 table,也就是說table是在有資料加載時延遲初始化的
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 找當前要插入的資料應該在哈希表中的位置,如果沒找到,代表哈希表中當前的位置是空的,否則就代表找到資料了
    // 如果找到資料就賦值,就說明hash沖突了,將舊的值給臨時變數p
    {
        if ((p = tab[i = (n - 1) & hash]) == null)
            //  說明沒有沖突,直接賦值
            tab[i] = newNode(hash, key, value, null);
        else {
            //  處理hash尋址沖突的代碼,解決的同一個位置的新資料和舊資料的共存問題
            代碼片段1(后面)
        }
    }
    ++modCount;//   增加當前的操作長度,可以用于執行緒不安全時的快速失敗機制
    if (++size > threshold)//   觸發擴容
        resize();
    afterNodeInsertion(evict);
    return null;
}

注:這里的 (n-1)&hash 是一種高效的除模取余運算
- 傳統的方式進行求余運算,需要先將10進制轉成2進制到記憶體中進行計算,然后再把結果轉換成10進制
- 而位運算是直接在記憶體中進行,不需要經過這些轉換
- 但是位運算只能用于除數是2的n次方的數的求余

代碼片段1:
{
    /*
        這里會經過三個判斷:
        1,如果一來是key沖突,就直接替換值
        2, key不一樣,且為是紅黑樹,則呼叫紅黑樹的插入方法
        3, 直接對鏈表進行寫入,如果鏈表過長會轉換結構為紅黑樹,或者key沖突就直接替換
    */
    //  這個e是一會將會被寫入的資料的臨時變數
    Node<K,V> e; K k;
    //  如果鍵的值以及節點 hash 等于鏈表中的第一個鍵值對節點時,說明key沖突,則將 e 指向該鍵值對
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
        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);
                // 如果鏈表長度大于或等于樹化閾值(8),則進行將鏈表優化為紅黑樹或者進行擴容操作
                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    treeifyBin(tab, hash);
                break;
            }
            //  如果當前遍歷到的資料和要插入的資料的 key 是一樣,和上面之前的一樣,賦值給變數 e,下面替換內容
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                break;
            //  開始下一次遍歷
            p = e;
        }
    }
    // 判斷要插入的鍵值對是否存在 HashMap 中
    if (e != null) { // existing mapping for key
        V oldValue = e.value;
        // onlyIfAbsent 表示是否僅在 oldValue 為 null 的情況下更新鍵值對的值
        if (!onlyIfAbsent || oldValue == null)
            e.value = value;
        //  鉤子函式:一般不會使用
        afterNodeAccess(e);
        return oldValue;
    }
}

代碼片段2:


并行遍歷

并行遍歷是java8給jdk多加的一個介面,用來做并行遍歷的,它的使用方式如下:

public static void main(String[] args) {
    //  構造一個map
    HashMap map=new HashMap(1,4);
    //  插入值
    for (int i = 0; i < 6; i++) {
        map.put(i,"值_"+i);
    }
    //  得到并行遍歷器
    Spliterator spliterator = map.entrySet().spliterator();
    System.out.println("開始分塊元素的遍歷");

    //  開始分割遍歷器
    Spliterator s1 = spliterator.trySplit();
    //  執行原先的遍歷器
    System.out.println("執行第一塊區域的元素遍歷:");
    spliterator.forEachRemaining(item-> System.out.println(item));
    //  執行被分出去的遍歷器
    System.out.println("執行第二塊區域的元素遍歷:");
    s1.forEachRemaining(item-> System.out.println(item));
    System.out.println("執行完畢");
}

結果如下:
開始分塊元素的遍歷
執行第一塊區域的元素遍歷:1=值_1
3=值_3
5=值_5
執行第二塊區域的元素遍歷:0=值_0
2=值_2
4=值_4
執行完畢

由這段代碼我們可以看出jdk8新加的這個特性的好處,相比傳統的Iterator方法,該方法可以更加高效的處理并行讀取map資料,可以將資料分割為多個迭代器,然后傳入不同的執行緒,利用多核更好的處理好海量資料,

那么map是怎么做到這一點的?原始碼如下:

hashMap的內部類:EntrySet
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    ...
    public final Spliterator<Map.Entry<K,V>> spliterator() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    ...
}

由此我們發現實質是利用了里面的一個內部類EntrySpliterator

static final class EntrySpliterator<K,V>
    extends HashMapSpliterator<K,V>
    implements Spliterator<Map.Entry<K,V>> {
    EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                     int expectedModCount) {
    super(m, origin, fence, est, expectedModCount);
    }
}

其中的繼承的這個Spliterator介面非常有意思,
我查了下資料,發現它是jdk官方提供的一個并行遍歷介面:
Spliterator介面是Java為了并行遍歷資料源中的元素而設計的迭代器,這個可以類比最早Java提供的順序遍歷迭代器Iterator,但一個是順序遍歷,一個是并行遍歷,

為什么有了Iterator還需要spliterator呢?

從最早Java提供順序遍歷迭代器Iterator時,那個時候還是單核時代,但現在多核時代下,順序遍歷已經不能滿足需求了,如何把多個任務分配到不同核上并行執行,才是能最大發揮多核的能力,所以Spliterator應運而生!

如果感興趣的可以讀一下這篇博客
了解Java Spliterator 這篇文章就夠了

為什么有了hahsMap還會重寫spliterator呢?

如果深入原始碼會發現,spliterator類有自己實作的默認分割遍歷,但是hashMap卻重寫了一套分割演算法,
原來Spliterator的一個特點是每次將元素拆分出去一半,對于HashMap,由于hashMap底層是鏈表,如果要完全精確到元素,勢必會造成演算法的復雜和性能的低下,
因此,Spliterator在HashMap的實作程序中,直接是按bucket進行處理,這樣會導致每次拆分的資料并不均勻,HashMap中實際上是按照bucket的數量平均拆分,只是可能每個bucket上面Node的數量可能不一致,另外有的bucket可能為空,


我們來看一下spliterator的重寫中幾個比較重要的概念以及重寫

HashMapSpliterator對于spliterator的部分實作
static class HashMapSpliterator<K,V> {
    // 需要遍歷的 Map 物件
    final MyHashMap<K,V> map;
    // 當前正在遍歷的節點
    Node<K,V> current;          // current node
    // 當前迭代器開始遍歷的桶索引
    int index;                  // current index, modified on advance/split
    // 當前迭代器遍歷上限的桶索引
    // fence的英語代表柵欄(zha lan)的意思,這里代表邊界
    int fence;                  // one past last index
    // 需要遍歷的元素個數
    int est;                    // size estimate
    // 期望運算元,用于多執行緒情況下,如果多個執行緒同時對 HashMap 進行讀寫,
    // 那么這個期望運算元 expectedModCount 和 HashMap 的 modCount 就會不一致,這時候拋個例外出來,稱為“快速失敗”
    int expectedModCount;       // for comodification check
    
    MapSpliterator(MyHashMap<K,V> m, int origin,
                       int fence, int est,
                       int expectedModCount) {
        this.map = m;
        this.index = origin;
        this.fence = fence;
        this.est = est;
        this.expectedModCount = expectedModCount;
    }

    // 獲取柵欄
    final int getFence() { // initialize fence and size on first use
        int hi;
        // 第一個分割迭代器會執行下面 if 內的代碼,因為構造傳入的fence值為-1
        if ((hi = fence) < 0) {
            MyHashMap<K,V> m = map;
            est = m.size;// 獲取需要遍歷的元素個數
            expectedModCount = m.modCount;//獲取當前修改的數量
            Node<K,V>[] tab = m.table;//得到資料
            hi = fence = (tab == null) ? 0 : tab.length;//  如果為null就是0
        }
        return hi;
    }

    // 獲取當前迭代器需要遍歷的元素個數
    public final long estimateSize() {
        getFence(); // force init
        return (long) est;
    }
    
}

這段代碼體現了兩個概念
柵欄機制以及快速失敗(后面會講解,先把概念放在這里放在這里)

然后再看EntrySpliterator的實作

static final class EntrySpliterator<K,V>
        extends HashMapSpliterator<K,V>
        implements Spliterator<Map.Entry<K,V>> {
        EntrySpliterator(HashMap<K,V> m, int origin, int fence, int est,
                         int expectedModCount) {
            super(m, origin, fence, est, expectedModCount);
        }

        //  分割遍歷器
        public EntrySpliterator<K,V> trySplit() {
            //  hi代表上邊界
            //  mid代表限制值+當前索引號對折一半
            //  >>>用來代表一半對折(如果是奇數的話,會先減一再次對折)
            int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
            //  構建出一個新的分割器的同時,將自己的上下邊界也進行修改
            return (lo >= mid || current != null) ? null :
                new EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
                                          expectedModCount);
        }

        //  遍歷剩下的元素
        public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
            int i, hi, mc;
            if (action == null)
                throw new NullPointerException();
            HashMap<K,V> m = map;
            Node<K,V>[] tab = m.table;
            if ((hi = fence) < 0) {
                mc = expectedModCount = m.modCount;
                hi = fence = (tab == null) ? 0 : tab.length;
            }
            else
                mc = expectedModCount;
            if (tab != null && tab.length >= hi &&
                (i = index) >= 0 && (i < (index = hi) || current != null)) {
                //  這段代碼沒有什么好說的,就是遍歷桶,然后把桶里面的元素給讀取出來進行函式式處理
                Node<K,V> p = current;
                current = null;
                do {
                    //如果p為空則移動到下一個bucket
                    if (p == null)
                        p = tab[i++];
                    else {
                        //遍歷鏈表 呼叫外部函式處理
                        action.accept(p);
                        p = p.next;
                    }
                    //  進行讀取邊界控制判斷,不會讀取到另外的分割器的邊界資料
                } while (p != null || i < hi);
                if (m.modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }

        //  對單個元素進行遍歷,前進一次,這里就不分析了
        public boolean tryAdvance(Consumer<? super Map.Entry<K,V>> action) {
            ...
        }

        public int characteristics() {
            ...
        }
    }

快速失敗

總所周知,hahsMap是執行緒不安全的,為了避免在在執行緒不安全時使用了hahsMap,java官方提供了一種檢測機制,

fail-fast 機制,即快速失敗機制,是java集合(Collection)中的一種錯誤檢測機制,當在迭代集合的程序中該集合在結構上發生改變的時候,就有可能會發生fail-fast,即拋出 ConcurrentModificationException例外,

其中,并行遍歷就利用了這個機制

在EntrySpliterator類的forEachRemaining方法的該段代碼就體現了快速失敗機制的原理:
public void forEachRemaining(Consumer<? super Map.Entry<K,V>> action) {
    ...
    //  獲取預期資料量
    if ((hi = fence) < 0) {
        mc = expectedModCount = m.modCount;
        hi = fence = (tab == null) ? 0 : tab.length;
    }
    else
        mc = expectedModCount;
    if (tab != null && tab.length >= hi &&
        ...
        //  比較預期資料量以及實時資料量
        if (m.modCount != mc)
            throw new ConcurrentModificationException();
    }
}

也就是在遍歷時將期望資料量(expectedModCount)和當前實際資料量進行比較(modCount)
如果不相等,就說明在遍歷程序中,有其他執行緒在對資料進行修改,就會報錯

注意:fail-fast機制并不保證在不同步的修改下一定會拋出例外,它只是盡最大努力去拋出,所以這種機制一般僅用于檢測bug,(比如資料的卻發生了變化,但是資料量卻是相等的情況下)


以上,就是hahsMap的底層原始碼淺析,實際上hashMap所運用到的技巧遠不止如此,如果想要提升,推薦直接看jdk原始碼以及其他人的相應決議博客,
最后,希望大家一鍵三連,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/240961.html

標籤:其他

上一篇:ConcurrentHashMap雜談

下一篇:加密演算法(一):30行代碼破解凱撒加密

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more