在讲这个部分之前,请试着去做一下下面这道题。
题:给定一串序列(char[] (小写字母)),要求你将其排序并且不能出现重复也不能有一个缺席。
没错,我们可以这么做:
char[] arr = new char[26];
//假设ch 为某个小写字母;
arr[ch - 'a']++;
我们可以讲这个字母减掉’ ‘ a ’(即 97),这样a对应0,b对应1…
这样我们就只需要遍历一遍数组,只要不是’\u0000’(默认值),就打印or其他。
这样的思想就是“哈希思想”,把一个元素以特定的方法放进数组里,只要知道这个方法,就可以快速查找并且快速发现重复…
Set和Map的最大用处就是查找!
一些实验数据事实表明:
哈希表越满越容易冲突
设计:
public static final double Load_FACTOR = 0.75;//一直处于七五分满
本质上跟【线性探测法】的思路是一致的,放在顺序表的其他空的位置。
但是这个方法是按照一个规律去实现的
【i】就是“跳跃的次数”,即第一次发现该位置有人,跳一次,此时 i为1
这种方法是线性探测法的优化,加快了放置查找的速度
也就是在顺序表“外”
如图所示,让数组的类型为“链表的节点”,遇到该位置已经被占用,就头插进去就好
有些地方用二叉搜索树,甚至高度平衡的二叉树【即红黑树】
如下图所示:
是否是就很像一个一个的“桶”
public class HashBuck {//哈希桶法
static class Node {
}
public Node[] array;
public int useSize;
public static final double Load_FACTOR = 0.75;//"全局"常数
public HashBuck() {
array = new Node[10];
}
public void put(int key) {
}
private void resize() {
}
private double calculateLoadFactor() {
}
public boolean get(int key) {
}
}
//静态内部类
static class Node {
public int key;
public Node next;
public Node(int key) {
this.key = key;
}
}
public Node[] array;
public int useSize;
public static final double Load_FACTOR = 0.75;//"全局"常数
public HashBuck() {
array = new Node[10];//默认大小为10
}
int index = key % array.length;
【取模法是很常见的】public void put(int key) {
int index = key % array.length;
Node cur = array[index];
//检测
while(cur != null) {
if(cur.key == key) {
return;
}else {
cur = cur.next;
}
}
//头插
Node newOne = new Node(key);
newOne.next = array[index];
array[index] = newOne;
//已存放元素+1,并且计算是否超过负载因子最大值,若超过,则扩容重塑
useSize++;
if(calculateLoadFactor() > Load_FACTOR) {
resize();
}
}
private double calculateLoadFactor() {
return (double) useSize / array.length;
}
private void resize() {
//扩容两倍,当然可以其他倍数(系统为1.5倍,随后细说)
Node[] newArr = new Node[array.length * 2];
//重塑
for (int i = 0; i < array.length; i++) {
while(array[i] != null) {
int index = array[i].key % newArr.length;
Node cur = newArr[index];
Node newOne = new Node(array[i].key);
if(cur == null) {
newArr[index] = newOne;
}else {
// 尾插法 while(cur.next != null) {
// cur = cur.next;
// }
// cur.next = new Node(array[i].key, array[i].val);
newOne.next = cur;
newArr[index] = newOne;
}
array[i] = array[i].next;
}
}
//指向新的节点数组
array = newArr;
}
public boolean get(int key) {
int index = key % array.length;
Node cur = array[index];
while(cur != null) {
if(cur.key == key) {
return true;
}
cur = cur.next;
}
return false;
}
public class HashBuck {
static class Node {
public int key;
public int val;
public Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
public Node[] array;
public int useSize;
public static final double Load_FACTOR = 0.75;//"全局"常数
public HashBuck() {
array = new Node[10];
}
public void put(int key, int val) {
int index = key % array.length;
Node cur = array[index];
while(cur != null) {
if(cur.key == key) {
cur.val = val;
return;
}else {
cur = cur.next;
}
}
Node newOne = new Node(key, val);
newOne.next = array[index];
array[index] = newOne;
useSize++;
if(calculateLoadFactor() > Load_FACTOR) {
resize();
}
}
private void resize() {
Node[] newArr = new Node[array.length * 2];
for (int i = 0; i < array.length; i++) {
while(array[i] != null) {
int index = array[i].key % newArr.length;
Node cur = newArr[index];
Node newOne = new Node(array[i].key, array[i].val);
if(cur == null) {
newArr[index] = newOne;
}else {
// 尾插 while(cur.next != null) {
// cur = cur.next;
// }
// cur.next = new Node(array[i].key, array[i].val);
newOne.next = cur;
newArr[index] = newOne;
}
array[i] = array[i].next;
}
}
array = newArr;
}
private double calculateLoadFactor() {
return (double) useSize / array.length;
}
public int get(int key) {
int index = key % array.length;
Node cur = array[index];
while(cur != null) {
if(cur.key == key) {
return cur.val;
}
cur = cur.next;
}
return -1;
}
}
hashcode()
,这个方法可以获取到哈希值(引用类型的身份证),这样就可以进行取模运算了
hashcode()
方法,String系统已经重写了public class MyHashMap <K, V> {
//如果用到泛型的话,记住要重写一些方法,如equals 和 hashCode
//以及在特定位置补上自己的哈希方法
static class Node<K, V> {
public K key;
public V val;
public Node<K, V> next;
public Node(K key, V val) {
this.key = key;
this.val = val;
}
}
public Node<K, V>[] array;
public int useSize;
public static final double Load_FACTOR = 0.75;//"全局"常数
public MyHashMap() {
array = (Node<K,V>[])(new Node[10]);
}
public void put(K key, V val) {
int index = key.hashCode() % array.length;
Node<K, V> cur = array[index];
while(cur != null) {
if(cur.key.equals(key)) {
cur.val = val;
return;
}else {
cur = cur.next;
}
}
Node<K, V> newOne = new Node<>(key, val);
newOne.next = array[index];
array[index] = newOne;
useSize++;
if(calculateLoadFactor() > Load_FACTOR) {
resize();
}
}
private void resize() {
Node<K, V>[] newArr = (Node<K,V>[]) (new Node[array.length * 2]);
for (int i = 0; i < array.length; i++) {
while(array[i] != null) {
int index = array[i].key.hashCode() % newArr.length;
Node<K, V> cur = newArr[index];
Node<K, V> newOne = new Node<>(array[i].key, array[i].val);
if(cur == null) {
newArr[index] = newOne;
}else {
// 尾插 while(cur.next != null) {
// cur = cur.next;
// }
// cur.next = new Node(array[i].key, array[i].val);
newOne.next = cur;
newArr[index] = newOne;
}
array[i] = array[i].next;
}
}
array = newArr;
}
private double calculateLoadFactor() {
return (double) useSize / array.length;
}
public V get(int key) {
int index = key % array.length;
Node<K, V> cur = array[index];
while(cur != null) {
if(cur.key.equals(key)) {
return cur.val;
}
cur = cur.next;
}
return null;
}
}
一般用普通类实例化接口的形式,这样这个引用的功能更加具有针对性。
接下来来看看Map的一些基本功能(高亮即重点)
方法 | 解释 |
---|---|
V get(Object key) | 返回key对应的value值 |
V getOrDefault(Object key, V defaultValue) | 返回对应value,不存在则返回defaultValue |
V put(K key, V value) | 放置键值对(key重复则覆盖) |
V remove(Object key) | 根据唯一的key删除键值对 |
Set keySet() | key不重复,将所有key值放在Set里并返回 |
Collection values() | 将所有values放在集合中并返回 |
Set |
返回所有键值对集(entry即条目) |
boolean containsKey(Object key) | 是否存在key值 |
boolean containsValue(Object value) | 是否存在value值 |
其中还有一个特别重要的静态内部接口,Entry
这里我们可以理解为,将key和value打包起来了,成为一个独立的引用,其实里面
方法 | 解释 |
---|---|
K getKey() | 获取键值对的key值 |
V getValue() | 获取键值对的value值 |
V setValue(V value) | 设置键值对的value值 |
Set<Map.Entry<Integer, Integer>> set = hashMap.entrySet();
Map.Entry<Integer, Integer>[] entry = (Map.Entry<Integer, Integer>[]) set.toArray();//把map的 key 和 value 组装成一个整体
entry[0].getKey();
entry[0].getValue();
entry[0].setValue();
无法设置key,转化为Set有什么用,等一下将Set的时候细说!
接下来解答一些疑惑,就是为什么返回类型是接口/抽象类型,但是仍然可以正常使用?
至于Entry的本质
下面是Set的基本功能(高亮即重点)
方法 | 解释 |
---|---|
boolean add(E e) | 增加元素,重复则会失败,返回false |
void clear() | 清空集合 |
boolean contains(Object o) | 查看元素是否存在 |
Iterator iterator() | 返回迭代器 |
boolean remove(Object o) | 删除对应元素 |
int size() | 返回集合大小 |
boolean isEmpty() | 集合是否为空 |
Object[] toArray() | 转化为数组(非基本数据类型数组!) |
boolean containsAll(Collection> c) | 查询c集合的所有元素是否个个都存在 |
boolean addAll(Collection extends E> c) | 添加一整个集合的所有元素,去重作用(完全不重复则返回true)(有现成的集合类的时候适用) |
这个“工具”的作用就是遍历集合
用法如下:
hasNext() 与 next()
这两个方法打配合public static void main(String[] args) {
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("小卡拉", 3);
hashMap.put("马拉圈", 4);
hashMap.put("芜湖", 5);
System.out.println(hashMap.get("小"));
Set<Map.Entry<String, Integer>> set = hashMap.entrySet();
//for-each 语法遍历
for(Map.Entry<String, Integer> s : set) {
System.out.print(s.getKey() + " " + s.getValue() + " ");
}
//迭代器遍历法
Iterator<Map.Entry<String, Integer>> iter = set.iterator();
//Iterator> 用通配符也可以
while(iter.hasNext()) {
System.out.print(iter.next() + " ");
}
}
这个方法有两个用法
(T[])set.toArray();
//需要强制类型转化!由于擦除机制,数组的返回是Object[]
set.toArray(arr);
//这个arr必须是对应的非基本数据类型数组!或者父类数组
//set里面的元素被整合到arr里了
//返回值可以不接收,接收的话,方式跟上面一样
需要重点强调的一点是,基本数据类型的数组与其包装类的数组不能直接相互转化(自然也不存在自动拆箱装箱),必须遍历一遍!!!
其他方法相对简单,不细说
TREEIFY_THRESHOLD
树化方法treeifyBin
就不带大家看了
这个操作是将容量调整为最接近的二进制数(较大)
在一个构造方法中有用到
文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭!这是我的代码仓库!(在马拉圈的23.2里)代码仓库
Map&Set · 代码位置