一、HashMap存取效率高原因
1、Hash
也叫散列、哈希。
主要用於信息安全領域中的算法,把長度不同的信息轉化為雜亂的128位的編碼,找到一種數據內容與地址之間的映射關系。
註意:不同的輸入可能會散列成相同的輸出
我們最熟悉的Object類中就提供了hashcode的方法。
public native int hashCode();
2、數據結構
Java集合的實現底層大都是基本數據結構的又一層封裝。
數組:尋址容易,插入和刪除困難
鏈表正好相反。
HashMap正好將二者互補了一下,推出了鏈表+數組的組合方式,也叫鏈表散列、“拉鏈法”。
結構示意圖:
放入元素時,根據key值通過hashcode找到對應數組的位置,放入橫向數組的某個格子中。因為前面說到hashcode值不能保證唯一,如果之後hashcode值對應的數組位置中已經有值,就放到相連的鏈表中。
查找元素也是按這個過程來進行。
代碼實現:
註意:每個Node中都持有下一個節點的引用。
3、算法優化
由上面的數據結構介紹,可以看出,在查找的時候,盡量避免查找鏈表能夠大大提高存取效率。
目標:元素盡可能均勻分布,這樣查找的時候不必查找鏈表,效率很高。
思路一:
取模運算,實現是可以實現,但取模運算消耗大、效率不高。
思路二:
首先,&運算比取模運算效率高。
hashmap采用的是下面這種與運算。
大同小異,都是為了減少碰撞,避免hash到同一個位置,使元素分布更均勻。在實現的基礎上,考慮性能問題。
二、Java中ArrayList和LinkedList區別
ArrayList和LinkedList的大致區別如下:
1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
2.對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指針。
3.對於新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。
上代碼:
static final int N=50000;
static long timeList(List list){
long start=system.currentTimeMillis();
Object o = new Object();
for(int i=0;i<N;i++) {
list.add(0, o);
}
return System.currentTimeMillis()-start;
}
static long readList(List list){
long start=System.currentTimeMillis();
for(int i=0,j=list.size();i<j;i++){
}
return System.currentTimeMillis()-start;
}
static List addList(List list){
Object o = new Object();
for(int i=0;i<N;i++) {
list.add(0, o);
}
return list;
}
public static void main(String[] args) {
System.out.println("ArrayList添加"+N+"條耗時:"+timeList(new ArrayList()));
System.out.println("LinkedList添加"+N+"條耗時:"+timeList(new LinkedList()));
List list1=addList(new ArrayList<>());
List list2=addList(new LinkedList<>());
System.out.println("ArrayList查找"+N+"條耗時:"+readList(list1));
System.out.println("LinkedList查找"+N+"條耗時:"+timeList(list2));
}
當我們在集合中裝5萬條數據,測試運行結果如下:
顯然我們可以看出ArrayList更適合讀取數據,linkedList更多的時候添加或刪除數據。
ArrayList內部是使用可増長數組實現的,所以是用get和set方法是花費常數時間的,但是如果插入元素和刪除元素,除非插入和刪除的位置都在表末尾,否則代碼開銷會很大,因為裏面需要數組的移動。
LinkedList是使用雙鏈表實現的,所以get會非常消耗資源,除非位置離頭部很近。但是插入和刪除元素花費常數時間。
三、關於JAVA實現鏈表的基本功能
鏈表結構,通常包含表頭,節點1,節點2...節點n,其中節點又包含了數據內容和下個節點的地址。和數組結構(應該叫做順序表吧大概......)不一樣,鏈表並不用占據連續的內存,它們的區別就不多說了,相信大家都知道。
說說怎麽實現吧,既然要用引用的方式來代替指針,那麽就需要一個特別的類結構:需要同名的成員保存下一個節點的信息。
public class Node {
private String data;
private Node nextNode;
public String getData() {
return data;
}
public void setData(String data) {
this.data = https://my.oschina.net/u/2489417/blog/data;
}
public Node getNextNode() {
return nextNode;
}
public void setNextNode(Node nextNode) {
this.nextNode = nextNode;
}
}
該怎麽使用呢?讓我們來初始化一個鏈表吧!
private Node InitNode() {
// 當前節點
Node curNode = new Node();
// 構建頭結點
Node head = new Node();
head.setData("head");
head.setNextNode(null);
// 當前節點位於頭結點
curNode = head;
// 新增第一個節點
Node n1 = new Node();
// 獲取到當前節點,使得的下一個節點設置為n1
curNode.setNextNode(n1);
n1.setData("node1");
n1.setNextNode(null);
// 當前節點位於第一個節點
curNode = n1;
// 第二個節點
Node n2 = new Node();
curNode.setNextNode(n2);
n2.setData("node2");
n2.setNextNode(null);
curNode = n2;
// 第三個節點
Node n3 = new Node();
curNode.setNextNode(n3);
n3.setData("node3");
n3.setNextNode(new Node());
curNode = n3;
// 第四個節點
Node n4 = new Node();
curNode.setNextNode(n4);
n4.setData("node4");
n4.setNextNode(new Node());
curNode = n4;
return head;
}
註意curNode的變動,使得當前節點總落在最後一個節點上,下次插入時就不需要知道前面一個節點的名字了,通過curNode就可以直接插入了。
到底成功了沒有,我們來遍歷一下。
LinkMain m = new LinkMain();
Node testNode = m.InitNode();
Node iter = testNode.getNextNode();
while (null != iter) {
if (null != iter.getData()) {
System.out.println(iter.getData());
}
iter = iter.getNextNode();
}
輸出結果如下:,
其中testNode是這樣的:
----------------------------分割線---------------------------------------
OK,搞定了初始化和遍歷,讓我們來試試插入一個節點吧,需求是在某個鏈表中,第N個位置插入一個節點temp:
新增原理:對於temp節點來說
紅色代表的是之前連接,黑色的是之後應該做的。
private Node addNode(Node head, int n, Node temp) {
int i = 0;
while (null != head) {
if (i == n) {
temp.setNextNode(head.getNextNode());
head.setNextNode(temp);
return head;
} else {
head = head.getNextNode();
i++;
}
}
return head;
}
新增後再遍歷一下
// 新增一個節點
Node temp = new Node();
temp.setData("tempNode");
temp.setNextNode(null);
Node n3 = m.addNode(testNode, 2, temp);
iter = testNode.getNextNode();
while (null != iter) {
if (null != iter.getData()) {
System.out.println(iter.getData());
}
iter = iter.getNextNode();
}
那麽效果如何呢?
其中testNode的內容應該是
OK,結果正確。
----------------------------分割線---------------------------------------
鏈表的刪除節點功能
一開始還搞不定刪除,後來畫圖分析了下,終於解決,放上代碼。
難點是設置P,Q兩點的指向。其中q是p的下個節點。
應該先找到需要刪除的位置。
原理很簡單,就是要繞過q來連接p和後後(q後面的節點)節點。
刪除前:
刪除後:
Tags: 信息安全 Object public 示意圖 JAVA
文章來源: