title: 与人工智能协作,java基础篇,2025面试题过关斩将
date: 2025-02-18 11:38:53
tags:
- java锁机制
- java集合
- ConcurrentHashMap与CAS
与人工智能协作,java基础篇,2025面试题过关斩将
给每一个找工作和非找工作的人观看。
众所周知,工作中呢,基础知识就会忘得很快,因为工作中很少会留意到这种基础知识,或者是说工作中具体功能用的比较多,但是不会关心是由什么实现的。
毕竟上班已经很忙了,需求在催、业务在催、自己空闲的时候也很难去关注这些基础知识。
OK,回到今天的问题,与AI协作巩固基础知识。
CAP理论 (一致性、可用性、分区容错性)
这时一个架构方面的问题,根据不同的业务场景,你要去怎么设计你的程序代码?
一、CAP理论的三个特性
- **一致性(Consistency)**:
- 在分布式系统中,若多节点同时操作同一数据,所有节点最终应得到一致的结果。
- 强一致性指所有节点数据状态同时相同,但在分布式系统中实现强一致性较为困难,因此有时会采用最终一致性作为折中方案。
- **可用性(Availability)**:
- 分布式系统需确保无论何种故障,用户请求都能在有限时间内得到响应,系统不能拒绝服务或长时间无响应。
- **分区容错性(Partition Tolerance)**:
- 分布式系统可能因网络、硬件故障等原因被分割成多个独立区域,分区容错性要求系统在此情况下仍能提供服务。
二、CAP理论的核心思想
CAP理论的核心思想是,在分布式系统中,由于网络分区和节点故障是不可避免的,而保证一致性和可用性需要对网络分区做出不同的权衡,因此分布式系统最多只能同时满足一致性、可用性和分区容错性这三个特性中的两个。
三、CAP理论的权衡与取舍
在设计分布式系统时,需要根据具体的应用场景和需求,在CAP三个特性之间进行权衡和取舍。以下是一些常见的权衡方案:
- **CA(一致性+可用性)**:
- 牺牲分区容错性。
- 适用于对一致性和可用性要求高,但对分区容错性要求不高的场景。
- 例如,传统的单节点数据库(如MySQL单机模式)在发生网络分区时会选择停止服务,以保证一致性和可用性。
- **CP(一致性+分区容错性)**:
- 牺牲可用性。
- 适用于对一致性要求高,可容忍系统一定程度上不可用的场景。
- 例如,金融交易系统为了保证数据的一致性,可能会在网络分区时暂停服务,直到网络恢复并同步数据。
- **AP(可用性+分区容错性)**:
- 牺牲一致性。
- 适用于对高可用性要求高,而一致性要求较低的场景。
- 例如,社交媒体和缓存系统通常会优先保证系统的可用性和分区容错性,而允许在短时间内出现数据不一致的情况。
四、实际应用例子
-
银行系统:
- 银行系统对数据的一致性要求非常高,因为错误的交易数据可能会导致严重的财务问题。
- 因此,银行系统可能会选择CP组合,牺牲一定的可用性来保证数据的一致性。
-
互联网服务:
- 互联网服务对系统的可用性要求非常高,因为用户期望能够随时随地访问服务。
- 因此,互联网服务可能会选择AP组合,牺牲一定的数据一致性来保证系统的高可用性和分区容错性。例如,社交媒体平台在面临网络分区时可能会继续提供服务,即使这意味着用户可能会看到稍微过时的数据。
ConcurrentHashMap的分段锁和CAS有什么区别
在Java并发编程中,
ConcurrentHashMap
是一个非常重要的线程安全的哈希表实现。为了优化并发性能,ConcurrentHashMap
在不同版本中采用了不同的技术,其中包括分段锁和CAS(Compare-And-Swap)操作。以下是这两种技术的详细解释和它们之间的区别:分段锁(Segment Locking)
定义与应用:
- 分段锁是
ConcurrentHashMap
在Java 7及之前版本中使用的一种锁机制。 - 它将整个哈希表分为多个段(Segment),每个段相当于一个小的哈希表,并独立进行锁定操作。
- 这样,多个线程可以同时访问不同的段,而不会发生冲突,从而极大地提高了并发性能。
工作原理:
ConcurrentHashMap
内部维护一个Segment
数组,每个Segment
都实现了ReentrantLock
接口,用于获取锁。- 当需要插入或更新元素时,会根据键的哈希值判断它属于哪个段,然后对该段进行加锁操作。
- 如果多个线程尝试访问同一个段,则只有一个线程能够获取锁,其他线程会等待或重新尝试。
优势:
- 通过细化锁的粒度,分段锁允许更多的并发访问,特别是在读操作多于写操作的场景中。
CAS(Compare-And-Swap)
定义与应用:
- CAS是一种无锁的原子操作,用于确保多线程环境下的数据一致性。
- 在Java 8及之后的版本中,
ConcurrentHashMap
采用了更细粒度的锁和CAS操作,以进一步提高并发性能。
工作原理:
- CAS操作包含三个操作数:内存值V、预期值A和新值B。
- 操作过程是将内存值V与预期值A进行比较,如果相等,则将内存值V更新为新值B,并返回操作是否成功的结果。
- 如果不相等,则说明有其他线程已经修改了该值,此时当前线程会重新尝试CAS操作。
在
ConcurrentHashMap
中的应用:- 当需要对某个节点进行更新操作时,
ConcurrentHashMap
会使用CAS操作来保证线程安全。 - 例如,在插入新元素时,会先尝试使用CAS操作更新节点的值,如果成功,则完成插入;如果失败,则说明有其他线程已经修改了该节点,此时会重新尝试插入或采取其他措施。
优势:
- CAS操作是一种非阻塞的并发控制方式,避免了锁的竞争和阻塞,从而提高了并发性能。
- 它适用于简单的变量更新操作,并且可以在不使用锁的情况下保证数据的一致性。
区别与总结
- 锁机制不同:分段锁是通过将哈希表分为多个段并独立锁定来提高并发性能的;而CAS是一种无锁的原子操作,通过比较并交换内存值来实现数据一致性。
- 适用场景不同:分段锁更适合于读操作多于写操作的场景;而CAS更适用于简单的变量更新操作和高并发环境。
- 在
ConcurrentHashMap
中的演变:在Java 7及之前版本中,ConcurrentHashMap
主要使用分段锁机制;而在Java 8及之后版本中,则采用了更细粒度的锁和CAS操作来进一步提高并发性能。
综上所述,分段锁和CAS都是
ConcurrentHashMap
中用于优化并发性能的重要技术。它们在不同的应用场景下发挥着各自的优势,共同为Java并发编程提供了高效、线程安全的哈希表实现。
JAVA的锁机制
1. 公平锁与非公平锁
- 公平锁:指多个线程按照申请锁的顺序来获取锁,类似于日常的排队。这种锁保证了线程获取锁的公平性,避免了线程饥饿问题。
- 非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序来,通俗来说就是插队。非公平锁的性能通常比公平锁更高,因为它减少了线程切换和上下文切换的开销。但可能会导致某些线程长时间等待而无法获取锁。
2. 可重入锁(ReentrantLock)
- 定义:可重入锁是一种允许同一个线程多次获取锁的锁机制。
- 特点:
- 支持手动释放锁,通常使用try-finally结构来确保锁最终被释放。
- 可以创建公平锁和非公平锁。
- 支持可中断的锁请求和锁尝试机制。
- 适用场景:适用于需要多次获取锁的递归调用场景。
3. 自旋锁
- 定义:自旋锁是一种尝试获取锁的循环方式,不会使线程阻塞。当线程尝试获取锁失败时,它会不断循环尝试获取锁,直到成功为止。
- 特点:
- 减少了线程上下文切换的开销,但在长时间自旋时会浪费CPU资源。
- 适用于锁持有时间较短的场景。
- 适用场景:适用于多处理器系统中,且锁持有时间较短的场景。
4. 共享锁与独占锁
- **共享锁(读锁)**:允许多个线程同时读取共享资源,但不允许修改。这种锁适用于读多写少的场景,可以提高并发性能。
- **独占锁(写锁)**:在一个线程写数据时,不允许其他线程读或写。这种锁保证了数据的一致性和完整性。
5. StampedLock
- 定义:StampedLock是ReadWriteLock的改进版,主要用于高性能的读多写少场景。它引入了一个称为“戳”的概念,可以在读操作中乐观地获取锁。
- 特点:
- 乐观读锁不会阻止写操作,可以提升读操作的性能。
- 悲观读锁和写锁类似于ReentrantReadWriteLock中的锁。
- 适用场景:适用于读多写少且对读性能要求较高的场景。
区别与联系
- 公平锁与非公平锁:主要区别在于线程获取锁的顺序。公平锁保证了线程获取锁的公平性,但性能较低;非公平锁性能较高,但可能导致线程饥饿。
- 可重入锁与自旋锁:可重入锁允许同一个线程多次获取锁,而自旋锁则是一种尝试获取锁的循环方式。两者可以结合使用,例如在可重入锁的实现中加入自旋机制以提高性能。
- 共享锁与独占锁:共享锁允许多个线程同时读取共享资源,而独占锁则不允许其他线程在写数据时读或写。两者通常用于读写分离的场景中以提高并发性能。
综上所述,Java中的锁机制种类繁多,各有特点和适用场景。在选择锁机制时需要根据具体的应用场景和需求进行权衡和选择。
Java中各种锁机制的简单代码
这些示例展示了每种锁机制的核心用法和特性。
1. 公平锁与非公平锁
Java中的ReentrantLock
类支持创建公平锁和非公平锁。
import java.util.concurrent.locks.ReentrantLock;
public class FairAndNonFairLockExample {
// 公平锁
private static final ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁
private static final ReentrantLock nonFairLock = new ReentrantLock();
public static void main(String[] args) {
Runnable fairTask = () -> {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了公平锁");
} finally {
fairLock.unlock();
}
};
Runnable nonFairTask = () -> {
nonFairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了非公平锁");
} finally {
nonFairLock.unlock();
}
};
// 创建线程并启动
Thread thread1 = new Thread(fairTask, "线程1");
Thread thread2 = new Thread(fairTask, "线程2");
Thread thread3 = new Thread(nonFairTask, "线程3");
Thread thread4 = new Thread(nonFairTask, "线程4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
2. 可重入锁(ReentrantLock)
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Runnable task = () -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了锁");
// 递归调用
someMethod();
} finally {
lock.unlock();
}
};
// 创建线程并启动
Thread thread1 = new Thread(task, "线程1");
Thread thread2 = new Thread(task, "线程2");
thread1.start();
thread2.start();
}
private static void someMethod() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 再次获取了锁(递归调用)");
} finally {
lock.unlock();
}
}
}
3. 自旋锁
Java标准库中没有直接提供自旋锁的实现,但我们可以自己实现一个简单的版本。
public class SpinLock {
private volatile boolean isLocked = false;
public void lock() {
while (isLocked) {
// 自旋等待
}
isLocked = true;
}
public void unlock() {
isLocked = false;
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable task = () -> {
spinLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了自旋锁");
} finally {
spinLock.unlock();
}
};
// 创建线程并启动
Thread thread1 = new Thread(task, "线程1");
Thread thread2 = new Thread(task, "线程2");
thread1.start();
thread2.start();
}
}
4. 共享锁与独占锁
Java中的ReentrantReadWriteLock
类提供了共享锁(读锁)和独占锁(写锁)。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public static void main(String[] args) {
Runnable readTask = () -> {
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了读锁");
} finally {
rwLock.readLock().unlock();
}
};
Runnable writeTask = () -> {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了写锁");
} finally {
rwLock.writeLock().unlock();
}
};
// 创建线程并启动
Thread thread1 = new Thread(readTask, "线程1");
Thread thread2 = new Thread(readTask, "线程2");
Thread thread3 = new Thread(writeTask, "线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
5. StampedLock
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private static final StampedLock stampedLock = new StampedLock();
public static void main(String[] args) {
Runnable readTask = () -> {
long stamp = stampedLock.tryOptimisticReadLock();
try {
// 乐观读锁尝试获取数据
System.out.println(Thread.currentThread().getName() + " 尝试获取乐观读锁");
// 验证锁的有效性
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
// 获取悲观读锁
System.out.println(Thread.currentThread().getName() + " 获取了悲观读锁");
} finally {
stampedLock.unlockRead(stamp);
}
}
} finally {
// 乐观锁不需要显式解锁
}
};
Runnable writeTask = () -> {
long stamp = stampedLock.writeLock();
try {
System.out.println(Thread.currentThread().getName() + " 获取了写锁");
} finally {
stampedLock.unlockWrite(stamp);
}
};
// 创建线程并启动
Thread thread1 = new Thread(readTask, "线程1");
Thread thread2 = new Thread(writeTask, "线程2");
thread1.start();
thread2.start();
}
}
Java线程池
线程池的各个参数
核心线程数、最大线程数、线程存活时间、单位、线程工厂、线程队列、线程拒绝handler。
线程池在Java中主要通过java.util.concurrent.Executors
工厂类来创建,也可以通过ThreadPoolExecutor
构造函数来创建。以下是线程池的主要参数及其解释:
- **corePoolSize(核心线程数)**:
- 线程池中的核心线程数,即使线程处于空闲状态,线程池也会保留核心线程数。只有当工作队列满时,才会创建超出核心线程数的线程。
- **maximumPoolSize(最大线程数)**:
- 线程池中允许的最大线程数。当工作队列满且已创建的线程数小于最大线程数时,线程池会尝试创建新的线程来处理任务。
- **keepAliveTime(线程空闲时间)**:
- 当线程数超过核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。
- **unit(时间单位)**:
keepAliveTime
参数的时间单位,常用的有TimeUnit.MILLISECONDS
、TimeUnit.SECONDS
等。
- **workQueue(工作队列)**:
- 用于保存等待执行的任务的阻塞队列。常见的队列类型有
LinkedBlockingQueue
(基于链表的阻塞队列)、ArrayBlockingQueue
(基于数组的阻塞队列)、SynchronousQueue
(一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作)等。
- 用于保存等待执行的任务的阻塞队列。常见的队列类型有
- **threadFactory(线程工厂)**:
- 用于创建新线程的工厂。如果没有指定,则使用
Executors.defaultThreadFactory()
来创建线程。
- 用于创建新线程的工厂。如果没有指定,则使用
- **handler(拒绝执行处理器)**:
- 当线程池和工作队列都满了时,用于处理被拒绝的任务的处理器。常见的拒绝策略有
AbortPolicy
(抛出RejectedExecutionException
)、CallerRunsPolicy
(在调用者的线程中运行被拒绝的任务)、DiscardPolicy
(丢弃被拒绝的任务)和DiscardOldestPolicy
(丢弃最早的未处理的任务)等。
- 当线程池和工作队列都满了时,用于处理被拒绝的任务的处理器。常见的拒绝策略有
Java数据结构
线性结构
1. 数组(Array)
特点:
- 固定大小的数据结构,用于存储相同类型的元素。
- 访问速度快,但插入和删除操作效率较低(需要移动元素)。
用途:
- 存储固定大小的集合。
- 用于实现静态数据结构,如栈和队列的底层实现。
int[] array = new int[5];
array[0] = 1;
array[1] = 2;
// ...
2. 链表(LinkedList)
特点:
- 由一系列节点组成,每个节点包含数据和指向下一个节点的引用。
- 支持快速的插入和删除操作,但访问速度较慢(需要从头节点开始遍历)。
用途:
- 实现动态数据结构,如动态数组、栈、队列和哈希表。
- 用于表示具有顺序关系的元素集合。
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
// ...
3. 栈(Stack)
特点:
- 后进先出(LIFO)的数据结构。
- 支持快速的插入(压栈)和删除(弹栈)操作。
用途:
- 实现表达式求值、语法解析和递归调用等功能。
- 用于保存临时数据。
示例代码(使用Java内置的Stack
类):
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
Integer top = stack.pop();
4. 队列(Queue)
特点:
- 先进先出(FIFO)的数据结构。
- 支持快速的插入(入队)和删除(出队)操作。
用途:
- 实现任务调度、消息传递和广度优先搜索等功能。
- 用于保存待处理的任务或事件。
示例代码(使用Java内置的LinkedList
作为队列):
LinkedList<Integer> queue = new LinkedList<>();
queue.add(1);
queue.add(2);
Integer head = queue.poll();
非线性结构
5. 树(Tree)
特点:
- 具有层次结构的数据结构,每个节点可以包含子节点。
- 支持快速的查找、插入和删除操作(取决于树的类型,如二叉搜索树、平衡树等)。
用途:
- 实现文件系统、数据库索引和表达式树等功能。
- 用于表示具有层次关系的数据集合。
示例代码(以二叉搜索树为例,使用自定义类实现):
class TreeNode {
int val;
TreeNode left;
TreeNode right;
// 构造函数、getter和setter方法
}
// 插入、查找和删除操作的实现...
6.哈希表
import java.util.LinkedList;
import java.util.List;
public class HashTable<K, V> {
private static final int DEFAULT_CAPACITY = 16;
private List<LinkedList<Entry<K, V>>> table;
private int size;
// 哈希表条目类
private static class Entry<K, V> {
K key;
V value;
Entry(K key, V value) {
this.key = key;
this.value = value;
}
}
// 构造函数,初始化哈希表
public HashTable() {
this(DEFAULT_CAPACITY);
}
public HashTable(int capacity) {
table = new LinkedList[capacity];
for (int i = 0; i < capacity; i++) {
table[i] = new LinkedList<>();
}
this.size = 0;
}
// 哈希函数
private int hash(K key) {
return Math.abs(key.hashCode()) % table.length;
}
// 插入键值对
public void put(K key, V value) {
int index = hash(key);
LinkedList<Entry<K, V>> bucket = table[index];
for (Entry<K, V> entry : bucket) {
if (entry.key.equals(key)) {
entry.value = value; // 如果键已存在,则更新值
return;
}
}
bucket.add(new Entry<>(key, value));
size++;
}
// 删除键值对
public V remove(K key) {
int index = hash(key);
LinkedList<Entry<K, V>> bucket = table[index];
for (Entry<K, V> entry : bucket) {
if (entry.key.equals(key)) {
V value = entry.value;
bucket.remove(entry);
size--;
return value;
}
}
return null; // 如果键不存在,则返回null
}
// 查找值
public V get(K key) {
int index = hash(key);
LinkedList<Entry<K, V>> bucket = table[index];
for (Entry<K, V> entry : bucket) {
if (entry.key.equals(key)) {
return entry.value;
}
}
return null; // 如果键不存在,则返回null
}
// 打印哈希表中的所有键值对
public void printTable() {
for (int i = 0; i < table.length; i++) {
LinkedList<Entry<K, V>> bucket = table[i];
System.out.println("Bucket " + i + ": " + bucket);
}
}
// 获取哈希表的大小
public int getSize() {
return size;
}
// 判断哈希表是否为空
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
HashTable<String, Integer> hashTable = new HashTable<>();
hashTable.put("apple", 1);
hashTable.put("banana", 2);
hashTable.put("orange", 3);
System.out.println("Hash table elements: ");
hashTable.printTable();
System.out.println("Value for key 'banana': " + hashTable.get("banana"));
System.out.println("Removed key 'apple', new hash table elements: ");
hashTable.remove("apple");
hashTable.printTable();
}
}
7. 图(Graph)
特点:
- 由节点和边组成的数据结构,用于表示实体之间的关系。
- 支持复杂的遍历和搜索操作(如深度优先搜索、广度优先搜索等)。
用途:
- 实现社交网络、地图和电路图等功能。
- 用于表示具有复杂关系的数据集合。
示例代码(使用自定义类实现):
class Graph {
private Map<Integer, List<Integer>> adjList;
// 构造函数、添加边和遍历方法
}
// 遍历、搜索和路径查找操作的实现...
8.堆
import java.util.Arrays;
public class MaxHeap {
private int[] heap;
private int size;
private int capacity;
// 构造函数,初始化最大堆
public MaxHeap(int capacity) {
this.capacity = capacity;
this.heap = new int[capacity];
this.size = 0;
}
// 交换数组中的两个元素
private void swap(int i, int j) {
int temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
// 上移操作,维护最大堆性质
private void heapifyUp(int index) {
while (index != 0 && heap[(index - 1) / 2] < heap[index]) {
swap((index - 1) / 2, index);
index = (index - 1) / 2;
}
}
// 下移操作,维护最大堆性质
private void heapifyDown(int index) {
int largest = index;
int left = 2 * index + 1;
int right = 2 * index + 2;
if (left < size && heap[left] > heap[largest]) {
largest = left;
}
if (right < size && heap[right] > heap[largest]) {
largest = right;
}
if (largest != index) {
swap(index, largest);
heapifyDown(largest);
}
}
// 插入元素
public void insert(int element) {
if (size == capacity) {
throw new IllegalStateException("Heap is full");
}
heap[size] = element;
size++;
heapifyUp(size - 1);
}
// 删除并返回最大元素
public int extractMax() {
if (size == 0) {
throw new IllegalStateException("Heap is empty");
}
int max = heap[0];
heap[0] = heap[size - 1];
size--;
heapifyDown(0);
return max;
}
// 打印堆中的元素
public void printHeap() {
System.out.println(Arrays.toString(Arrays.copyOfRange(heap, 0, size)));
}
// 获取堆的大小
public int getSize() {
return size;
}
// 判断堆是否为空
public boolean isEmpty() {
return size == 0;
}
public static void main(String[] args) {
MaxHeap maxHeap = new MaxHeap(10);
maxHeap.insert(10);
maxHeap.insert(20);
maxHeap.insert(5);
maxHeap.insert(30);
maxHeap.insert(15);
System.out.println("Heap elements: " + maxHeap.printHeap());
System.out.println("Extracted max: " + maxHeap.extractMax());
System.out.println("Heap elements after extraction: " + maxHeap.printHeap());
}
}
哈希冲突的原因
哈希冲突的根本原因是哈希函数的设计不够好,导致计算得到的哈希值不够随机。具体来说,可能是由于哈希函数的设计不够均匀,或者散列函数的碰撞率较高,使得不同的键值计算出了相同的哈希值。
哈希冲突的解决方法
- **链地址法(链表法)**
- 实现原理:当发生哈希冲突时,将冲突的元素存储在一个链表中。每个哈希桶中存储一个链表的头节点,当新的元素哈希到该桶中时,如果发生冲突,则将其添加到链表中。
- 特点:链地址法能够有效地解决哈希冲突问题,且实现相对简单。然而,当冲突较多时,链表的长度会增加,从而影响查找效率。
- 开放地址法
- 实现原理:当发生哈希冲突时,通过探测方式在散列表中寻找下一个可用的空槽来存储冲突的元素。常见的探测方式包括线性探测、二次探测和双重散列等。
- 特点:开放地址法不需要额外的存储空间来存储冲突的元素,但探测过程可能会增加查找和插入操作的时间复杂度。
- 再哈希法
- 实现原理:当发生哈希冲突时,使用另外一个哈希函数再次计算索引位置。如果计算出的索引位置仍然冲突,可以继续尝试使用其他哈希函数。
- 特点:再哈希法通过引入多个哈希函数来分散冲突的元素,但实现相对复杂,且需要额外的存储空间来存储多个哈希函数。
- 建立公共溢出区
- 实现原理:当发生哈希冲突时,将冲突的元素存储在一个公共的溢出区中。这个区域可以是链表、数组等数据结构。在查找元素时,先通过哈希函数计算索引位置,然后从溢出区中查找。
- 特点:建立公共溢出区的方法能够灵活地处理哈希冲突问题,但同样需要额外的存储空间来存储溢出区的元素。