在Java面试中,当涉及到动态扩容数组的知识点,通常会探讨以下几个核心概念和技术细节:
1. 动态扩容机制
- 原理:动态扩容是一种数据结构技术,用于在运行时根据需要自动增加数组的容量。当数组达到其容量极限时,将创建一个更大的数组并将原数组的元素复制到新数组中。
- 触发条件:一般是在添加元素时,如果数组已满(即数组的大小等于其容量),就需要进行扩容。
- 扩容策略:扩容策略可能不同,但最常见的是将数组的大小增加一定比例(例如,增加50%或者翻倍)。翻倍是一种常见的实践,因为它平衡了复制数据的成本和频繁扩容的开销。
2. 时间复杂度分析
- 添加操作:在不需要扩容的情况下,向动态数组添加元素的时间复杂度是O(1),即常数时间复杂度。但在扩容的情况下,由于需要复制原数组到新数组,时间复杂度是O(n),其中n是原数组的大小。
- 平摊时间复杂度:虽然单次扩容操作的时间复杂度是O(n),但是从长期来看,添加操作的平摊时间复杂度仍然是O(1)。这是因为扩容操作不是频繁进行的,而且每次扩容后,能够进行的添加操作数量也随之增加。
3. 空间复杂度
- 随着数组的扩容,空间复杂度也增加。在最坏的情况下,如果每次只增加一个元素并触发扩容,空间复杂度可能会达到O(n)。
4. 实现考虑
- 数据复制:动态扩容时需要复制原数组到新的更大的数组,这个过程会消耗时间和资源。优化数据复制过程是提高动态数组性能的一个关键点。
- 容量初始化:合理设置初始容量可以减少扩容操作的频率,特别是在预知数据规模的情况下。
- 容错性:提供对索引越界的检查,确保数组操作的安全性。
5. 应用场景
- 动态数组在需要经常添加元素而事先又不知道需要存储多少元素的场景下非常有用,如ArrayList在Java集合框架中的应用。
掌握这些关键概念和技术细节不仅能帮助你在面试中回答有关动态扩容的问题,也能让你更深入地理解Java集合框架中的ArrayList等数据结构的内部工作机制。
面试中关于Java动态扩容的题目通常旨在评估你对数据结构、算法以及Java集合框架的理解。下面是三道相关的面试题目,每个题目都附带了源代码示例。
题目1: 实现一个支持动态扩容的栈
题目描述:
实现一个栈,它支持基本的push
和pop
操作,并且可以根据元素的数量动态地扩展和收缩内部存储。
源码:
public class DynamicStack<T> {
private Object[] elements;
private int size;
private static final int DEFAULT_CAPACITY = 10;
public DynamicStack() {
elements = new Object[DEFAULT_CAPACITY];
size = 0;
}
public void push(T element) {
ensureCapacity();
elements[size++] = element;
}
@SuppressWarnings("unchecked")
public T pop() {
if (size == 0) {
throw new EmptyStackException();
}
T element = (T) elements[--size];
elements[size] = null; // Eliminate obsolete reference
return element;
}
private void ensureCapacity() {
if (size == elements.length) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
题目2: 实现一个动态扩容的循环队列
题目描述:
实现一个循环队列,它支持enqueue
(入队)和dequeue
(出队)操作,并且能够在达到容量限制时自动扩容。
源码:
public class DynamicCircularQueue<T> {
private T[] queue;
private int front;
private int rear;
private int size;
private static final int DEFAULT_CAPACITY = 10;
@SuppressWarnings("unchecked")
public DynamicCircularQueue() {
queue = (T[]) new Object[DEFAULT_CAPACITY];
front = 0;
rear = 0;
size = 0;
}
public boolean enqueue(T data) {
ensureCapacity();
queue[rear] = data;
rear = (rear + 1) % queue.length;
size++;
return true;
}
public T dequeue() {
if (size == 0) {
throw new NoSuchElementException("Queue is empty");
}
T data = queue[front];
queue[front] = null; // help GC
front = (front + 1) % queue.length;
size--;
return data;
}
@SuppressWarnings("unchecked")
private void ensureCapacity() {
if (size == queue.length) {
T[] newQueue = (T[]) new Object[queue.length * 2];
for (int i = 0; i < size; i++) {
newQueue[i] = queue[(front + i) % queue.length];
}
front = 0;
rear = size;
queue = newQueue;
}
}
}
题目3: 实现一个自动扩容的哈希表
题目描述:
设计并实现一个简单的哈希表,它支持put
和get
操作。当元素数量超过容量的负载因子时,哈希表应自动扩容。
源码:
public class SimpleHashTable<K, V> {
private static class Entry<K, V> {
final K key;
V value;
Entry<K, V> next;
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
private Entry<K, V>[] table;
private int size;
private static final int DEFAULT_CAPACITY = 16;
private static final float LOAD_FACTOR = 0.75f;
@SuppressWarnings("unchecked")
public SimpleHashTable() {
table = (Entry<K, V>[]) new Entry[DEFAULT_CAPACITY];
size = 0;
}
public void put(K key, V value) {
if (size >= LOAD_FACTOR * table.length) {
resize();
}
int index = indexFor(key.hashCode(), table.length);
for (Entry<K, V> e = table[index]; e != null; e = e.next) {
if (e.key.equals(key)) {
e.value = value;
return;
}
}
addEntry(key, value, index);
}
public V get(K key) {
int index = indexFor(key.hashCode(), table.length);
for (Entry<K, V> e = table[index]; e != null; e = e.next) {
if (e.key.equals(key)) {
return e.value;
}
}
return null;
}
private void resize() {
Entry<K, V>[] oldTable = table;
int newCapacity = oldTable.length * 2;
@SuppressWarnings("unchecked")
Entry<K, V>[] newTable = (Entry<K, V>[]) new Entry[newCapacity];
transfer(newTable);
table = newTable;
}
private void transfer(Entry<K, V>[] newTable) {
for (Entry<K, V> e : table) {
while (e != null) {
Entry<K, V> next = e.next;
int index = indexFor(e.key.hashCode(), newTable.length);
e.next = newTable[index];
newTable[index] = e;
e = next;
}
}
}
private void addEntry(K key, V value, int bucketIndex) {
Entry<K, V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(key, value, e);
size++;
}
private int indexFor(int h, int length) {
return h & (length-1);
}
}
这些题目覆盖了动态数据结构的基础知识,并展示了如何在实际编程中应用这些概念。在面试中,理解和能够实现这些基本数据结构的动态扩容机制是非常重要的。