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

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

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

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

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类支持创建公平锁和非公平锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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标准库中没有直接提供自旋锁的实现,但我们可以自己实现一个简单的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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类提供了共享锁(读锁)和独占锁(写锁)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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)

特点‌:

  • 固定大小的数据结构,用于存储相同类型的元素。
  • 访问速度快,但插入和删除操作效率较低(需要移动元素)。

用途‌:

  • 存储固定大小的集合。
  • 用于实现静态数据结构,如栈和队列的底层实现。
1
2
3
4
int[] array = new int[5];
array[0] = 1;
array[1] = 2;
// ...

2. 链表(LinkedList)

特点‌:

  • 由一系列节点组成,每个节点包含数据和指向下一个节点的引用。
  • 支持快速的插入和删除操作,但访问速度较慢(需要从头节点开始遍历)。

用途‌:

  • 实现动态数据结构,如动态数组、栈、队列和哈希表。
  • 用于表示具有顺序关系的元素集合。
1
2
3
4
LinkedList<Integer> list = new LinkedList<>();
list.add(1);
list.add(2);
// ...

3. 栈(Stack)

特点‌:

  • 后进先出(LIFO)的数据结构。
  • 支持快速的插入(压栈)和删除(弹栈)操作。

用途‌:

  • 实现表达式求值、语法解析和递归调用等功能。
  • 用于保存临时数据。

示例代码‌(使用Java内置的Stack类):

1
2
3
4
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
Integer top = stack.pop();

4. 队列(Queue)

特点‌:

  • 先进先出(FIFO)的数据结构。
  • 支持快速的插入(入队)和删除(出队)操作。

用途‌:

  • 实现任务调度、消息传递和广度优先搜索等功能。
  • 用于保存待处理的任务或事件。

示例代码‌(使用Java内置的LinkedList作为队列):

1
2
3
4
LinkedList<Integer> queue = new LinkedList<>();
queue.add(1);
queue.add(2);
Integer head = queue.poll();

非线性结构

5. 树(Tree)

特点‌:

  • 具有层次结构的数据结构,每个节点可以包含子节点。
  • 支持快速的查找、插入和删除操作(取决于树的类型,如二叉搜索树、平衡树等)。

用途‌:

  • 实现文件系统、数据库索引和表达式树等功能。
  • 用于表示具有层次关系的数据集合。

示例代码‌(以二叉搜索树为例,使用自定义类实现):

1
2
3
4
5
6
7
8
class TreeNode {
int val;
TreeNode left;
TreeNode right;
// 构造函数、getter和setter方法
}

// 插入、查找和删除操作的实现...

6.哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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)

特点‌:

  • 由节点和边组成的数据结构,用于表示实体之间的关系。
  • 支持复杂的遍历和搜索操作(如深度优先搜索、广度优先搜索等)。

用途‌:

  • 实现社交网络、地图和电路图等功能。
  • 用于表示具有复杂关系的数据集合。

示例代码‌(使用自定义类实现):

1
2
3
4
5
6
class Graph {
private Map<Integer, List<Integer>> adjList;
// 构造函数、添加边和遍历方法
}

// 遍历、搜索和路径查找操作的实现...

8.堆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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. 建立公共溢出区
    • 实现原理‌:当发生哈希冲突时,将冲突的元素存储在一个公共的溢出区中。这个区域可以是链表、数组等数据结构。在查找元素时,先通过哈希函数计算索引位置,然后从溢出区中查找。
    • 特点‌:建立公共溢出区的方法能够灵活地处理哈希冲突问题,但同样需要额外的存储空间来存储溢出区的元素。

数据库

达梦数据库和oracle有什么区别

达梦数据库(DM数据库)和Oracle数据库是两种不同的数据库管理系统,它们在多个方面存在显著的区别。以下是对两者主要差异的详细分析:

一、体系结构

  • Oracle数据库采用多进程架构,包括PMON、SMON、DBWR、LGWR、CKPT、ARCn等多个进程。
  • 达梦数据库则采用单进程多线程架构,主进程是dmserver,包含checkpoint线程、I/O线程、监听线程、日志写线程等多个线程。

二、启动与状态转换

  • Oracle数据库可以从关闭状态经过nomount、mount到open状态。
  • 达梦数据库的状态转换相对灵活,除了mount和suspend之间不能直接转化外,其余状态可以任意转换。

三、安全性

  • Oracle数据库提供了安全控制和审计功能,支持细粒度的权限管理,并通过Profile机制控制用户资源使用。
  • 达梦数据库则采用参数PWD_POLICY管理密码策略,且在国密算法支持、国产化适配等方面有优势,符合国内安全合规要求。

四、表空间管理

  • 在表空间管理上,两者都允许一个表空间包含多个数据文件,且一个数据文件只能属于一个表空间。但Oracle能对表空间或者数据文件进行offline操作,而达梦仅能对表空间进行offline。
  • 在数据文件迁移方面,Oracle在12c以前需要手动在操作系统层进行迁移,并在数据库层更改路径参数;而达梦通过一个命令即可在操作系统层完成整体迁移。

五、用户与模式

  • Oracle数据库的用户和模式(schema)是一一对应关系。
  • 达梦数据库则允许一个用户下拥有多个模式。

六、备份与恢复

  • Oracle数据库的RMAN工具支持热备份,允许在数据库运行时进行备份。
  • 达梦数据库的RMAN可能仅支持冷备份,需要数据库处于非活动状态。但请注意,达梦数据库在进行热备时需要额外的配置或操作。

七、表与索引

  • Oracle数据库默认创建的是堆表。
  • 达梦数据库默认创建的是索引组织表,这种表有且仅有一个聚簇索引键,表数据按照聚簇索引键排序。

八、SQL语法与管理工具

  • 两者在SQL语法上存在共通之处,但也存在一些差异。例如,在执行.sql脚本时,Oracle使用“@”符号,而达梦数据库使用“start”或者“`”符号。
  • 管理工具方面,Oracle使用SQL*Plus作为命令行工具,而达梦数据库使用DISQL。

九、其他功能特性

  • Oracle数据库以其出色的性能优化技术和大规模部署能力著称,提供了诸如并行查询、分区表、自动工作负载管理等功能。
  • 达梦数据库则在对标Oracle的功能方面进行了优化和适配,如提供了DataWatch、DSC、DMHS等架构来对应Oracle的ADG、RAC、OGG等架构。

十、兼容性设置

  • 达梦数据库提供了与Oracle数据库的兼容性设置,如通过dm.ini配置文件中的相关参数来设置对Oracle日期格式的兼容性、PL/SQL异常处理中SQLCODE的错误码值一致性等。
  • 这些兼容性设置有助于简化从Oracle迁移到达梦数据库的过程,降低迁移成本和风险。

综上所述,达梦数据库和Oracle数据库在体系结构、启动与状态转换、安全性、表空间管理、用户与模式、备份与恢复、表与索引、SQL语法与管理工具以及其他功能特性等方面都存在显著的差异。用户在选择数据库时,应根据自身的业务需求和系统环境进行综合考虑。


与人工智能协作,java基础篇,2025面试题过关斩将
http://example.com/2025/02/18/与人工智能协作,java基础篇,2025面试题过关斩将/
作者
zpqwe
发布于
2025年2月18日
许可协议