一、为什么要用哈希表

树的操作通常需要O(N)的时间级,而哈希表中无论存有多少数据,它的插入和查找(有时包括删除)只需要接近常量级的时间,即O(1)的时间级。

但是哈希表也有一定的缺点:它是基于数组的,数组创建后难以扩展。而某些哈希表在基本填满时,性能下降明显,所以事先必须清楚哈希表中将要存储多少数据。而且目前没有一种简便的方法可以对哈希表进行有序(从大到小或者从小到大)的遍历,除非哈希表本身是有序的,但事实上这是违背哈希原则的。

综合以上:当不需要有序遍历数据,而且可以提前预测需要存储的数据项的数目,使用哈希表的结构是十分方便的。

二、哈希化

把巨大的整数(关键字)范围压缩到一个可接受的数组范围内,便于存储和查找。通常来说,我们要存储5000个数据,但数据的关键字范围可能是0-200000。我们不可能去开辟200000的数组去存储这5000个数据,这就需要一个函数把关键字和数组下标对应起来。这就是哈希函数。通常的做法是取余操作。i=N%size;i为下标,N为关键字,size为数组大小。不过通常来说,size设为要存储数据项数目的两倍。

如果哈希表存满时,需要扩展哈希表。我们需要新建一个更大的数组来存储数据,然后把原表中数据一一取出放入新表中。需要注意的是数据放入新表时需要重新用哈希函数计算哈希值,不能直接进行数组的复制,因为哈希函数的size已经变了。

通常而言我们把哈希数组的容量设为一个质数。首先来说假如关键字是随机分布的,那么无所谓一定要模质数。但在实际中往往关键字有某种规律,例如大量的等差数列,那么公差和模数不互质的时候发生碰撞的概率会变大,而用质数就可以很大程度上回避这个问题。对于除法哈希表h(k)=k mod m,注意二进制数对取余就是该二进制数最后r位数。这样一来,Hash函数就和键值(用二进制表示)的前几位数无关了,这样我们就没有完全用到键值的信息,这种选择m的方法是不好的。所以好的方法就是用质数来表示m,使得这些质数,不太接近2的幂或者10的幂。

三、解决冲突

首先一般哈希表是不允许重复的关键字,否则查找函数只能返回最先查到的关键字,无法找到所有的对应数据项。如果重写查找函数让它可以找到所有的对应数据项,这又会使得无论是否是重复关键字,查找操作都要搜索整个表,非常耗时。

存储过程中可能出现存储的数据项关键字不同,但计算出来的哈希值是相同的,这就是冲突。

通常采用以下两种方法来解决冲突。

1、开放地址法

直接在哈希表中找到一个空位,把冲突的数据项存进去。

2、链地址法

把哈希表中存储的数据格式设为链表,这样可以把冲突的数据放入对应位置的链表中即可。

四、开放地址法的java实现

根据在查找下一个空位置时采用的方法,可以把开放地址法分为三种:线性探测、二次探测和再哈希法。

1、线性探测法

线性探测就是根据数组下标一个挨着一个去检测,直到找到一个空位置。

java实现:

DataItem类定义了哈希表存储的数据内容和关键字。

package hash;

class DataItem    //hash表中存放的数据格式
{
private int iData; // 设为关键字
//--------------------------------------------------------------
public DataItem(int ii) // 构造器
{ iData = ii; }
//--------------------------------------------------------------
public int getKey() //获取关键字
{ return iData; }
//--------------------------------------------------------------
} // end class DataItem
////////////////////////////////////////////////////////////////

HashTable类作为哈希表的实现

package hash;

class HashTable        //定义哈希表
{
private DataItem[] hashArray; // 数组形式
private int arraySize; //哈希表的大小
private DataItem nonItem; // 删除数据时,将被删除的数据设为nonItem
//-------------------------------------------------------------
public HashTable(int size) //构造器,指定哈希表的大小
{
arraySize = size;
hashArray = new DataItem[arraySize];
nonItem = new DataItem(-1); // 把nonItem的关键字设为-1
}
//-------------------------------------------------------------
public void displayTable() //显示哈希表
{
System.out.print("Table: ");
for(int j=0; j<arraySize; j++)
{
if(hashArray[j] != null)
System.out.print(hashArray[j].getKey() + " ");
else
System.out.print("** "); //该位置没有存数据
}
System.out.println("");
}
//-------------------------------------------------------------
public int hashFunc(int key)
{
return key % arraySize; // 哈希函数
}
//-------------------------------------------------------------
public void insert(DataItem item) // 插入数据
// 默认表未满,事实上哈希表是不允许存满的,哈希表的大小比实际存储的数据数要大。
{
int key = item.getKey(); // 获取数据项的关键字,用于计算哈希值
int hashVal = hashFunc(key); // 计算哈希值
// 当前位置存有数据并且该数据未被删除
while(hashArray[hashVal] != null &&
hashArray[hashVal].getKey() != -1)
{
++hashVal; // 查找下一个位置
hashVal %= arraySize; // 到达表的末尾时,hashVal值变成1,。构成循环,从而可以查找整个表
}
hashArray[hashVal] = item; // 找到位置
} // end insert()
//-------------------------------------------------------------
public DataItem delete(int key) // 根据关键字删除数据
{
int hashVal = hashFunc(key); // 根据关键字计算哈希值 while(hashArray[hashVal] != null) // 该位置存有数据
{ // 两者的关键字是否相同
if(hashArray[hashVal].getKey() == key)
{
DataItem temp = hashArray[hashVal]; // 保存删除的数据项,用于返回
hashArray[hashVal] = nonItem; // 删除
return temp; // 返回删除的数据项
}
++hashVal; // 关键字不相同,继续查找下一个
hashVal %= arraySize; //循环
}
return null; // 未找到
} // end delete()
//-------------------------------------------------------------
public DataItem find(int key) // 表中是否存在该关键字的数据项
{
int hashVal = hashFunc(key); while(hashArray[hashVal] != null)
{
if(hashArray[hashVal].getKey() == key)
return hashArray[hashVal];
++hashVal;
hashVal %= arraySize;
}
return null;
}
//-------------------------------------------------------------
} // end class HashTable
////////////////////////////////////////////////////////////////

HashTableApp类中包含了主程序

package hash;
import java.io.*; class HashTableApp
{
public static void main(String[] args) throws IOException
{
DataItem aDataItem;
int aKey, size, n, keysPerCell;
System.out.print("Enter size of hash table: ");
size = getInt();//从控制台获取一个整数作为哈希表的大小
System.out.print("Enter initial number of items: ");
n = getInt(); //随机生成n个数作为数据存入哈希表
keysPerCell = 10;//随机生成函数的因子 HashTable theHashTable = new HashTable(size);//初始化 for(int j=0; j<n; j++) // 生成并插入
{
aKey = (int)(java.lang.Math.random() *
keysPerCell * size);
aDataItem = new DataItem(aKey);//封装为哈希表中的数据格式
theHashTable.insert(aDataItem);//插入
} while(true)
{
System.out.print("Enter first letter of ");
System.out.print("show, insert, delete, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
theHashTable.displayTable();
break;
case 'i':
System.out.print("Enter key value to insert: ");
aKey = getInt();
aDataItem = new DataItem(aKey);
theHashTable.insert(aDataItem);
break;
case 'd':
System.out.print("Enter key value to delete: ");
aKey = getInt();
theHashTable.delete(aKey);
break;
case 'f':
System.out.print("Enter key value to find: ");
aKey = getInt();
aDataItem = theHashTable.find(aKey);
if(aDataItem != null)
{
System.out.println("Found " + aKey);
}
else
System.out.println("Could not find " + aKey);
break;
default:
System.out.print("Invalid entry\n");
} // end switch
} // end while
} // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//--------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//--------------------------------------------------------------
} // end class HashTableApp
////////////////////////////////////////////////////////////////

2、二次探测法

线性探测法会发生集聚现象,即冲突数据项会集聚在一起,原因是查找空数据项是一步一步移动的。

二次探测法是为了防止集聚产生的一种尝试方法,思想是探测间隔较远的单元,而不是临近的单元。具体方法是把步长设为探测次数的平方,比如第1次探测步长为1,第2次为4,第3次为9以此类推。

但是二次探测法会产生二次集聚。通常不采用该方法,因为有更好的解决方案。

3、再哈希法

方法是对冲突的关键字用另一个哈希函数计算其值,把结果作为搜索时的步长。这就使得不同的关键字步长不同,避免了集聚现象。

第二个哈希函数必须具备以下条件:

(1)与第一个哈希函数不同

(2)不能得出结果为0,否则步长为0.

通常第二个哈希函数采用如下函数:

step=constant-(key%contant)

constant是一个质数且小于数组容量,key是关键字。step范围在1-constant之间。

再哈希法要求表的容量是一个质数,这是为了使查找操作可以遍历整个表。否则假设表的容量为15,不是一个质数。而查找初始位置为4,查找步长为5,那么每次查找都是固定的三个数,即下标为9,14,4对应的数据。设为质数可以避免这种情况。

再哈希法的java实现

package hashDouble;

class DataItem
{
private int iData; // 关键字
//--------------------------------------------------------------
public DataItem(int ii) // 构造器
{ iData = ii; }
//--------------------------------------------------------------
public int getKey() //获取关键字
{ return iData; }
//--------------------------------------------------------------
} // end class DataItem
////////////////////////////////////////////////////////////////
package hashDouble;

class HashTable
{
private DataItem[] hashArray;
private int arraySize;
private DataItem nonItem;
//-------------------------------------------------------------
HashTable(int size) // 构造器
{
arraySize = size;
hashArray = new DataItem[arraySize];
nonItem = new DataItem(-1);
}
//-------------------------------------------------------------
public void displayTable()
{
System.out.print("Table: ");
for(int j=0; j<arraySize; j++)
{
if(hashArray[j] != null)
System.out.print(hashArray[j].getKey()+ " ");
else
System.out.print("** ");
}
System.out.println("");
}
//-------------------------------------------------------------
public int hashFunc1(int key)
{
return key % arraySize;
}
//-------------------------------------------------------------
public int hashFunc2(int key) //再哈希
{
return 5 - key % 5;
}
//------------------------------------------------------------- public void insert(int key, DataItem item)
// 假设表未满
{
int hashVal = hashFunc1(key); // 计算哈希值
int stepSize = hashFunc2(key); // 计算步长 while(hashArray[hashVal] != null &&
hashArray[hashVal].getKey() != -1)//非空且数据未删除
{
hashVal += stepSize; // 加步长
hashVal %= arraySize; // 循环到表头
}
hashArray[hashVal] = item; // 插入
} // end insert()
//-------------------------------------------------------------
public DataItem delete(int key) // 删除
{
int hashVal = hashFunc1(key); //计算哈希值
int stepSize = hashFunc2(key); // 计算步长 while(hashArray[hashVal] != null) // 非空
{
if(hashArray[hashVal].getKey() == key)//找到
{
DataItem temp = hashArray[hashVal];
hashArray[hashVal] = nonItem;
return temp;
}
hashVal += stepSize;
hashVal %= arraySize;
}
return null; // 无法找到
} // end delete()
//-------------------------------------------------------------
public DataItem find(int key) // 查找
// 假设表未满
{
int hashVal = hashFunc1(key);
int stepSize = hashFunc2(key); while(hashArray[hashVal] != null) // 非空
{
if(hashArray[hashVal].getKey() == key)
return hashArray[hashVal]; // 找到返回
hashVal += stepSize; // 加步长
hashVal %= arraySize;
}
return null; // can't find item
}
//-------------------------------------------------------------
} // end class HashTable
////////////////////////////////////////////////////////////////
package hashDouble;
import java.io.*;
class HashDoubleApp
{
public static void main(String[] args) throws IOException
{
int aKey;
DataItem aDataItem;
int size, n; System.out.print("Enter size of hash table: ");
size = getInt();
System.out.print("Enter initial number of items: ");
n = getInt(); HashTable theHashTable = new HashTable(size); for(int j=0; j<n; j++)
{
aKey = (int)(java.lang.Math.random() * 2 * size);
aDataItem = new DataItem(aKey);
theHashTable.insert(aKey, aDataItem);
} while(true)
{
System.out.print("Enter first letter of ");
System.out.print("show, insert, delete, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
theHashTable.displayTable();
break;
case 'i':
System.out.print("Enter key value to insert: ");
aKey = getInt();
aDataItem = new DataItem(aKey);
theHashTable.insert(aKey, aDataItem);
break;
case 'd':
System.out.print("Enter key value to delete: ");
aKey = getInt();
theHashTable.delete(aKey);
break;
case 'f':
System.out.print("Enter key value to find: ");
aKey = getInt();
aDataItem = theHashTable.find(aKey);
if(aDataItem != null)
System.out.println("Found " + aKey);
else
System.out.println("Could not find " + aKey);
break;
default:
System.out.print("Invalid entry\n");
} // end switch
} // end while
} // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//--------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//--------------------------------------------------------------
} // end class HashDoubleApp
////////////////////////////////////////////////////////////////

五、链地址法的java实现

因为链地址法采用链表作为表中的数据格式,所以允许存储相同的关键字数据。而且我们可以把链设为有序链表。

Link类定义了链表结构

package hashChain;

class Link        //定义链表
{ // 可以动态存储数据,扩充容量
private int iData; // 关键字
public Link next; // 链接到下一个
//-------------------------------------------------------------
public Link(int it) //构造器
{ iData= it; }
//-------------------------------------------------------------
public int getKey() //获取关键字
{ return iData; }
//-------------------------------------------------------------
public void displayLink() // 显示
{ System.out.print(iData + " "); }
} // end class Link
////////////////////////////////////////////////////////////////

SortedList定义了有序链表

package hashChain;

class SortedList   //有序链表
{
private Link first; // 链表头
//-------------------------------------------------------------
public void SortedList() // 构造器
{ first = null; }
//-------------------------------------------------------------
public void insert(Link theLink) // 插入并有序
{
int key = theLink.getKey();
Link previous = null; // 前一个数据项
Link current = first; while( current != null && key > current.getKey() )//非空且关键字大于当前数据关键字
{
previous = current; //继续查找下一个
current = current.next;
}
if(previous==null) // 如果表为空
first = theLink; // 表头指向该数据项
else // 表非空
previous.next = theLink; // key<current.getKey()时,数据项应插入previous后,previous-->theLink
theLink.next = current; // theLink --> current
} // end insert()
//-------------------------------------------------------------
public void delete(int key) //删除
{
Link previous = null;
Link current = first; while( current != null && key != current.getKey() )//未找到
{
previous = current;
current = current.next; // 查找下一个
} if(previous==null) // 要删除数据项为表头
first = first.next; // 删除表头
else // 不是表头
previous.next = current.next; // 删除current
} // end delete()
//-------------------------------------------------------------
public Link find(int key) // 查找
{
Link current = first; while(current != null && current.getKey() <= key)
{
if(current.getKey() == key) // 找到
return current; // 返回
current = current.next; //未找到继续查找下一个
}
return null; //未找到
} // end find()
//-------------------------------------------------------------
public void displayList()//显示
{
System.out.print("List (first-->last): ");
Link current = first;
while(current != null)
{
current.displayLink();
current = current.next;
}
System.out.println("");
}
} // end class SortedList
////////////////////////////////////////////////////////////////

HashTable定义哈希表,表中数据为SortedList形式

package hashChain;

class HashTable
{
private SortedList[] hashArray;
private int arraySize;
//-------------------------------------------------------------
public HashTable(int size) //构造器
{
arraySize = size;
hashArray = new SortedList[arraySize]; // 初始化数组,数组中存储的是链表
for(int j=0; j<arraySize; j++) // 初始化每个数组元素
hashArray[j] = new SortedList();
}
//-------------------------------------------------------------
public void displayTable()
{
for(int j=0; j<arraySize; j++)
{
System.out.print(j + ". ");
hashArray[j].displayList();
}
}
//-------------------------------------------------------------
public int hashFunc(int key) // 计算哈希值
{
return key % arraySize;
}
//-------------------------------------------------------------
public void insert(Link theLink) // 插入数据
{
int key = theLink.getKey(); //获取关键字
int hashVal = hashFunc(key); // 计算关键字哈希值
hashArray[hashVal].insert(theLink); // 插入哈希表中对应的位置
} // end insert()
//-------------------------------------------------------------
public void delete(int key) // 根据关键字删除数据
{
int hashVal = hashFunc(key); // 计算关键字哈希值
hashArray[hashVal].delete(key); // 删除哈希表中对应数据
} // end delete()
//-------------------------------------------------------------
public Link find(int key) // 查找
{
int hashVal = hashFunc(key);
Link theLink = hashArray[hashVal].find(key);
return theLink;
}
//-------------------------------------------------------------
} // end class HashTable
////////////////////////////////////////////////////////////////

主函数

package hashChain;
import java.io.*; class HashChainApp
{
public static void main(String[] args) throws IOException
{
int aKey;
Link aDataItem;
int size, n, keysPerCell = 100;
System.out.print("Enter size of hash table: ");
size = getInt();
System.out.print("Enter initial number of items: ");
n = getInt();
HashTable theHashTable = new HashTable(size); for(int j=0; j<n; j++)
{
aKey = (int)(java.lang.Math.random() *
keysPerCell * size);
aDataItem = new Link(aKey);
theHashTable.insert(aDataItem);
}
while(true)
{
System.out.print("Enter first letter of ");
System.out.print("show, insert, delete, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
theHashTable.displayTable();
break;
case 'i':
System.out.print("Enter key value to insert: ");
aKey = getInt();
aDataItem = new Link(aKey);
theHashTable.insert(aDataItem);
break;
case 'd':
System.out.print("Enter key value to delete: ");
aKey = getInt();
theHashTable.delete(aKey);
break;
case 'f':
System.out.print("Enter key value to find: ");
aKey = getInt();
aDataItem = theHashTable.find(aKey);
if(aDataItem != null)
System.out.println("Found " + aKey);
else
System.out.println("Could not find " + aKey);
break;
default:
System.out.print("Invalid entry\n");
} // end switch
} // end while
} // end main()
//--------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//-------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
//--------------------------------------------------------------
} // end class HashChainApp
////////////////////////////////////////////////////////////////

六、如何设计哈希函数

1、不使用无用数据项

关键字的选取时,要提出原始数据中的无用数据项,例如起始位、校验位、结束位等,因为这些数据位没有携带信息。

2、使用所有的有用数据位

所有的有用数据位在哈希函数中都应当有体现。不要使用前四位或者后五位等其他方法。

3、使用质数作为取模运算的基数。

若关键字完全随机分布,质数和非质数的表现差不多。但是当关键字不是随机分布时,就应该使用质数作为哈希表的大小。使用质数可以是关键字较为平均的映射到哈希表的各个位置。

七、开放地址法和链地址法的比较

开放地址法在表快满时,性能有明显下降,且对哈希表进行扩展时操作复杂。链地址法需要设计链表类,但是不会随着数据项的增多导致性能快速下降,而且可以动态扩展哈希表。

参考文献:

java数据结构与算法(第二版)

哈希表的java实现的更多相关文章

  1. 哈希表,Java中的hashCode

    哈希表: 将我们所需的键通过哈希函数转换索引,然后存储在一个数组中. 哈希表是时间和空间之间的平衡,体现空间换时间的算法思想(联想到预加载,缓存等,有时候多存储,预处理缓存一些东西,带来时间复杂度的改 ...

  2. 数据结构中的哈希表(java实现)利用哈希表实现学生信息的存储

    哈希表 解释 哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过把关键码映射的位置去寻找存放值的地方 内存结构分析图 1.定义一个类为结点,存储的信息 2.定义链表的相关操作 3.定义一个数组存 ...

  3. 【算法】哈希表的诞生(Java)

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  4. Java数据结构和算法 - 哈希表

    Q: 如何快速地存取员工的信息? A: 假设现在要写一个程序,存取一个公司的员工记录,这个小公司大约有1000个员工,每个员工记录需要1024个字节的存储空间,因此整个数据库的大小约为1MB.一般的计 ...

  5. 自己动手实现java数据结构(五)哈希表

    1.哈希表介绍 前面我们已经介绍了许多类型的数据结构.在想要查询容器内特定元素时,有序向量使得我们能使用二分查找法进行精确的查询((O(logN)对数复杂度,很高效). 可人类总是不知满足,依然在寻求 ...

  6. Java知多少(79)哈希表及其应用

    哈希表也称为散列表,是用来存储群体对象的集合类结构. 什么是哈希表 数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系.当要查找一个对象时,只能以某种顺 ...

  7. 哈希表hashTable的Java设计

    1:哈希表的概念 2:设计原理 3:哈希表的Java设计

  8. Java哈希表入门

    Java哈希表(Hash Table) 最近做题经常用到哈希表来进行快速查询,遂记录Java是如何实现哈希表的.这里只简单讲一下利用Map和HashMap实现哈希表. 首先,什么是Map和HashMa ...

  9. 查找-------(HashCode)哈希表的原理

    这段时间 在 准备软件设计师考试    目的是想复习一下  自己以前没怎么学的知识    在这个过程中  有了很大的收获  对以前不太懂得东西  在复习的过程中  有了很大程度的提高 比如在复习 程序 ...

随机推荐

  1. poj 1066 Treasure Hunt 线段相交

    题目链接 题目描述 一个正方形房间被分成若干个小室,宝藏在其中某一点.现可炸开任意一堵墙壁的中点位置.问至少要炸开多少堵墙才能从外面到达宝藏所在地. 思路 (很巧妙,没想到) 直接枚举墙壁与正方形外壁 ...

  2. Linux 之 rsync实现服务器的文件同步

    rsync实现服务器的文件同步 参考文献链接: 一.rsync实现负载均衡集群文件同步,搭建线上测试部署环境 二.rsync. 三.rsync常见错误. 四.rsync 安装使用详解. 环境部署: 服 ...

  3. 为IIS增加PHP支持

    环境: win2008x64 + PHP5.3

  4. C#知识点总结:Monitor和Lock以及区别

    Monitor对象 1.Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,当然在使用过程中为了避免获取 ...

  5. 使用Naive Bayes从个人广告中获取区域倾向

    RSS源介绍:https://zhidao.baidu.com/question/2051890587299176627.html http://www.rssboard.org/rss-profil ...

  6. Javascript 评估用户输入密码的强度

    什么是一个安全的密码呢? 1.如果密码少于5位,那么就认为这是一个弱密码. 2.如果密码只由数字.小写字母.大写字母或其它特殊符号当中的一种组成,则认为这是一个弱密码. 3.如果密码由数字.小写字母. ...

  7. 洛谷—— P3865 【模板】ST表

    https://www.luogu.org/problemnew/show/P3865 题目背景 这是一道ST表经典题——静态区间最大值 请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每 ...

  8. 华硕win7安装ubuntu14.04.02注意事项

    一.win7下划出给ubuntu系统的分区 1.win7自带分磁盘的工具,只需要压缩步骤即可,不需要继续分盘符格式化等操作 win7下为绿色 安装时为free space 二.制作启动盘并安装注意事项 ...

  9. RabbitMQ 核心概念

    目录 RabbitMQ 特点 AMQP 协议 RabbitMQ 消息传递机制 Message Exchange 1. 简介 2. 类型 3. 属性 RabbitMQ 特点 RabbitMQ 相较于其他 ...

  10. Maven教程:tutorialspoint-maven

    来自turorialspoint的Maven教程(英文),官网:http://www.tutorialspoint.com/maven/index.htm 这个教程在国内已经被翻译成中文,官网:htt ...