日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

HashMap,ArrayMap,SparseArray 源码角度分析,Android中的数据结构你该如何去选择?

發(fā)布時(shí)間:2023/12/15 Android 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HashMap,ArrayMap,SparseArray 源码角度分析,Android中的数据结构你该如何去选择? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

table = newTab;

可以看到當(dāng)我們的table數(shù)組存儲(chǔ)的節(jié)點(diǎn)值大于threshold時(shí),會(huì)按我們的當(dāng)前數(shù)組大小的兩倍生成一個(gè)新的數(shù)組,并把舊數(shù)組上的數(shù)據(jù)復(fù)制到新數(shù)組上這就是我們的HashMap擴(kuò)容。伴隨著一個(gè)新數(shù)組的生成和數(shù)組數(shù)據(jù)的copy,會(huì)有一定性能上的損耗。如果我們?cè)谑褂肏ashMap的是能夠明確HashMap能夠一開(kāi)始就清楚的知道HashMap存儲(chǔ)的鍵值對(duì)個(gè)數(shù),我建議我們使用HashMap的另一個(gè)構(gòu)造方法。

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

注意了這個(gè)initialCapacity值最好去2的整數(shù)次冪。如果我們要存放40個(gè)鍵值對(duì),那我們這個(gè)initialCapacity最好傳64。至于為什么這樣,我們下次在去討論。

下面我們來(lái)分析HashMap的get方法:

public V get(Object key) {

Node<K,V> e;

return (e = getNode(hash(key), key)) == null ? null : e.value;

}

final Node<K,V> getNode(int hash, Object key) {

Node<K,V>[] tab; Node<K,V> first, e; int n; K k;

if ((tab = table) != null && (n = tab.length) > 0 &&

(first = tab[(n - 1) & hash]) != null) {

if (first.hash == hash && // always check first node

((k = first.key) == key || (key != null && key.equals(k))))

return first;

if ((e = first.next) != null) {

if (first instanceof TreeNode)

return ((TreeNode<K,V>)first).getTreeNode(hash, key);

do {

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

return e;

} while ((e = e.next) != null);

}

}

return null;

}

在get的時(shí)候,我們首先會(huì)根據(jù)我們的key去計(jì)算它的hash值,如果這個(gè)hash值不存在,我們直接反回null。

如果存在,在沒(méi)有發(fā)生hash沖突的情況下也就是根據(jù)當(dāng)前hash值計(jì)算出的索引上的存儲(chǔ)數(shù)據(jù)不是以樹(shù)和鏈表的形式存儲(chǔ)的時(shí)候,我們直接返回當(dāng)前索引上存儲(chǔ)的值,如果時(shí)鏈表樹(shù),我們就去遍歷節(jié)點(diǎn)上的數(shù)據(jù)通過(guò)equals去比對(duì),找到我們需要的在返回。

通過(guò)上面我可以得出結(jié)論,當(dāng)HashMap沒(méi)有發(fā)生hash沖突時(shí),hashMap的查找和插入的時(shí)間復(fù)雜度都是O(1),效率時(shí)非常高的。

當(dāng)我們發(fā)生擴(kuò)容和hash沖突時(shí),會(huì)帶來(lái)一定性能上的損耗。

HashMap大致分析完了。

下面我們來(lái)分析分析Android為我們提供的ArrayMap和SparseArray。

二.我們?cè)趤?lái)看看ArrayMap:


public class A
rrayMap<K, V> extends SimpleArrayMap<K, V> implements Map<K, V> {

MapCollections<K, V> mCollections;

int[] mHashes;

Object[] mArray;

通過(guò)源碼我們可以看到ArrayMap繼承自SimpleArrayMap實(shí)現(xiàn)了Map接口,ArrayMap內(nèi)部是兩個(gè)數(shù)組,一個(gè)存放hash值,一個(gè)存放Obeject對(duì)象也就是value值,這一點(diǎn)就和HashMap不一樣了。我們現(xiàn)來(lái)看看ArrayMap的構(gòu)造方法:

public ArrayMap(int capacity) {

super(capacity);

}

public SimpleArrayMap() {

mHashes = ContainerHelpers.EMPTY_INTS;

mArray = ContainerHelpers.EMPTY_OBJECTS;

mSize = 0;

}

我們發(fā)現(xiàn)ArrayMap的初始化會(huì)給我們初始化兩個(gè)空數(shù)組,并不像HashMap一樣為我們默認(rèn)初始化了一個(gè)大小為16的table數(shù)組,下面我們繼續(xù)往下看:

public V put(K key, V value) {

final int osize = mSize;

final int hash;

int index;

if (key == null) {

hash = 0;

index = indexOfNull();

} else {

hash = key.hashCode();

index = indexOf(key, hash);

}

if (index >= 0) {

index = (index<<1) + 1;

final V old = (V)mArray[index];

mArray[index] = value;

return old;

}

index = ~index;

if (osize >= mHashes.length) {

final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
(osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);

final int[] ohashes = mHashes;

final Object[] oarray = mArray;

allocArrays(n);

if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {

throw new ConcurrentModificationException();

}

if (mHashes.length > 0) {

if (DEBUG) Log.d(TAG, “put: copy 0-” + osize + " to 0");

System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);

System.arraycopy(oarray, 0, mArray, 0, oarray.length);

}

freeArrays(ohashes, oarray, osize);

}

if (index < osize) {

if (DEBUG) Log.d(TAG, "put: move " + index + “-” + (osize-index)

  • " to " + (index+1));

System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);

System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);

}

if (CONCURRENT_MODIFICATION_EXCEPTIONS) {

if (osize != mSize || index >= mHashes.length) {

throw new ConcurrentModificationException();

}

}

mHashes[index] = hash;

mArray[index<<1] = key;

mArray[(index<<1)+1] = value;

mSize++;

return null;

}

我們先看看put方法的實(shí)現(xiàn)。首先就是判段key是否null,是null,hash值直接置為0,如果不為null,通過(guò)Obejct的hashCode()方法計(jì)算出hash值。然后通過(guò)indexfOf方法計(jì)算出index的值。下面我們來(lái)看看indexOf方法:

int indexOf(Object key, int hash) {

final int N = mSize;

// Important fast case: if nothing is in here, nothing to look for.

if (N == 0) {

return ~0;

}

int index = binarySearchHashes(mHashes, N, hash);

// If the hash code wasn’t found, then we have no entry for this key.

if (index < 0) {

return index;

}

// If the key at the returned index matches, that’s what we want.

if (key.equals(mArray[index<<1])) {

return index;

}

// Search for a matching key after the index.

int end;

for (end = index + 1; end < N && mHashes[end] == hash; end++) {

if (key.equals(mArray[end << 1])) return end;

}

// Search for a matching key before the index.

for (int i = index - 1; i >= 0 && mHashes[i] == hash; i–) {

if (key.equals(mArray[i << 1])) return i;

}

// Key not found – return negative value indicating where a

// new entry for this key should go. We use the end of the

// hash chain to reduce the number of array entries that will

// need to be copied when inserting.

return ~end;

}

我們可以看到indexOf方法內(nèi)部是根據(jù)binarySearchHashes()去搜索hash值得,下面我們?cè)賮?lái)看看binarySearchHashes()

內(nèi)部調(diào)用了ContainerHelpers.binarySearch(hashes, N, hash);我們?cè)诳磥?lái)看看binarySearch方法。

static int binarySearch(int[] array, int size, int value) {

int lo = 0;

int hi = size - 1;

while (lo <= hi) {

int mid = (lo + hi) >>> 1;

int midVal = array[mid];

if (midVal < value) {

lo = mid + 1;

} else if (midVal > value) {

hi = mid - 1;

} else {

return mid; // value found

}

}

return ~lo; // value not present

}

可以發(fā)現(xiàn)binarySearch是典型得二叉搜索算法。所以我們可以得出結(jié)論,ArrayMap插入和索引是基于二叉搜索實(shí)現(xiàn)得。這種搜索得效率也很高,他的時(shí)間復(fù)雜度O(log(n)),但是和HashMap O(1)還是有點(diǎn)差距的。

下面我們繼續(xù)看indexOf方法,如果我們通過(guò)二叉搜索查到得index值小于0,代表我們沒(méi)有存儲(chǔ)過(guò)該數(shù)據(jù)則直接返回,如果index大于0,我們就去通過(guò)equals去比對(duì)原來(lái)索引得上得key,如果相等,代表我們存儲(chǔ)過(guò)該值,直接返回index,到時(shí)候我們存儲(chǔ)的時(shí)候會(huì)直接覆蓋掉當(dāng)前已經(jīng)存儲(chǔ)得值。如果不相等,出現(xiàn)Hash沖突,重新計(jì)算出一個(gè)index值返回。

下面我們來(lái)看看ArrayMap如何處理Hash沖突和擴(kuò)容的(我們沒(méi)有指定容量的時(shí)候,ArrayMap默認(rèn)初始化了兩個(gè)空數(shù)組)。

if (osize >= mHashes.length)

出現(xiàn)hash沖突后,如果我們的存儲(chǔ)數(shù)據(jù)數(shù)量大小已經(jīng)大于等于我們的hash數(shù)組的大小。我們對(duì)數(shù)組進(jìn)行擴(kuò)容。

final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
(osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);

if (mHashes.length > 0) {

if (DEBUG) Log.d(TAG, “put: copy 0-” + osize + " to 0");

System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);

System.arraycopy(oarray, 0, mArray, 0, oarray.length);

}

如果我們的osize(已經(jīng)存儲(chǔ)的多少value個(gè)數(shù))大于等于兩倍的BASE_SIZE(常量為4)我們就在原來(lái)osize的基礎(chǔ)上擴(kuò)容0.5倍,

如果我們的osize小于8(兩個(gè)BASE_SIZE)并且大于4(一個(gè)BASE_SIZE),我們將數(shù)組擴(kuò)容到8,否則我們將數(shù)組大小擴(kuò)容到4。

根據(jù)上面的分析,我們可以得出結(jié)論,ArrayMap的插入和索引是基于二分法的。查找和索引效率不如HashMap。但是要比HashMap占用更少的內(nèi)存空間,HashMap擴(kuò)容實(shí)是在原來(lái)起table的基礎(chǔ)上擴(kuò)容一倍,而ArrayMap實(shí)在存儲(chǔ)數(shù)據(jù)的個(gè)數(shù)上擴(kuò)容0.5倍,不會(huì)造成太多的空間浪費(fèi)。在移動(dòng)設(shè)備上內(nèi)存遠(yuǎn)比PC設(shè)備上值錢(qián)的多。ArrayMap的設(shè)計(jì)就是用時(shí)間換取空間。

三.SparseArray:


public SparseArray() {

this(10);

}

public SparseArray(int initialCapacity) {

if (initialCapacity == 0) {

mKeys = EmptyArray.INT;

mValues = EmptyArray.OBJECT;

} else {

mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);

mKeys = new int[mValues.length];

}

mSize = 0;

}

public void put(int key, E value) {

int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

if (i >= 0) {

mValues[i] = value;

} else {

i = ~i;

if (i < mSize && mValues[i] == DELETED) {

mKeys[i] = key;

mValues[i] = value;

return;

}

if (mGarbage && mSize >= mKeys.length) {

gc();

Capacity == 0) {

mKeys = EmptyArray.INT;

mValues = EmptyArray.OBJECT;

} else {

mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);

mKeys = new int[mValues.length];

}

mSize = 0;

}

public void put(int key, E value) {

int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

if (i >= 0) {

mValues[i] = value;

} else {

i = ~i;

if (i < mSize && mValues[i] == DELETED) {

mKeys[i] = key;

mValues[i] = value;

return;

}

if (mGarbage && mSize >= mKeys.length) {

gc();

總結(jié)

以上是生活随笔為你收集整理的HashMap,ArrayMap,SparseArray 源码角度分析,Android中的数据结构你该如何去选择?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。