目录
代码展示
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
TestSet();
}
public static void TestSet() {
// HashSet是基于哈希表实现的集合,它不保证元素的顺序,添加、删除、查找等操作的平均时间复杂度为常数时间
Set<String> s = new HashSet<>();
// add(key): 如果key不存在,则插入,返回true
// 如果key存在,返回false
boolean isIn = s.add("apple");
s.add("orange");
s.add("peach");
s.add("banana");
System.out.println(s.size());
System.out.println(s);
isIn = s.add("apple");
// add(key): key如果是空,抛出空指针异常
// s.add(null);
// contains(key): 如果key存在,返回true,否则返回false
System.out.println(s.contains("apple"));
System.out.println(s.contains("watermelen"));
// remove(key): key存在,删除成功返回true
// key不存在,删除失败返回false
// key为空,抛出空指针异常
s.remove("apple");
System.out.println(s);
s.remove("watermelen");
System.out.println(s);
Iterator<String> it = s.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
}
}
源码部分
add
1.E是什么
是一个泛型参数,它代表集合中元素的类型。
2.E的例子
当创建一个具体的集合实例时,例如Set<String> s = new TreeSet<>();
,这里的String
就是E
所代表的实际类型。也就是说,E
在这个例子中被确定为String
类型,所以add
方法中的e
参数的类型就是String
,它用于接收一个要添加到集合中的String
类型的元素。
3.E的好处
3.1类型安全
通过使用泛型,编译器可以在编译时检查元素的类型是否符合集合的要求。例如,在Set<String> s
中,如果试图添加一个非String
类型的元素(如一个Integer
),编译器会报错,从而避免了在运行时出现类型不匹配的错误。
3.2代码复用
泛型使得集合类(如Set
接口及其实现类)可以处理多种不同类型的元素,而不需要为每种元素类型编写单独的集合类。例如,Set
接口可以通过指定不同的E
类型来创建可以容纳Integer
类型元素的集合(Set<Integer>
)、容纳Double
类型元素的集合(Set<Double>
)等,大大提高了代码的复用性。
4.插入过程
4.1计算哈希值
- 当调用
s.add("apple")
等添加操作时,首先会根据元素(如 "apple")计算其哈希值。哈希值是通过哈希函数对元素进行计算得到的一个整数值。不同的元素通常会计算出不同的哈希值,但也有可能不同元素计算出相同哈希值(这种情况称为哈希冲突,后面会提到如何处理)。 - 例如,对于 "apple" 这个字符串,哈希函数会根据其字符编码等信息计算出一个对应的哈希值,假设为
hashValue1
。
4.2确定存储位置
4.3处理哈希冲突(如果有)
扩容之后再次找到应该放在数组下标的位置
4.4整个处理的流程HashBuck代码展示
public class HashBuck {
static class Node{
//一个元素由3个部分组成
//存储在哈希桶中的数据元素的一个值
//通过 key % array.length 来实现简单的哈希计算
public int key;
//它是与特定 key 相关联并存储在哈希桶中的实际数据内容
public int val;
public Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
//定义了一个节点数组
public Node[] array;
//计数
public int UsedSize;
//设置哈希桶来接受相同的元素
public HashBuck(){
//它先开辟了一个能容纳 10 个 Node 类型节点的数组作为哈希桶
//然后通过 put 方法将键值对插入到对应的哈希桶位置中
array = new Node[10];
}
//桶中放置元素
public void put(int key,int val){
//找到这个元素在数组中的下标
int index = key % array.length;
//在找到数组中位置的地方设立一个节点指针
Node cur = array[index];
//头插法
while (cur != null){
//遍历这个数组中有没有和插入元素相同的数
//如果有就更新这个相同的val
//因为map不能设置相同的值
if(cur.key == key){
cur.val = val;
return;
}
cur = cur.next;
}
//如果没有就将其进行创建节点
//再头插法连接
Node node = new Node(key, val);
node.next = array[index];
array[index] = node;
UsedSize++;
//尾插法
/*
//开创新节点
Node node = new Node(key, val);
//用这个标志去移动完成尾插法加入新的节点
while (cur.next != null){
cur = cur.next;
}
cur.next = node;
*/
//元素加入桶中了之后要判断是否负载
if(loadFactor() >= 0.75){
//说明已经超载需要扩容
resize();
}
}
/*
* 面试题目:
* 扩容需要注意什么?
* 答:把所有元素重新哈希
* */
private void resize() {
//假设是2倍扩容,不能直接即将元素还放在原来下标
//eg. 12%10=2 12%20=12
Node[] tmpArr = new Node[array.length*2];
//遍历原来数组,将所有元素《重新哈希》到新的数组当中
//如果没有元素则会显示null
for (int i = 0; i < array.length; i++) {
Node cur = array[i];
while (cur != null){
//不能直接即将元素还放在原来下标
int newIndex = cur.key % tmpArr.length;
//记录当前节点的下一个节点
//因为如果cur后面有多个数据,想都先插入再跳过这个数组下标位置
Node curNext = cur.next;
//再头插入
cur.next = tmpArr[newIndex];
tmpArr[newIndex] = cur;
cur = curNext;
}
}
//最终将扩容好覆盖到原来数组
array = tmpArr;
}
private double loadFactor(){
return UsedSize*1.0 / 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;
}
}
contains
1.含义
检查集合中是否包含特定的元素。它是java.util.Collection
接口定义的方法之一
2.功能
检查一个集合是否包含特定的元素
containsKey
1.含义
检查映射(如HashMap
、TreeMap
等)中是否包含指定键(key)的方法
2.功能
containsKey
方法来判断元素是否存在。先计算元素的哈希值,然后根据哈希值找到元素可能存储的位置(索引),再检查该位置及其关联的链表(如果有哈希冲突)中是否存在与给定元素相等的元素。
remove
1.含义
用于从集合中删除元素的方法
2.功能
从集合中移除一个指定的元素
Iterator
1.含义
iterator
是一个方法,用于获取与特定集合(如 List
、Set
等)相关联的迭代器对象。在这里,s
是一个 TreeSet
集合(前面代码定义为 Set<String> s = new TreeSet<>();
),s.iterator()
就是调用 TreeSet
集合的 iterator
方法来获取一个能够遍历该 TreeSet
集合元素的迭代器。
2.功能
它提供了一种统一且通用的方式来访问集合中的元素,而无需关心集合内部的具体存储结构和实现细节。不同类型的集合(如 ArrayList
、HashSet
、TreeSet
等)都可以通过各的 iterator
方法提供适合自身的迭代器,以便按照特定的顺序(例如,TreeSet
按照元素的排序顺序)遍历集合中的元素。
hasNext
1.含义
hasNext
是迭代器接口(Iterator
)中定义的一个方法。当通过 s.iterator()
获取到迭代器对象并赋值给 it
(即 Iterator<String> it = s.iterator();
)后,it.hasNext()
就是调用这个迭代器对象上的 hasNext
方法。它用于判断在当前迭代器位置之后是否还有其他元素可供遍历。
2.功能
在遍历集合元素的过程中,每次循环都会先调用 hasNext
来检查是否还有下一个元素。如果 hasNext
返回 true
,则表示迭代器后面还有未被遍历到的元素,此时可以继续执行后续操作(如调用 next
方法获取下一个元素);如果 hasNext
返回 false
,则说明已经遍历完集合中的所有元素,循环应该结束。
next
1.含义
next
同样是迭代器接口(Iterator
)中定义的一个方法。当 it.hasNext()
返回 true
时,it.next()
就是调用这个迭代器对象上的 next
方法。它用于获取当前迭代器位置之后的下一个元素,并将迭代器的指针向前移动一位,以便下次循环时能够获取到下一个新的元素。
2.功能
在遍历集合的代码逻辑中,通常是在 hasNext
返回 true
的情况下调用 next
方法来逐个获取集合中的元素。例如,在你给出的代码中,通过 System.out.print(it.next() + " ");
将获取到的下一个元素输出到控制台,并在元素后面添加一个空格,从而实现逐个元素的输出展示,直到 hasNext
返回 false
,完成整个集合元素的遍历输出。
综上所述,iterator
用于获取集合的迭代器对象,hasNext
用于判断是否还有后续元素可供遍历,next
用于获取下一个元素并移动迭代器指针,它们共同协作实现了对集合元素的有序、完整的遍历操作。
当遇到方法时,去了解源码会更加理解这个方法的运作