数据结构
1. 数组(Array)
-
定义:存储固定大小同类型元素的数据结构。
-
关键源码:
int[] intArray = new int[10]; // 声明一个长度为10的整型数组,分配内存空间 -
应用场景:存储固定数量同类型数据,如一周的天气温度。
2. ArrayList
-
定义:基于动态数组实现的List接口实现类。
-
关键源码:
public boolean add(E e) { ensureCapacityInternal(size + 1); // 确保数组容量足够,内部方法 elementData[size++] = e; // 将元素添加到数组末尾,并更新size } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果是默认空数组 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 设置最小容量 } ensureExplicitCapacity(minCapacity); // 显式确保容量 } private void ensureExplicitCapacity(int minCapacity) { modCount++; // 修改计数器,用于迭代器的快速失败检测 // 溢出保护代码 if (minCapacity - elementData.length > 0) grow(minCapacity); // 扩容 } private void grow(int minCapacity) { // 溢出保护代码 int oldCapacity = elementData.length; // 旧容量 int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量仍小于最小容量,则设置为最小容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 处理非常大的容量情况 // minCapacity通常接近size,所以这是个胜利的操作 elementData = Arrays.copyOf(elementData, newCapacity); // 复制数组到新容量的数组 } -
应用场景:存储数量不确定的数据,如动态的新闻列表。
3. LinkedList
-
定义:基于双向链表实现的List接口实现类。
-
关键源码:
public void add(E e) { linkLast(e); // 将元素添加到链表末尾 } void linkLast(E e) { final Node<E> l = last; // 获取当前最后一个节点 final Node<E> newNode = new Node<>(l, e, null); // 创建新节点,前驱为最后一个节点,后继为null last = newNode; // 更新最后一个节点为新节点 if (l == null) first = newNode; // 如果原来链表为空,则新节点也是第一个节点 else l.next = newNode; // 否则,将原来最后一个节点的后继指向新节点 size++; // 更新链表大小 modCount++; // 修改计数器 } -
应用场景:频繁插入删除数据的场景,如音乐播放列表。
4. HashMap
-
定义:基于哈希表实现的Map接口实现类。
-
关键源码:
HashMap是一个链表数组结构。它首先会创建一个长度为 16 的链表数组。插入键值对时,会通过键的哈希值与数组长度进行位与运算来计算插入数组的索引(Index)。如果索引位置的链表不为空,会循环遍历链表,检查是否存在相同的键。如果存在相同键,则更新该键对应的值;如果不存在,则创建一个新的节点并将其添加到链表的末尾。当链表长度超过 8 时,该链表会被转换为红黑树,以优化性能。此外,当哈希表中的元素数量超过一定阈值时,数组会进行扩容。这个阈值通常是数组长度乘以负载因子(默认负载因子为 0.75)。例如,如果数组长度为 16,负载因子为 0.75,那么当哈希表中的元素数量超过 12(16 × 0.75)时,数组会扩容。扩容时,会创建一个新的哈希表,其长度通常是原表的两倍,然后将原表中的所有键值对重新插入到新表中。
1. 申请一个初始长度为16的数组
在
HashMap中,数组通常用于存储桶(Bucket),每个桶可以是一个链表或红黑树的头节点。Node<K, V>[] table = new Node[DEFAULT_INITIAL_CAPACITY]; // 初始化数组2. 通过
key的hash值计算index计算键的哈希值,并通过位与操作计算保存到数组的Index。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); // 扰动函数,提高哈希值的分布均匀性 } int indexFor(int hash, int length) { return hash & (length - 1); // 计算索引, 位与保证inde不超过length-1 }3. 将
key-value插入到数组中key和value封装成Node,根据计算的索引,将Node插入到对应的桶中。如果桶不为空,遍历链表,检查是否存在相同的键,如果存在,更新该键对应的值;如果不存在,创建一个新的节点并添加到链表的末尾。如果链表长度超过8,链表会被转换成红黑树。
public V put(K key, V value) { if (key == null) { throw new NullPointerException("Key cannot be null"); } int hash = hash(key); int index = indexFor(hash, table.length); // 获取该索引位置的节点 Node<K, V> node = table[index]; // 检查是否已经存在相同的键 if (node != null) { Node<K, V> current = node; while (current != null) { if (current.hash == hash && (current.key == key || key.equals(current.key))) { // 键已存在,更新值 V oldValue = current.value; current.value = value; return oldValue; } current = current.next; } // 键不存在,插入新节点 node = new Node<>(hash, key, value, node); table[index] = node; size++; // 检查链表长度,如果超过8,转换为红黑树 if (node instanceof TreeNode) { ((TreeNode<K, V>) node).treeifyBin(table, index); } else if (size >= TREEIFY_THRESHOLD) { treeifyBin(table, index); } } else { // 桶为空,直接插入新节点 table[index] = new Node<>(hash, key, value, null); size++; } // 检查是否需要扩容 if (size > table.length * loadFactor) { resize(); } return null; }4.数组长度的调整(扩容)
当哈希表中的元素数量超过一定阈值时,数组会进行扩容。这个阈值通常是数组长度乘以负载因子(默认负载因子为0.75)。例如,如果数组长度为16,负载因子为0.75,那么当哈希表中的元素数量超过16 * 0.75 = 12时,数组会进行扩容。创建一个新的更大的哈希表(通常是原表长度的2倍),并将原表中的所有键值对重新插入到新表中。
// 扩容操作 private void resize() { int oldCapacity = table.length; int newCapacity = oldCapacity << 1; // 新容量为原容量的2倍 Node<K, V>[] newTable = new Node[newCapacity]; // 重新哈希并插入所有键值对 for (int i = 0; i < oldCapacity; i++) { Node<K, V> node = table[i]; if (node != null) { table[i] = null; // 清空旧桶 if (node instanceof TreeNode) { // 如果是红黑树,拆分为链表并重新哈希 ((TreeNode<K, V>) node).split(newTable, newCapacity, i); } else { // 如果是链表,直接重新哈希 while (node != null) { Node<K, V> next = node.next; int newIndex = indexFor(node.hash, newCapacity); node.next = newTable[newIndex]; newTable[newIndex] = node; node = next; } } } } table = newTable; // 更新引用 } -
应用场景:根据键快速查找值,如存储用户ID与信息的映射。
5. SparseArray
-
定义:基于整数键的优化数组结构。
-
关键源码:
public void put(int key, E value) { int i = ContainerHelpers.binarySearch(mKeys, mSize, key); // 二分查找键的位置 if (i >= 0) { mValues[i] = value; // 如果找到相同键,则更新值 } else { i = ~i; // 计算插入位置 if (i < mSize && mValues[i] == DELETED) { // 如果当前位置是已删除的节点 mKeys[i] = key; // 更新键 mValues[i] = value; // 更新值 return; } if (mGarbage && mSize >= mKeys.length) { // 如果有垃圾节点且需要扩容 gc(); // 回收垃圾节点 // 因为索引可能变化,所以重新查找 i = ContainerHelpers.binarySearch(mKeys, mSize, key); if (i >= 0) { mValues[i] = value; // 更新值 return; } i = ~i; // 计算插入位置 } mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); // 插入键 mValues = GrowingArrayUtils.insert(mValues, mSize, i, value); // 插入值 mSize++; // 更新大小 } } -
应用场景:存储视图ID与对象的映射。
6. LruCache
-
定义:基于LRU算法的缓存类。
-
关键源码:
public final void put(Key key, Value value) { if (key == null || value == null) { throw new NullPointerException("key == null || value == null"); } synchronized (this) { cache.put(key, value); // 将键值对放入缓存 afterPut(key, value); // 插入后的处理 } } private void afterPut(K key, V value) { int cost = sizeOf(key, value); // 计算新元素的大小,通常为元素的字节数 if (cost < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } pruneToSize(cost); // 根据新元素大小,修剪缓存以满足最大容量限制 } private void pruneToSize(int size) { while (true) { if (size <= maxWeight) { // 如果当前大小不超过最大容量,则退出 break; } Map.Entry<K, V> toEvict = cache.eldest(); // 获取最老的元素(最近最少使用的元素) if (toEvict == null) { // 如果没有元素可移除,则退出 break; } K key = toEvict.getKey(); // 获取最老元素的键 V value = toEvict.getValue(); // 获取最老元素的值 cache.remove(key); // 从缓存中移除最老元素 size -= sizeOf(key, value); // 更新当前大小 entryRemoved(true, key, value, null); // 调用回调方法,通知元素被移除 } } -
应用场景:图片缓存等,通过缓存最近使用的数据,提高数据访问效率。
算法
排序算法
-
冒泡排序
冒泡排序是一种简单的排序算法,通过重复遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行直到没有再需要交换,也就是说该数列已经排序完成。
public static void bubbleSort(int[] arr) { int n = arr.length; boolean swapped; for (int i = 0; i < n - 1; i++) { swapped = false; // 用于优化,如果某一轮没有发生交换,说明数组已经排序完成 for (int j = 0; j < n - 1 - i; j++) { // 比较相邻元素,如果前一个元素大于后一个元素,则交换 if (arr[j] > arr[j + 1]) { // 交换 arr[j] 和 arr[j + 1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; swapped = true; // 发生了交换 } } // 如果某一轮没有发生交换,说明数组已经排序完成 if (!swapped) { break; } } } -
快速排序(Quick Sort)
快速排序是一种高效的排序算法,采用分治法的思想。它选择一个元素作为“基准”(pivot),将数组分为两部分,一部分包含比基准小的元素,另一部分包含比基准大的元素,然后递归地对这两部分进行快速排序。
public static void quickSort(int[] arr, int low, int high) { if (low < high) { // pi 是分区索引,arr[pi] 现在位于正确位置 int pi = partition(arr, low, high); // 递归排序分区的两部分 quickSort(arr, low, pi - 1); // 排序左半部分 quickSort(arr, pi + 1, high); // 排序右半部分 } } private static int partition(int[] arr, int low, int high) { int pivot = arr[high]; // 选择最后一个元素作为基准 int i = (low - 1); // 较小元素的索引 for (int j = low; j < high; j++) { // 如果当前元素小于或等于基准 if (arr[j] <= pivot) { i++; // 交换 arr[i] 和 arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 交换 arr[i+1] 和 arr[high] (或基准) int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; // 返回分区索引 }
搜索算法
-
二分查找
二分查找是一种在有序数组中查找特定元素的高效算法。它通过将数组分为两半,比较中间元素与目标值,根据比较结果决定在左半部分还是右半部分继续查找,直到找到目标值或搜索范围为空。
public static int binarySearch(int[] arr, int target) { int low = 0; int high = arr.length - 1; while (low <= high) { int mid = low + (high - low) / 2; // 防止 (low + high) 溢出 // 检查中间元素是否是目标值 if (arr[mid] == target) { return mid; // 找到目标值,返回索引 } // 如果目标值大于中间元素,搜索右半部分 if (target > arr[mid]) { low = mid + 1; } else { // 如果目标值小于中间元素,搜索左半部分 high = mid - 1; } } return -1; // 目标值不存在,返回 -1 }
1799

被折叠的 条评论
为什么被折叠?



