集合
Java 集合类是一种容器,可容纳其他数据,用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组(数据对)
整体感受下,
按照存储数据格式的不同(单列还是双列),简单的可以分为两大类Collection和Map
Collection
集合就像容器装东西,往进添加或者往出拿,对应的就是添加或者删除,集合类就为这些功能提供了对应的方法。
Collection是List、Set 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现
Collection中定义的常用抽象方法:
- 添加
add(Object obj)
addAll(Collection coll) - 获取有效元素的个数
int size() - 清空集合
void clear() - 是否是空集合
boolean isEmpty() - 是否包含某个元素
boolean contains(Object obj) - 删除
boolean remove 只会删除找到的第一个元素 - 集合是否相等
boolean equals(Object obj) - 获取集合对象的哈希值
hashCode() - 遍历
iterator():返回迭代器对象,用于集合遍历
List
- 由于数组存储数据有局限性,用来替代数组
- 元素有序、且可重复,每个对应一个整数型的序号,可根据序号存取容器中的元素
- 主要实现类:ArrayList、LinkedList和Vector
常用方法:增删改查
- void add(int index, Object ele): 在index位置插入ele元素
- Object remove(int index): 移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele): 设置指定index位置的元素为ele
- 2.Object get(int index): 获取指定index位置的元素ele
ArrayList
List的主要实现类,本质上,ArrayList对象引用指向一个‘’变长数组‘’
实现对ArrayList的增删改查:
public static void main(String[] args) {
// 注意泛型,和迭代器或遍历语句前后保持一致
List<String> list = new ArrayList();
// 添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("z");
// 输出列表形式
System.out.println("toString形式:");
System.out.println(list);
// for each
System.out.println("增强for循环:");
for (String val : list) {
System.out.println(val);
}
// for循环
// 注意:容器的大小是size(),不是length()
System.out.println("for循环: ");
for (int i = 0; i < list.size(); i++) {
System.out.println("waiting...");
}
// 删除元素
list.remove("b");
// 下标从0开始
list.remove(1);
// 修改元素
list.set(0, "aka hh");
// 使用迭代器循环
// 容器自带iterator()方法,List实现了Collection接口,可以直接 . 出来
System.out.println("iterator迭代器: ");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
运行结果:
ArrayList扩容机制:
- java8以后,ArrayList类似懒汉式,一开始,创建一个长度为0的数组,当添加第一个元素时再创建一个容量为10的数组,10为其默认初始容量
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
- 扩容操作,主要靠下面语句来执行,
扩容默认为原数组扩容1.5倍(1+0.5),向右移位1
int newCapacity = oldCapacity + (oldCapacity >> 1);
按照默认值扩容后,进行判断,看新数组的长度是否满足要求,如果满足要求,将原数组的数据拷贝到新的数组,完成扩容操作。
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
实现扩容的本质:
开辟更大的数组空间,将原数组中的数据复制到新的数组中
LinkedList
底层采用链表的方式实现,由于链表地天然属性,对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
LinkedList<String> list = new LinkedList<>();
将上面的ArrayList 创建为LinkedList,其余语句保持不变,同样可得到下面运行结果:
Vector
Vector是线程安全的
再使用List容器时,最好把ArrayList作为默认选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用
List小结
三者的异同:
-
Vector线程安全,其余两种非线程安全
-
ArrayList和LinkedList:
ArrayList实现了基于动态数组的数据结构,对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针
LinkedList基于链表的数据结构,对于**新增和删除操作*,LinkedList比较占优势,因为ArrayList要移动数据 -
ArrayList和Vector
二者几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此,开销就比ArrayList要大,访问慢。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍
Set
Set的概念类似于箱子,东西可以扔入的比较“随意”,没有一个固定的位置,因此,无法通过索引查找,这就要求存入的元素不能重复。
Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
HashSet
Set接口的主要实现类,没有重复元素,向HashSet中添加数据时,HashSet 根据对象的 hashcode 值来存储该对象,同时也能够根据 hashcode 值来快速定位到该元素。表面无序,实则有序,其顺序来自hashcode
特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
判断两个元素相等: hashCode() 相等 && equals() 相等。
HashSet实现及使用方式:
HashSet<String> list = new HashSet<>();
// 添加元素
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
// 输出列表形式
System.out.println("toString形式:");
System.out.println(list);
// for each
System.out.println("增强for循环:");
for (String val : list) {
System.out.println(val);
}
// 删除元素
list.remove("b");
// 使用迭代器循环
System.out.println("iterator迭代器: ");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
由于Set中的元素没有下标的概念,因此,不能使用下标访问元素,只能使用上述iterator或for each 来访问集合元素
相应的,删除元素的方式也只能根据内容来删除特定的元素
向HashSet中添加元素步骤:
- HashSet 会调用该对象的 hashCode() 方法得到 hashCode 值,然后根据 hashCode 值,通过散列函数决定该对象在 HashSet 底层数组中的存储位置。(散列函数会与底层数组的长度相计算得到在数组中的下标)
- 如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
hashcode()值相等,equals()结果也为true的话,则两个元素相等 - 如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
HashSet扩容方式:
* @param c the collection whose elements are to be placed into this set
* @throws NullPointerException if the specified collection is null
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
LinkedHashSet
是HashSet的子类,根据元素的hashCode值确定元素存储位置,且使用双向链表维护存储的顺序,同样地,不允许元素重复
TreeSet
- 底层使用红黑树存储数据
- 确保数据处于排序状态
ps:树形结构属于数据结构中的内容,目前了解的不深,后续再做补充