哈希表(散列表)
哈希表存储的键值对(key:value),针对任意的key通过哈希算法
转换为固定的位置(数组)
key-位置关系并不是一对一,多对一的关系,所以会引发哈希冲突
解决哈希冲突:
数组+链表 链地址法(第一种方式)
Map接口中为什么定义equals和hashCode方法在Map中呢?
自定义类型
resize()的条件:
1)table == null || table.length == 0
2) size > threshold
为什么不同的对象hashCode相同?
hashCode实际指的某一个对象的内存地址,而返回的一个int类型,它是一个有限的集合,
这就会导致哈希值和对象并不是一一对应的关系,所以不同对象来说hashCode有可能会相等
class MyHashMap<K,V>{
private Node<K,V>[] table;
private int size;
class Node<K,V>{
protected int hash;
protected K key;
protected V value;
protected Node<K, V> next;
public Node(int hash, K key, V value) {
this.hash = hash;
this.key = key;
this.value = value;
}
}
class iterator{
public boolean hasNext(Node<K,V> table){
Node<K,V> currentNode = table.next;
return currentNode != null;
}
public Node<K,V> next(Node<K,V> table){
Node<K,V> currentNode = null;
if(hasNext(table)){
}
return null;
}
public Node<K,V> remove(Node<K,V> table){
return null;
}
}
public MyHashMap(int capacity){
table = new Node[capacity];
}
public int hash(K key){
int h;
return (h = key.hashCode()) ^ (h >>> 16);
}
//put 方法的思想:对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。
key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引
indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。
如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。
· 如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
· 如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
· 如果我们再次放入同样的key,在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。
public void put(K key, V value){
//通过key得到索引位置
int hash = hash(key);
int index = table.length-1 & hash;
//判断该index位置是否存在节点
Node<K, V> firstNode = table[index];
if(firstNode == null){
table[index] = new Node(hash, key, value);
size++;
}else{
//查找当前链表中key是否已经存在
if(firstNode.key.equals(key)){
firstNode.value = value;
}else{
Node<K, V> currentNode = firstNode;
while(currentNode.next != null && !currentNode.key.equals(key)){
currentNode = currentNode.next;
}
if(currentNode.next == null){
if(currentNode.key.equals(key)){
currentNode.value = value;
}else{
currentNode.next = new Node(hash, key, value);
size++;
}
}else{
currentNode.value = value;
}
}
}
}
public V get(K key){
//获取对应key所在数组的index
int hash = hash(key);
int index = table.length-1 & hash;
Node<K, V> firstNode = table[index];
if(firstNode == null) {
return null;
}
if(hash == firstNode.hash && key == firstNode.key){
return firstNode.value;
}else{
//遍历链表
Node<K,V> currentNode = firstNode.next;
while(currentNode != null){
if(currentNode.hash == hash && currentNode.key == key){
return currentNode.value;
}
currentNode = currentNode.next;
}
}
return null;
}
public boolean remove(K key){
int hash = hash(key);
int index = hash & table.length-1;
Node<K,V> firstNode = table[index];
if(firstNode == null){
return false;
}else{
//1 2 3 4
while(firstNode.next != null && firstNode.next.next != null){
if(firstNode.key.equals(key) && firstNode.hash == hash){
firstNode.next = firstNode.next.next;
return true;
} else {
firstNode = firstNode.next;
}
}
}
return false;
}
public void reSize(Node<K,V>[] table){
Node<K,V>[] newTable = Arrays.copyOf(table,table.length*2);
for(int i=0;i<table.length;){
if(table[i] != null){
Node<K,V> firstNode = table[i];
newTable[i] = firstNode;
while (firstNode.next != null){
newTable[i].next = firstNode.next;
firstNode = firstNode.next;
}
}else {
i++;
}
}
}
}
1、hashMap迭代有几种方式?写代码描述
7种 4+3(foreach 未给出)
迭代器底层运用foreach实现
public static void main(String[] args) {
//HashMap迭代的方式
HashMap<String, String> map = new HashMap<>();
map.put("name", "zhangsna");
map.put("age", "18");
map.put("gender", "male");
map.put("address", "shannxi");
//使用EntryIterator
// map.Entry
Iterator itr1 = map.entrySet().iterator();
while(itr1.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)itr1.next();
System.out.println("key: " +entry.getKey() + " value: "+entry.getValue());
}
//使用keyIterator
Iterator itr2 = map.keySet().iterator();
while(itr2.hasNext()){
String key = (String)itr2.next();
System.out.println("key: "+key+ " value: "+map.get(key));
}
//使用valueIterator
Iterator itr3 = map.values().iterator();
while(itr3.hasNext()){
String value = (String)itr3.next();
System.out.println("value:"+value);
}
//for each利用EntrySet遍历
for(Map.Entry<String, String> entry:map.entrySet()){
System.out.println("key: "+entry.getKey()+" value: "+entry.getValue());
}
//jdk1.8 forEach进行遍历
map.forEach((k,v)-> System.out.println("key: "+k+" value:"+v));
}
2、HashTable 与 HashMap的区别与联系?
1、实现类、实现接口
HashMap继承自AbstractMap,HashTable继承自Dictionnary
实现接口都是Map、Cloneable、Serializable
2、默认容量
HashMap 16 HashTable 11
3、构造函数
HashMap在第一次put的时候初始化table
HashTale构造函数中初始化table
4、put方法
3、了解LinkedHashMap和TreeMap
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,
先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,
也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
public static void main(String[] args) {
//LinkedHash和TreeMap
//HashMap是无序的
Map<String, String> map = new HashMap<>();
map.put("aadjhd", "123");
map.put("fdsaa", "456");
map.put("fafafa", "789");
map.put("fsafa", "1011");
map.put("agwsggs" "1213");
map.put("ffdafa", "1415");
Iterator itr1 = map.entrySet().iterator();
while(itr1.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)itr1.next();
System.out.println(entry.getValue());
}
System.out.println("======================");
//LinkedHashMap是有序的,按照插入顺序去迭代的
LinkedHashMap<String, String> map1 = new LinkedHashMap<>();
map1.put("aadjhd", "123");
map1.put("fdsaa", "456");/ map1.put("fafafa", "789");
map1.put("fsafa", "1011");
map1.put("agwsggs", "1213");
map1.put("ffdafa", "1415");
Iterator itr2 = map1.entrySet().iterator();
while(itr2.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)itr2.next();
System.out.println(entry.getValue());
}
System.out.println("====================");
//LinkedHashMap是有序的,按照自然顺序去迭代的
TreeMap<String,String> map2 = new TreeMap<>();
map2.put("aadjhd", "123");
map2.put("fdsaa", "456");
map2.put("fafafa", "789");
map2.put("fsafa", "1011");
map2.put("agwsggs", "1213");
map2.put("ffdafa", "1415");
Iterator itr3 = map2.entrySet().iterator();
while(itr3.hasNext()){
Map.Entry<String, String> entry = (Map.Entry<String, String>)itr3.next();
System.out.println(entry.getValue());
}
System.out.println("====================");
}
4、自定义类型作为map的key时,怎么使用?
class PhoneNumber{
private String prefix;
private String number;
public PhoneNumber(String prefix, String number){
this.prefix = prefix;
this.number = number;
}
//使用自定义类型去作为HashMap的key(重写equals方法)
public boolean equals(Object obj){
if(this == obj){
return true;
}
PhoneNumber phe = (PhoneNumber)obj;
return phe.prefix == this.prefix && ((PhoneNumber) obj).number == this.number;
}
public int hashCode(){
int prefixAddress = this.prefix.hashCode();
int numberAddress = this.number.hashCode();
return prefixAddress+numberAddress;
}
}
public class GY20191113 {
public static void main(String[] args) {
//使用自定义类型去作为HashMap的key
HashMap<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber("029", "85658340"), "lvting");
map.put(new PhoneNumber("010", "83145678"), "yongxing");
map.put(new PhoneNumber("020", "62173212"), "mengqi");
map.put(new PhoneNumber("020", "62173212"), "huihui");
map.put(new PhoneNumber("030", "28734878"), "mengfan");
System.out.println(map.toString());
System.out.println(map.get(new PhoneNumber("029", "85658340")));
System.out.println(map.get(new PhoneNumber("020", "62173212")));
}
}
5、解决哈希冲突的方式
(1)开放地扯法
(2)再哈希法
(3)链地址法
(4)建立一个公共溢出区