1. 程式人生 > >如何看待程式碼中濫用HashMap?-知乎問題讀後感和相關研究

如何看待程式碼中濫用HashMap?-知乎問題讀後感和相關研究

昨天在知乎上看到了一個問題如何看待程式碼中濫用HashMap? .日常工程中使用HashMap確實挺多的 ,簡單方便快捷(至少感覺上是這樣) ,但越是簡單好用的東西 ,底層封裝的越複雜 .

跟進去看了一下 ,朱文彬老師進行了比較直觀的對比實驗 ,我也查閱了其他的資料 ,最後把這個實驗扒下來運行了 .

資料 HashMap的原理研究

1.HashMap的結構 ,陣列Col[對應HashCode] + 連結串列Row[對應資料節點Entry]

transient Node<K,V>[] table

2.設定初始容量(桶/陣列的數量 ,預設16) ,負載因子(判定Map滿的條件 ,預設0.75)

    public HashMap(int initialCapacity, float loadFactor) {
        //初始容量不能<0
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: "
                    + initialCapacity);
        //初始容量不能 > 最大容量值,HashMap的最大容量值為2^30
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //負載因子不能 < 0
if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 計算出大於 initialCapacity 的最小2^n值。 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1
; this.loadFactor = loadFactor; //設定HashMap的容量極限,當HashMap的容量達到該極限時就會進行擴容操作 threshold = (int) (capacity * loadFactor); //初始化table陣列 table = new Entry[capacity]; init(); }

3.put方法

    public V put(K key, V value) {
         //當key為null,呼叫putForNullKey方法,儲存null與table第一個位置中,這是HashMap允許為null的原因
         if (key == null)
             return putForNullKey(value);
         //計算key的hash值
         int hash = hash(key.hashCode());
         ------(1)
         //計算key hash 值在 table 陣列中的位置
         int i = indexFor(hash, table.length);
         ------(2)
         //從i出開始迭代 e,找到 key 儲存的位置
         for (Entry<K, V> e = table[i]; e != null; e = e.next) {
             Object k;
             //判斷該條鏈上是否有hash值相同的(key相同)
             //若存在相同,則直接覆蓋value,返回舊value
             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                 V oldValue = e.value;    //舊值 = 新值
                 e.value = value;
                 e.recordAccess(this);
                 return oldValue;     //返回舊值
             }
         }
         //修改次數增加1
         modCount++;
         //將key、value新增至i位置處
         addEntry(hash, key, value, i);
         return null;
     }

3.1計算hash值/查詢對應的陣列列表位置

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    static int indexFor(int h, int length) {
        return h & (length - 1);
    }

HashMap通過陣列加連結串列 ,如何均勻分佈資料?

  • 排布太緊連結串列會很長 ,查詢效率會變低(順序查詢)
  • 排布太鬆陣列會很大 ,浪費很多空間

hash+indexFor

  • hash是純數學計算
  • 合理分佈資料需要取模 ,indexFor是特殊的”取模”運算
    • 因為底層桶的大小length是2^n ,length-1 -> 111...111(2進位制)
    • 10110 & 1111 (22 & 15) -> 00110 (6)
    • 22%16 = 6
  • 利用二進位制&的特點 ,可以快速的達成取模的目的(%取模比&運算複雜)

3.2新增節點/相同key替換

    void addEntry(int hash, K key, V value, int bucketIndex) {
        //獲取bucketIndex處的Entry
        Entry<K, V> e = table[bucketIndex];
        //將新建立的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry 
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
        //若HashMap中元素的個數超過極限了,則容量擴大兩倍
        if (size++ >= threshold)
            resize(2 * table.length);
    }

4.get操作

    public V get(Object key) {
        // 若為null,呼叫getForNullKey方法返回相對應的value
        if (key == null)
            return getForNullKey();
        // 根據該 key 的 hashCode 值計算它的 hash 碼  
        int hash = hash(key.hashCode());
        // 取出 table 陣列中指定索引處的值
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
            Object k;
            //若搜尋的key與查詢的key相同,則返回相對應的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

5.擴容機制

隨著HashMap中的元素增加,hash衝突的機率也就變高,
因為陣列的長度是固定的。為了提高查詢的效率,要對陣列進行擴容(元素超過 大小length*負載因子loadFactor),
而在HashMap陣列擴容之後,最消耗效能的點就出現了:
原陣列中的資料必須重新計算其在新陣列中的位置,並put,這就是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);
        table = newTable;
        //更新臨界值
        threshold = (int)(newCapacity * loadFactor);
    }
    //舊陣列中元素往新陣列中遷移
    void transfer(Entry[] newTable) {
        //舊陣列
        Entry[] src = table;
        //新陣列長度
        int newCapacity = newTable.length;
        //遍歷舊陣列
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);//放在新陣列中的index位置
                    e.next = newTable[i];//實現連結串列結構,新加入的放在鏈頭,之前的的資料放在鏈尾
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }

    }

6.執行緒安全

HashMap是執行緒不安全的 ,因此在多執行緒應用時 ,可以考慮使用:

  • HashTable(同步鎖整個table陣列 ,效率較低)
  • Collections.synchronizedMap(對每一個方法增加了synchronized ,但並不保證put/get/contain之間的同步)
  • ConcurrentHashMap(同步鎖每次只鎖一個桶 ,可以多執行緒同時讀寫不同桶 ,也保證了put/get同一個桶的同步)

實驗一 Map/List/陣列的記憶體佔用情況

package sourceCode.javaSE;

import static sourceCode.objMemoryUtil.ObjMemoryCostUtil.*;

//import static -> 靜態匯入 ,匯入全部(*)或指定的靜態方法 ,可以直接使用 ,不用加System.這樣的字首名
import static java.lang.System.out;

/**
 * 作者:朱文彬
 * 連結:https://www.zhihu.com/question/28119895/answer/40494358
 * 來源:知乎
 * 著作權歸作者所有,轉載請聯絡作者獲得授權。
 * <p>
 * 對各種map佔用的記憶體大小進行研究
 */
public class HashMapMemoryTest {

    static void printSize(Object o) {
        out.printf("型別:%s,佔用記憶體:%.2f MB\n", o.getClass().getSimpleName(), deepSizeOf(o) / 1024D / 1024D);
    }

    public static void main(String[] args) throws Throwable {

        int size = 30000;

        java.util.Map<Object, Object> javaUtilHashMap = new java.util.HashMap<>();
        for (int i = 0; i < size; javaUtilHashMap.put(i, i), i++) {
        }

        /**
         * Java集合框架Koloboke ,目前的版本主要是替換java.util.HashSet和java.util.HashMap
         *
         * Koloboke對每個entry使用了更少的記憶體
         * Koloboke目標是把鍵和值儲存在同一行快取記憶體中
         * 所有的方法都經過了實現優化,而不是像AbstractSet類或AbstractMap類那樣委託給框架類(Skeleton Class)
         */
        net.openhft.koloboke.collect.map.hash.HashIntIntMap openHftHashIntIntMap = net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap();
        for (int i = 0; i < size; openHftHashIntIntMap.put(i, i), i++) {
        }

        java.util.ArrayList<Object> javaUtilArrayList = new java.util.ArrayList<>();
        for (int i = 0; i < size; javaUtilArrayList.add(i), i++) {
        }

        Integer[] objectArray = new Integer[size];
        for (int i = 0; i < size; objectArray[i] = i, i++) {
        }

        /**
         * hppc - High Performance Primitive Collections for Java
         * 對Java的原始集合型別如對映map、集合set、堆疊stack、列表list、佇列deque等進行了擴充套件,提供了更佳的記憶體利用率,帶來了更好的效能。
         */
        com.carrotsearch.hppc.IntArrayList hppcArrayList = new com.carrotsearch.hppc.IntArrayList();
        for (int i = 0; i < size; hppcArrayList.add(i), i++) {
        }

        int[] primitiveArray = new int[size];
        for (int i = 0; i < size; primitiveArray[i] = i, i++) {
        }

        out.println("java.vm.name=" + System.getProperty("java.vm.name"));
        out.println("java.vm.version=" + System.getProperty("java.vm.version"));
        out.println("容器元素總數:" + size);

        printSize(javaUtilHashMap);
        printSize(openHftHashIntIntMap);
        printSize(javaUtilArrayList);
        printSize(hppcArrayList);
        printSize(primitiveArray);
        printSize(objectArray);  
    }
}
容器元素總數:30000
型別:HashMap,佔用記憶體:2.08 MB
型別:UpdatableLHashParallelKVIntIntMap,佔用記憶體:0.50 MB
型別:ArrayList,佔用記憶體:0.58 MB
型別:IntArrayList,佔用記憶體:0.17 MB
型別:int[],佔用記憶體:0.11 MB
型別:Integer[],佔用記憶體:0.57 MB

記憶體差異的原因是:

 1. hash中為避免退化為陣列(如openhft的實現可以退化為陣列)或者連結串列(java.util.HashMap可能退化為連結串列)使用的空槽
 2. java.util.HashMap.Entry的額外佔用的記憶體,用於維持連結串列、記憶體對齊等
 3. 物件記憶體佔用:在HotSpot 64位jdk中,一個java.lang.Integer佔用16位元組,一個引用佔用4位元組,總共20位元組,而一個int只佔用4位元組

結果分析 :

 1. 處理 `大資料量的預設型別` 時 ,使用個性化的集合類可以減少型別推斷 ,節省拆裝箱的記憶體
 2. 陣列是較為底層的 ,記憶體使用上最少 ,但可支援的操作也很少 ,查詢效率也不那麼好
 3. 但面對數量巨大的key和簡單的value來說 ,使用陣列太耗費時間
 4. `記憶體優化` 和 `搜尋優化` 不可調和

補充:deepSizeOf(o) - 利用Instrumentation檢測JVM物件大小

文章內使用了java.lang.instrument.Instrumentation : Java Instrumentation指的是可以用獨立於應用程式之外的代理(agent)程式來監測和協助執行在JVM上的應用程式 ,監測和協助包括但不限於獲取JVM執行時狀態,替換和修改類定義等 .

最後參考Java物件佔用記憶體大小的計算方法 ,完整的計算Map和內部引用的所有成員的大小

package sourceCode.objMemoryUtil;

/*
 * @(#)MemoryCalculator.java    1.0 2010-11-8
 *
 * Copyright 2010 Richard Chen([email protected]) All Rights Reserved.
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * 利用Instrumentation去檢測JVM中的情況 ,還可以分析JVM中載入的所有物件等
 * 1.編寫premain函式 ,作為JVM啟動時的回撥函式 ,注入Instrumentation例項到工具類中
 * 2.編寫MANIFEST.MF ,指定Premain-Class的位置
 * 3.單獨打包工具類和MANIFEST
 * 4.在使用入口程式設定VM-operation : -javaagent:target/objMemoryUtil.jar ,指定代理的工具類jar
 */
public class ObjMemoryCostUtil {
    /**
     * JVM將在啟動時通過{@link #premain}初始化此成員變數.
     */
    private static Instrumentation instrumentation = null;


    /**
     * JVM在初始化後在呼叫應用程式main方法前將呼叫本方法, 本方法中可以寫任何main方法中可寫的程式碼.
     *
     * @param agentArgs 命令列傳進行來的代理引數, 內部需自行解析.
     * @param inst      JVM注入的控制代碼.
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        instrumentation = inst;
    }

    /**
     * 計算例項本身佔用的記憶體大小. 注意:
     * 1. 多次呼叫可能結果不一樣, 主要跟例項的狀態有關
     * 2. 例項中成員變數如果是reference型別, 則reference所指向的例項佔用記憶體大小不統計在內 (只計算基本型別的成員)
     *
     * @param obj 待計算記憶體佔用大小的例項.
     * @return 記憶體佔用大小, 單位為byte.
     */
    public static long shallowSizeOf(Object obj) {
        if (instrumentation == null) {
            throw new IllegalStateException("Instrumentation initialize failed");
        }
        if (isSharedObj(obj)) {
            return 0;
        }
        return instrumentation.getObjectSize(obj);
    }

    /**
     * 計算例項佔用的記憶體大小, 含其成員變數所引用的例項, 遞迴計算.
     *
     * @param obj 待計算記憶體佔用大小的例項.
     * @return 記憶體佔用大小, 單位為byte.
     */
    public static long deepSizeOf(Object obj) {
        Map calculated = new IdentityHashMap();
        Stack unCalculated = new Stack();
        unCalculated.push(obj);
        long result = 0;
        do {
            result += doSizeOf(unCalculated, calculated);
        } while (!unCalculated.isEmpty());
        return result;
    }

    /**
     * 判斷obj是否是共享物件. 有些物件, 如interned Strings, Boolean.FALSE和Integer#valueOf()等.
     *
     * @param obj 待判斷的物件.
     * @return true, 是共享物件, 否則返回false.
     */
    private static boolean isSharedObj(Object obj) {
        if (obj instanceof Comparable) {
            if (obj instanceof Enum) {
                return true;
            } else if (obj instanceof String) {
                return (obj == ((String) obj).intern());
            } else if (obj instanceof Boolean) {
                return (obj == Boolean.TRUE || obj == Boolean.FALSE);
            } else if (obj instanceof Integer) {
                return (obj == Integer.valueOf((Integer) obj));
            } else if (obj instanceof Short) {
                return (obj == Short.valueOf((Short) obj));
            } else if (obj instanceof Byte) {
                return (obj == Byte.valueOf((Byte) obj));
            } else if (obj instanceof Long) {
                return (obj == Long.valueOf((Long) obj));
            } else if (obj instanceof Character) {
                return (obj == Character.valueOf((Character) obj));
            }
        }
        return false;
    }

    /**
     * 確認是否需計算obj的記憶體佔用, 部分情況下無需計算.
     *
     * @param obj        待判斷的物件.
     * @param calculated 已計算過的物件.
     * @return true, 意指無需計算, 否則返回false.
     */
    private static boolean isEscaped(Object obj, Map calculated) {
        return obj == null || calculated.containsKey(obj)
                || isSharedObj(obj);
    }

    /**
     * 計算棧頂物件本身的記憶體佔用.
     *
     * @param unCalculated 待計算記憶體佔用的物件棧.
     * @param calculated   物件圖譜中已計算過的物件.
     * @return 棧頂物件本身的記憶體佔用, 單位為byte.
     */
    private static long doSizeOf(Stack unCalculated, Map calculated) {
        Object obj = unCalculated.pop();
        if (isEscaped(obj, calculated)) {
            return 0;
        }
        Class clazz = obj.getClass();
        if (clazz.isArray()) {
            doArraySizeOf(clazz, obj, unCalculated);
        } else {
            while (clazz != null) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (!Modifier.isStatic(field.getModifiers())
                            && !field.getType().isPrimitive()) {
                        field.setAccessible(true);
                        try {
                            unCalculated.add(field.get(obj));
                        } catch (IllegalAccessException ex) {
                            throw new RuntimeException(ex);
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }
        calculated.put(obj, null);
        return shallowSizeOf(obj);
    }

    /**
     * 將陣列中的所有元素加入到待計算記憶體佔用的棧中, 等待處理.
     *
     * @param arrayClazz   陣列的型別.
     * @param array        陣列例項.
     * @param unCalculated 待計算記憶體佔用的物件棧.
     */
    private static void doArraySizeOf(Class arrayClazz, Object array,
                                      Stack unCalculated) {
        if (!arrayClazz.getComponentType().isPrimitive()) {
            int length = Array.getLength(array);
            for (int i = 0; i < length; i++) {
                unCalculated.add(Array.get(array, i));
            }
        }
    }
}

Instrumentation使用方式

  • 編寫MANIFEST.MF ,指定Premain-Class的位置
Manifest-Version: 1.0
Premain-Class: sourceCode.objMemoryUtil.ObjMemoryCostUtil
Created-By: 1.6.0_29
  • 單獨打包ObjMemoryCostUtil類 ,並將MANIFEST加入到Jar(粗淺的學習了下maven打包)
<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <executions>
        <execution>
            <id>objMemoryCostUtil</id>
            <goals>
                <goal>jar</goal>
            </goals>
                <phase>package</phase>
            <configuration>
                <finalName>objMemoryUtil</finalName>
                <includes>
                    <include>**/objMemoryUtil/**</include>
                </includes>
                <archive>
                    <manifestFile>src/main/java/sourceCode/objMemoryUtil/MANIFEST.MF</manifestFile>
                    <manifest><addClasspath>true</addClasspath></manifest>
                </archive>
            </configuration>
        </execution>
    </executions>
</plugin>
  • 在使用的Main函式中設定VM引數 ,指定jar包位置 -javaagent:target/objMemoryUtil.jar

這裡寫圖片描述

  • 執行即可

實驗二 Map/List/陣列的put效能

這個實現就是通過插入資料 ,計算插入時間

package sourceCode.javaSE;

import java.util.Collections;

import static java.lang.Math.*;
import static java.lang.System.*;
import static java.util.Arrays.*;

/**
 * 作者:朱文彬
 * 連結:https://www.zhihu.com/question/28119895/answer/40494358
 * 來源:知乎
 * 著作權歸作者所有,轉載請聯絡作者獲得授權。
 * <p>
 * 對各種map存取時間進行研究
 */

public class HashMapCPUTimeTest {

    /**
     * 計算各集合put操作的時間 ,可以設定重複次數取平均
     *
     * @param type 物件型別
     * @param r    執行緒所做的操作
     */
    static void printTime(Class type, Runnable r) {
        double time = timeCall(r, 30);
        char[] rpad = "                                    ".toCharArray();
        type.getSimpleName().getChars(0, type.getSimpleName().length(), rpad, 0);
        out.printf("型別:%s \t 耗時:%.2g s\n", new String(rpad), time);
    }

    /**
     * 根據重複次數 ,計算所花時間的平均值
     *
     * @param call   目標執行緒
     * @param repeat 重複次數
     * @return
     */
    public static double timeCall(Runnable call, int repeat) {
        double[] a = new double[repeat];
        setAll(a, i -> timeCall(call));
        if (repeat > 7) { //重複次數>7 ,只對中間的60%資料計算平均
            sort(a);
            int i = round(repeat * 0.2f);
            return stream(a, i, repeat - i).average().getAsDouble();
        }
        if (repeat > 3) { //重複次數>3 ,去掉一個最高分一個最低分 ,剩下的取平均
            sort(a);
            return stream(a, 1, repeat - 1).average().getAsDouble();
        }
        return stream(a).average().getAsDouble();
    }

    /**
     * 啟動執行緒 ,執行put操作
     *
     * @param call
     * @return
     */
    public static double timeCall(Runnable call) {
        long startA = nanoTime();//System.nanoTime提供基於系統的相對精確的時間 ,類似秒錶
        long start = nanoTime();
        try {
            call.run();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return 1E-9d * (max(0, nanoTime() - start - (start - startA))); //1E-9d :1乘以10的-9次方
    }


    public static void main(String[] args) throws Throwable {
        int size = 1000000;
        out.println("java.vm.name=" + System.getProperty("java.vm.name"));
        out.println("java.vm.version=" + System.getProperty("java.vm.version"));
        out.println("容器元素總數:" + size);

        printTime(java.util.HashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilHashMap = new java.util.HashMap<>();
            for (int i = 0; i < size; javaUtilHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.LinkedHashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = new java.util.LinkedHashMap<>();
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.concurrent.ConcurrentHashMap.class, () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = new java.util.concurrent.ConcurrentHashMap<>();
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(Collections.synchronizedMap(new java.util.concurrent.ConcurrentHashMap()).getClass(), () -> {
            java.util.Map<Object, Object> javaUtilLinkedHashMap = Collections.synchronizedMap(new java.util.concurrent.ConcurrentHashMap());
            for (int i = 0; i < size; javaUtilLinkedHashMap.put(i, i), i++) {
            }
        });

        printTime(java.util.TreeMap.class, () -> {
            java.util.Map<Object, Object> javaUtilTreeMap = new java.util.TreeMap<>();
            for (int i = 0; i < size; javaUtilTreeMap.put(i, i), i++) {
            }
        });

        printTime(net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap().getClass(), () -> {
            net.openhft.koloboke.collect.map.hash.HashIntIntMap openHftHashIntIntMap = net.openhft.koloboke.collect.map.hash.HashIntIntMaps.newUpdatableMap();
            for (int i = 0; i < size; openHftHashIntIntMap.put(i, i), i++) {
            }
        });

        printTime(java.util.ArrayList.class, () -> {
            java.util.ArrayList<Object> javaUtilArrayList = new java.util.ArrayList<>();
            for (int i = 0; i < size; javaUtilArrayList.add(i), i++) {
            }
        });

        printTime(Integer[].class, () -> {
            Integer[] objectArray = new Integer[size];
            for (int i = 0; i < size; objectArray[i] = i, i++) {
            }
        });

        printTime(com.carrotsearch.hppc.IntArrayList.class, () -> {
            com.carrotsearch.hppc.IntArrayList hppcArrayList = new com.carrotsearch.hppc.IntArrayList();
            for (int i = 0; i < size; hppcArrayList.add(i), i++) {
            }
        });

        printTime(int[].class, () -> {
            int[] primitiveArray = new int[size];
            for (int i = 0; i < size; primitiveArray[i] = i, i++) {
            }
        });

    }
}

容器元素總數:1000000
型別:HashMap 耗時:0.028 s
型別:LinkedHashMap 耗時:0.025 s
型別:ConcurrentHashMap 耗時:0.10 s
型別:SynchronizedMap 耗時:0.091 s
型別:TreeMap 耗時:0.25 s
型別:UpdatableLHashParallelKVIntIntMap 耗時:0.048 s
型別:ArrayList 耗時:0.0063 s
型別:Integer[] 耗時:0.0031 s
型別:IntArrayList 耗時:0.0033 s
型別:int[] 耗時:0.00064 s

  1. 單純對Put操作來講 ,和memory的實驗類似 ,越是底層越是簡單的結構 ,效率越高像TreeMap耗時是陣列的1000倍
  2. 但對於一些後續操作 ,排序/get來說 ,TreeMap/HashMap則不知道高到哪裡去
  3. 所以瞭解集合類的差異 ,各自的優缺點 ,合理的使用才是最重要的

相關推薦

如何看待程式碼濫用HashMap?-問題讀後感相關研究

昨天在知乎上看到了一個問題如何看待程式碼中濫用HashMap? .日常工程中使用HashMap確實挺多的 ,簡單方便快捷(至少感覺上是這樣) ,但越是簡單好用的東西 ,底層封裝的越複雜 . 跟進去看了一下 ,朱文彬老師進行了比較直觀的對比實驗 ,我也查閱了其他

使用scrapy爬取問題答案的相關欄位完整程式碼

目前程式健壯性有待提高。尤其是對question的各類異常處理還不夠。但是程式碼已經可用,附上程式碼執行後爬取到的資料。在爬取到101條quetion時已經爬取到2671條answer欄位了。。。。這差距好大。一方面是因為answer有知乎提供的API,更方便爬取,另一個方面

python scrapy爬取問題收藏夾下所有答案的內容圖片

上文介紹了爬取知乎問題資訊的整個過程,這裡介紹下爬取問題下所有答案的內容和圖片,大致過程相同,部分核心程式碼不同. 爬取一個問題的所有內容流程大致如下: 一個問題url 請求url,獲取問題下的答案個數(我不需要,因為之前獲取問題資訊的時候儲存了問題的回答個數) 通過答案的介面去獲取答案(如果一次獲取5

Android程式碼設定控制元件的寬

//在程式碼中設定控制元件大小的方法 private Button mbtn; mbtn = (Button) findViewById(R.id.btn_test); LayoutParams lp; lp=mbtn.getLayoutParams

android在程式碼設定控制元件的長

這裡介紹LinearLayout和RelativeLayout兩種佈局下的控制元件 LinearLayout.LayoutParams paramss = (LinearLayout.LayoutParams) imageview.getLayoutPar

mysqlcount(*),distinct的使用方法效率研究

SQL 語句的COUNT有兩種用途 1. 用來計算行數——Count(*) 2. 用來計算某個值的數量——COUNT(col1) Count(*) 永遠返回的都是結果集中的行數,而COUNT(col1)只返回col1值非空的記錄數,如果col1值全部非空, Count(*)和COUNT(col1)的結果是相同

模擬登錄selenium在python

sub spa down ble 版本 body IT sites from from selenium import webdriver from scrapy.selector import Selector browser = webdriver.Chrome(ex

大牛:入行十年,我如何看待IT行業?

大學畢業 在那 it培訓 在北京 基本上 但是 專業知識 技術 -- 一個行業的發展,會改變很多人的命運、生活。選擇好即將進入的行業,以及通向行業的機構入口,都是十分重要的。近日,一知乎大牛分享了他近10年的IT從業經歷,引起了網友的大範圍轉發和討論。真實的經歷,樸實的語言

馬化騰在提問基礎科學突破,為何1400條回答沒有區塊鏈技術?

騰訊CEO馬化騰在知乎提了6年以來的第一個問題,沉寂已久的知乎社群迎來了久違的熱鬧景象。僅僅花了一天的時間,就有超過40000人關注了提問,285萬人瀏覽,並有超過1400條回答。讀幣哥上知乎看了下,發現排名前列的回答在馬化騰提問後第一時間就已發出,幾乎可以斷定是知乎官方向他們透題了。 毫無疑問

如何看待、餓了麼後端的招聘紛紛由 Python 漸漸轉向 Java?

一開始會覺得php很快,python很靈活,Ruby很拽,c很高深,nodejs很裝逼,JAVA又笨重又麻煩又嚴格又死板。 維護三到五年的大型專案之後才會懂得JAVA的好啊。   還有人說我專案小的時候無所謂,大了之後重新開發一遍都來得及,說這種話的基本上都是不懂

用python爬取的圖片

首先,我們檢視一下知乎的robots協議。 User-agent: * Disallow: / 知乎是不允許爬取其根目錄的。 但是,我們只是用於實驗,而且訪問頻率和正常訪問差距不大,所以可以爬取。 先明確目的: 對手動輸入的網址進行解析 把爬取到的圖片儲存到指定目

[]二戰有哪些細思恐極的細節?---想買這本書了

作者:王鼎傑 連結:https://www.zhihu.com/question/30276520/answer/540971695 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 站在中國的立場上,二戰最細思極恐的一幕,莫過於1940年9月27

用於爬取某個話題下的精華問題所有回答的爬蟲

思路 我的整個演算法的思路還是很簡單的,文字版步驟如下:1、通過話題廣場進入某個話題的頁面,避免了登陸註冊頁面的驗證,查詢到對應要爬取的話題,從 url 中得到話題id2、該頁面的所有資源採用了延遲載入,如果採用模擬瀏覽器進行載入的話還是很麻煩,經研究後發現知乎有前後端資料傳輸的api,所以獲取資料方面

推薦|23個Python爬蟲開源專案程式碼:爬取微信、淘寶、豆瓣、、微博等

今天為大家整理了23個Python爬蟲專案。整理的原因是,爬蟲入門簡單快速,也非常適合新入門的小夥伴培養信心。所有連結指向GitHub,祝大家玩的愉快 1、WechatSogou [1]– 微信公眾號爬蟲。 基於搜狗微信搜尋的微信公眾號爬蟲介面,可以擴充套件成基於搜狗搜尋的爬

用JAVA實現一個爬蟲,爬取的上的內容(程式碼已無法使用)

在學習JAVA的過程中寫的一個程式,處理上還是有許多問題,爬簡單的頁面還行,複雜的就要跪. 爬取內容主要使用URLConnection請求獲得頁面內容,使用正則匹配頁面內容獲得所需的資訊存入檔案,使用正則尋找這個頁面中可訪問的URL,使用佇列儲存未訪問的URL

如何在python論文畫出漂亮的插圖?-from

強烈推薦 Python 的繪圖模組 matplotlib: python plotting 。畫出來的圖真的是高階大氣上檔次,低調奢華有內涵~ 適用於從 2D 到 3D,從標量到向量的各種繪圖。能夠儲存成從 eps, pdf 到 svg, png, jpg 的多種格式。並且 Matplotlib 的繪圖函

微博的 feed 流是如何實現的

談談知乎的Feed。 雖然我不是技術大佬。 簡單來說,Feeds這塊主要包括兩塊內容,就是生成feeds和更新feeds。生成feeds是什麼意思呢,比如我們已經關注的人做了特定操作,我們需要把這些活動加入你的feeds,讓你接收到。更新feeds包括的內容比較多,一種就是你關注點做了更新,比如你新關注了一個

前端:關於阮一峰部落格《學習Javascript閉包》章節最後兩個思考題

阮一峰部落格:《學習Javascript閉包》章節中最後有個思考題: 如果你能理解下面兩段程式碼的執行結果,應該就算理解閉包的執行機制了。 程式碼片段一 var name = "The Window"; var object = { name: "My Obj

[]機器學習使用正則化來防止過擬合是什麼原理?

我們相當於是給模型引數w 添加了一個協方差為1/alpha 的零均值高斯分佈先驗。 對於alpha =0,也就是不新增正則化約束,則相當於引數的高斯先驗分佈有著無窮大的協方差,那麼這個先驗約束則會非常弱,模型為了擬合所有的訓練資料,w可以變得任意大不穩定。alph