1、概述:
在java中,集合类和集合接口都是在java.util包下。它们提供了一种更灵活、动态底管理数据的方式,相对于定长的数组,集合可以根据动态扩展和缩减大小。这样可以更有效地利用内存空间,避免了固定长度数据的限制。
集合框架中的各种实现类和接口可以根据需求选择,从而更好地满足不同场景下的数据存储和操作。
2、集合、数组的区别:
1、动态性:集合具有动态性,可以根据需要动态增加和减少元素个数,而数组的长度是固定的,一旦创建就无法修改。
2、类型:数组可以是基本数据类型的数组,也可以是对象数组,但是将建的时必须指定数组的类型和长度。而集合可以存储任意类型的对象,且长度可以变化。
3、内存管理:数组时一个来纳许的内存快,一旦创建,长度是固定的,可能会导致内存的浪费和不足,而集合可以根据需要动态调整。
4.、数组可以包含任何类型的元素,但在编译时无法检测元素的类型,容易导致类型转换错误,而集合框架支持泛型,在编译时强制检测元素的类型,减少类型错误的可能性。
3、Collection集合(单例集合的顶层接口,不直接使用)
3.1、 Collection集合的的常用方法
方法名 | 说明 |
boolean add(E e) | 添加元素到集合的末尾(追加) |
boolean remove(Object o) | 删除指定的元素,成功则返回true(底层调用equles) |
void clear() | 清空集合 |
boolean contains(Object o) | 判断元素在集合中是否存在,存在则返回true(底层调用equles) |
boolean isEmpty() | 判断集合是否为空,空则返回true |
int size() | 返回集合中元素个数 |
import java.util.ArrayList;
import java.util.Collection;
/**
* 演示集合的基本操作
*/
public class CollectionDemo {
public static void main(String[] args) {
// 使用父类的引用指向子类的对象,形成多态
Collection<String> con = new ArrayList<>();
// 向集合中追加元素
con.add("东邪");
con.add("西毒");
con.add("南帝");
con.add("北丐");
con.add("中神通");
// 删除元素,通过元素名称删除
System.out.println("删除元素:" + con.remove("西毒"));
// 判断集合中是否包含指定参数元素
System.out.println("是否包含'西毒':" + con.contains("西毒")); // false
System.out.println("是否包含'东邪':" + con.contains("东邪")); // true
// 获取集合中元素个数
System.out.println("集合大小:" + con.size());
// 判断集合是否为空
System.out.println("集合是否为空:" + con.isEmpty()); // false
// 清空集合
con.clear();
// 再次判断集合是否为空
System.out.println("清空后集合是否为空:" + con.isEmpty()); // true
System.out.println("清空后的集合:" + con); // 打印空集合
}
}
3.2、Collection集合的遍历
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class MyCollectionTraversal {
public static void main(String[] args) {
// 使用ArrayList实现Collection接口,形成多态
Collection<String> myCollection = new ArrayList<>();
// 向集合中添加元素
myCollection.add("苹果");
myCollection.add("香蕉");
myCollection.add("橙子");
myCollection.add("葡萄");
// 使用增强型for循环遍历集合
System.out.println("使用增强型for循环遍历集合:");
for (String fruit : myCollection) {
System.out.print(fruit + "\t");
}
System.out.println(); // 换行
// 使用迭代器遍历集合
System.out.println("使用迭代器遍历集合:");
Iterator<String> iterator = myCollection.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + "\t");
}
System.out.println(); // 换行
}
}
3.3、 ArrayList构造和添加方法
方法名 | 说明 |
public ArrayList<E>() | 创建一个空集合 |
public boolean add(E e) | 将指定的参数元素追加到集合的末尾 |
public void add(int index ,E e) | 在集合的指定位置添加指定的元素(插入元素) |
public void addAll(E object) | 用于将指定集合中所有元素添加到当前集合中 |
/**
* 演示ArrayList的构造和添加方法
*/
public class MyArrayList {
public static void main(String[] args) {
// 创建空ArrayList
ArrayList<String> myList = new ArrayList<>(); // 使用String作为泛型
// 采用默认追加的方式添加元素
System.out.println("添加 '成龙' 是否成功:" + myList.add("成龙"));
System.out.println("添加 '李连杰' 是否成功:" + myList.add("李连杰"));
System.out.println("添加 '周星驰' 是否成功:" + myList.add("周星驰"));
System.out.println("添加 '梁朝伟' 是否成功:" + myList.add("梁朝伟"));
// 插入的方式添加元素
myList.add(1, "张曼玉"); // 指定位置插入元素,索引位置之后的元素会自动向后移动
// 创建新的ArrayList
ArrayList<String> newList = new ArrayList<>();
newList.add("巩俐");
newList.add("章子怡");
newList.add("刘德华");
newList.add("周杰伦");
// 查看集合中的元素
System.out.println("原集合内部元素:" + myList);
System.out.println("新集合内部元素:" + newList);
// 将新集合全部元素添加到原集合中
myList.addAll(newList);
System.out.println("合并后的集合内部元素:" + myList);
}
}
3.4、ArrayList实现原理
- 内部使用一个 Object 数组来存储元素。
- 当数组空间不足以容纳新的元素时,会自动扩容。扩容时通常会创建一个新的更大的数组,并将原数组中的元素拷贝到新数组中。
- 当数组中元素数量减少到一定程度时,会自动缩减数组的大小,以节省内存空间。
- ArrayList 支持随机访问,通过索引来直接访问元素,因为底层是基于数组实现的
public class MyArrayList<E> {
private static final int DEFAULT_CAPACITY = 10; // 默认初始容量
private Object[] elements; // 存储元素的数组
private int size; // 元素数量
public MyArrayList() {
elements = new Object[DEFAULT_CAPACITY]; // 初始化数组
size = 0; // 初始元素数量为0
}
// 添加元素
public void add(E element) {
ensureCapacity(); // 确保容量足够
elements[size++] = element; // 将元素放入数组中,并增加元素数量
}
// 获取指定索引的元素
public E get(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); // 索引越界检查
return (E) elements[index]; // 返回指定索引处的元素
}
// 确保容量足够
private void ensureCapacity() {
if (size == elements.length) { // 如果当前元素数量等于数组长度
int newCapacity = elements.length * 2; // 扩容为原来的两倍
Object[] newElements = new Object[newCapacity]; // 创建新数组
System.arraycopy(elements, 0, newElements, 0, size); // 将原数组元素复制到新数组
elements = newElements; // 更新数组引用
}
}
// 返回列表大小
public int size() {
return size;
}
}
4、hashMap
双列集合:用来存储键值对的集合。
interface Map<K,V>
: K(key)键 ,V(value)值- 将键映射到值的对象,不能出现重复的键,每个键最多可以映射到一个值
1、Map和Collection没有继承关系。 2、Map集合以key和value的方式存储数据:键值对 key和value都是引用数据类型。 key和value都是存储对象的内存地址。 key起到主导的地位,value是key的一个附属品。
4.1Map的基本方法
方法名 | 说明 |
V put(K key,V value) | 设置键值对 |
V remove(Object key) | 删除元素 |
void clear() | 清空集合 |
boolean containsKey(Object key) | 判断键是否存在,存在则返回true |
boolean containsValue(Object value) | 判断值是否存在,存在则返回true |
boolean isEmpty() | 判断集合是否为空 |
int size() | 获取集合元素个数 |
import java.util.HashMap;
import java.util.Map;
public class SimpleMapExample {
public static void main(String[] args) {
// 创建一个HashMap实例
Map<String, Integer> map = new HashMap<>();
// 设置键值对
map.put("apple", 10);
map.put("banana", 20);
map.put("orange", 15);
// 删除元素
map.remove("banana");
// 清空集合
map.clear();
// 判断键是否存在
boolean containsKey = map.containsKey("apple");
System.out.println("Contains key 'apple': " + containsKey);
// 判断值是否存在
boolean containsValue = map.containsValue(20);
System.out.println("Contains value 20: " + containsValue);
// 判断集合是否为空
boolean isEmpty = map.isEmpty();
System.out.println("Is map empty: " + isEmpty);
// 获取集合大小
int size = map.size();
System.out.println("Map size: " + size);
}
}
4.2HashMap原理
HashMap 在 JDK1.8 之前的实现方式 数组+链表,
但是在 JDK1.8 后对 HashMap 进行了底层优化,改为了由 数组+链表或者数值+红黑树
实现,主要的目的是提高查找效率
1. Jdk8 数组+链表或者数组+红黑树实现,当链表中的元素超过了 8 个以后, 会
将链表转换为红黑树,当红黑树节点 小于 等于 6 时又会退化为链表。
2. 当 new HashMap():底层没有创建数组,首次调用 put()方法示时,底层创建长度
为 16 的数组,jdk8 底层的数组是:Node[],而非 Entry[],用数组容量大小乘以加载因子得
到一个值,一旦数组中存储的元素个数超过该值就会调用 rehash 方法将数组容量增加到原
来的两倍,专业术语叫做扩容,在做扩容的时候会生成一个新的数组,原来的所有数据需要
重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能.
默认的负载因子大小为 0.75,数组大小为 16。也就是说,默认情况下,那么当 HashMap
中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为 2*16=32,即扩大一倍。
3. 在我们 Java 中任何对象都有 hashcode,hash 算法就是通过 hashcode 与自己进
行向右位移 16 的异或运算。这样做是为了计算出来的 hash 值足够随机,足够分散,还有
产生的数组下标足够随机,
map.put(k,v)实现原理
(1)首先将 k,v 封装到 Node 对象当中(节点)。
(2)先调用 k 的 hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(3)下标位置上如果没有任何元素,就把 Node 添加到这个位置上。如果说下标对应的位
置上有链表。此时,就会拿着 k 和链表上每个节点的 k 进行 equal。如果所有的 equals 方
法返回都是 false,那么这个新的节点将被添加到链表的末尾。如其中有一个 equals 返回了
true,那么这个节点的 value 将会被覆盖。
map.get(k)实现原理
(1)、先调用 k 的 hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(2)、在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返
14回 null。如果这个位置上有单向链表,那么它就会拿着参数 K 和单向链表上的每一个节点
的 K 进行 equals,如果所有 equals 方法都返回 false,则 get 方法返回 null。如果其中一
个节点的 K 和参数 K 进行 equals 返回 true,那么此时该节点的 value 就是我们要找的 value
了,get 方法最终返回这个要找的 value。
写在最后:
本篇文章主要是博主工作中个人的一下理解,记录工作中常用集合的使用方式、优点和用途,希望大家可以根据本文有一个了解。