与人工智能协作,java基础篇,2025面试题过关斩将


title: 与人工智能协作,java基础篇,2025面试题过关斩将
date: 2025-02-18 11:38:53
tags:

  • java锁机制
  • java集合
  • ConcurrentHashMap与CAS

给每一个找工作和非找工作的人观看。

众所周知,工作中呢,基础知识就会忘得很快,因为工作中很少会留意到这种基础知识,或者是说工作中具体功能用的比较多,但是不会关心是由什么实现的。

毕竟上班已经很忙了,需求在催、业务在催、自己空闲的时候也很难去关注这些基础知识。

OK,回到今天的问题,与AI协作巩固基础知识。

CAP理论 (一致性、可用性、分区容错性)

这时一个架构方面的问题,根据不同的业务场景,你要去怎么设计你的程序代码?

一、CAP理论的三个特性

  1. ‌**一致性(Consistency)**‌:
    • 在分布式系统中,若多节点同时操作同一数据,所有节点最终应得到一致的结果。
    • 强一致性指所有节点数据状态同时相同,但在分布式系统中实现强一致性较为困难,因此有时会采用最终一致性作为折中方案。
  2. ‌**可用性(Availability)**‌:
    • 分布式系统需确保无论何种故障,用户请求都能在有限时间内得到响应,系统不能拒绝服务或长时间无响应。
  3. ‌**分区容错性(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构造函数来创建。以下是线程池的主要参数及其解释:

  1. ‌**corePoolSize(核心线程数)**‌:
    • 线程池中的核心线程数,即使线程处于空闲状态,线程池也会保留核心线程数。只有当工作队列满时,才会创建超出核心线程数的线程。
  2. ‌**maximumPoolSize(最大线程数)**‌:
    • 线程池中允许的最大线程数。当工作队列满且已创建的线程数小于最大线程数时,线程池会尝试创建新的线程来处理任务。
  3. ‌**keepAliveTime(线程空闲时间)**‌:
    • 当线程数超过核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。
  4. ‌**unit(时间单位)**‌:
    • keepAliveTime参数的时间单位,常用的有TimeUnit.MILLISECONDSTimeUnit.SECONDS等。
  5. ‌**workQueue(工作队列)**‌:
    • 用于保存等待执行的任务的阻塞队列。常见的队列类型有LinkedBlockingQueue(基于链表的阻塞队列)、ArrayBlockingQueue(基于数组的阻塞队列)、SynchronousQueue(一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作)等。
  6. ‌**threadFactory(线程工厂)**‌:
    • 用于创建新线程的工厂。如果没有指定,则使用Executors.defaultThreadFactory()来创建线程。
  7. ‌**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());
    }

}

哈希冲突的原因

哈希冲突的根本原因是哈希函数的设计不够好,导致计算得到的哈希值不够随机。具体来说,可能是由于哈希函数的设计不够均匀,或者散列函数的碰撞率较高,使得不同的键值计算出了相同的哈希值。

哈希冲突的解决方法

  1. ‌**链地址法(链表法)**‌
    • 实现原理‌:当发生哈希冲突时,将冲突的元素存储在一个链表中。每个哈希桶中存储一个链表的头节点,当新的元素哈希到该桶中时,如果发生冲突,则将其添加到链表中。
    • 特点‌:链地址法能够有效地解决哈希冲突问题,且实现相对简单。然而,当冲突较多时,链表的长度会增加,从而影响查找效率。
  2. 开放地址法
    • 实现原理‌:当发生哈希冲突时,通过探测方式在散列表中寻找下一个可用的空槽来存储冲突的元素。常见的探测方式包括线性探测、二次探测和双重散列等。
    • 特点‌:开放地址法不需要额外的存储空间来存储冲突的元素,但探测过程可能会增加查找和插入操作的时间复杂度。
  3. 再哈希法
    • 实现原理‌:当发生哈希冲突时,使用另外一个哈希函数再次计算索引位置。如果计算出的索引位置仍然冲突,可以继续尝试使用其他哈希函数。
    • 特点‌:再哈希法通过引入多个哈希函数来分散冲突的元素,但实现相对复杂,且需要额外的存储空间来存储多个哈希函数。
  4. 建立公共溢出区
    • 实现原理‌:当发生哈希冲突时,将冲突的元素存储在一个公共的溢出区中。这个区域可以是链表、数组等数据结构。在查找元素时,先通过哈希函数计算索引位置,然后从溢出区中查找。
    • 特点‌:建立公共溢出区的方法能够灵活地处理哈希冲突问题,但同样需要额外的存储空间来存储溢出区的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

one 大白(●—●)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值