文章目录
- 1、数组与集合的区别?
- 2、List、Set、Map的区别?
- 3、ArrayList、LinkedList、Vector的区别?
- 4、Java集合的快速失败机制【fail-fast】
- 5、List接口常用方法
- 6、List的三种遍历方式
1、数组与集合的区别?
在Java中,数组和集合都是容器,都可以用来存储多个数据,它们在使用的时候有一些区别:
1)类型:在Java中数组是最基本的数据结构,在内存中是线性存储的,可以用来存储基本数据类型(如int,double等)和引用类型(如对象)。集合(Collection),在Java中是一种接口,它是一种存储对象的容器,只能存储引用类型的数据。
// 数组
int[] arr1 = new int[5]; //int型数组
String[] arr2 = new String[5]; //对象数组
// 集合对象
List<String> list = ArrayList<>(); //存储Stirng类型数据,如果不指定泛型,它默认类型为object
2)长度:数组在初始化时需要指定长度(大小),一旦定义,其长度无法改变。而集合的长度是可变的,提供了add()、remove()等方法操作数据;(动态扩容、缩容)
int[] arr = new int[5]; //数组长度为5
3)操作数据:数组可以通过索引直接访问和修改元素,具有较高的访问效率,时间复杂度O(1)。而集合类通常提供了一系列方法来操作元素,例如添加、删除、查找等,对元素的访问需要通过方法调用。
// 1.数组通过索引方式操作数据
int[] arr = new int[5]; //int类似数组,默认值为0
// 添加数据
arr[1]=18;
// 覆盖数据
arr[1]=20;
// 访问数据
System.out.println(arr[1]);//20
// 获取数组长度
System.out.println(arr.length);
// 2.集合通过方法调用方式操作数据
List<String> list = ArrayList<>();
// 添加数据
list.add("张三");
list.add("李四");
list.add("王五");
// 删除数据
list.remove(2);
// 获取数据
System.out.println(list.get(1));//李四
// 获取集合中元素的个数
System.out.println(list.size());//2
4)内存分配方式:数组在内存中是连续存储的,占用的内存空间是固定的。而集合类中的元素可以在内存中不连续存储,可以根据需要动态分配内存空间。
5)功能扩展性:集合类提供了丰富的方法和功能,例如排序、查找、遍历等,可以方便地进行各种操作。而数组的功能相对较少,需要自己编写代码实现相应的功能。
6)泛型:集合支持泛型,可以指定存储的元素类型,提供了更好的类型安全性。(避免类型转换问题)
小结:
- 数组和集合都是容器,可以存储多个数据。
- 数组的长度是固定的,一旦创建,无法改变。
- 集合的长度是可变的,可以动态添加或删除元素。
- 数组可以存储基本类型数据和引用类型数据,而集合只能存储引用类型数据。如果要存储基本类型数据,需要使用对应的包装类。
- 数组可以通过索引方式直接访问元素,而集合通常通过方法调用进行元素的操作和访问。
总的来说,数组适用于长度固定且元素类型简单的情况,而集合适用于长度可变且元素类型复杂的情况。
单列集合:
双列集合:
2、List、Set、Map的区别?
List、Set和Map是Java中常用的集合接口,它们有以下区别:
-
List是一个有序的集合,可以包含重复元素。它允许按照插入顺序访问集合中的元素,并且可以根据索引位置进行操作。常见的实现类有ArrayList、LinkedList 和 Vector。
-
Set是一个不允许包含重复元素的集合。它不保证元素的顺序,即不按照插入顺序存储元素。常见的实现类有HashSet、TreeSet 和LinkedHashSet。
- HashSet:基于哈希表实现,不保证元素的顺序。
- TreeSet:基于红黑树实现,按照元素的自然顺序或指定的比较器进行排序。
- LinkedHashSet:基于哈希表和链表实现,保持元素的插入顺序。
-
Map是一种键值对的集合,每个键都是唯一的。它允许通过键来访问和操作对应的值。常见的实现类有HashMap、TreeMap和LinkedHashMap、ConcurrentHashMap。
-
HashMap:基于哈希表实现,不保证键值对的顺序。
-
TreeMap:基于红黑树实现,按照键的自然顺序或指定的比较器进行排序。
-
LinkedHashMap:基于哈希表和双向链表实现,保持键值对的插入顺序。
-
ConcurrentHashMap:线程安全的Map。
-
ConcurrentHashMap:
ConcurrentHashMap位于juc包,它是哈希表的线程安全版本,并且对HashMap进行改进。相比于HashMap,在多线程环境下,ConcurrentHashMap提供了更好的并发性能和线程安全性,主要通过以下几个方面来实现:
- 分段锁:ConcurrentHashMap将整个哈希表分成多个段(Segment),每个段维护着一个独立的哈希表。不同的线程可以同时访问不同的段,从而提高并发性能。每个段都有自己的锁,只有在修改该段的时候才需要加锁,这样可以减小锁的粒度,提高并发性能。
- CAS操作:ConcurrentHashMap使用了CAS(Compare and Swap)操作来保证线程安全。CAS是一种无锁的原子操作,它可以在不使用锁的情况下实现对共享变量的原子操作。
- volatile修饰符:ConcurrentHashMap使用volatile修饰符来保证可见性。volatile修饰的变量对所有线程可见,当一个线程修改了volatile变量的值,其他线程可以立即看到最新的值。
ConcurrentHashMap的使用方式与HashMap类似,可以通过put、get、remove等方法来操作元素。但需要注意的是,虽然ConcurrentHashMap是线程安全的,但在某些情况下,仍然需要额外的同步措施来保证一致性。
3、ArrayList、LinkedList、Vector的区别?
1)数据结构层面:
- ArrayList底层基于动态数组实现的,适合随机访问元素。
- LinkedList底层基于双向链表实现,适合数据的频繁插入和删除操作。
- Vector和ArrayList类似,底层也是基于动态数组实现的,Vector大部分方法都是线程同步的。(JDK1.0就诞生了)
2)线程安全层面:
-
ArrayList和LinkedList在多线程场景下都是不安全的(不提供内置的同步机制),需要外部同步(同步方法或者锁)。
- 如何保证ArrayList的线程安全:https://blog.csdn.net/qq_44544908/article/details/129020075
-
Vector是线程安全的,因为它的大部分方法都是同步的,但在性能上不如ArrayList和LinkedList。
3)性能层面:
-
ArrayList在随机访问和尾部添加元素方面性能好,因为它底层基于动态数组实现,可以通过索引直接访问元素,随机访问元素的时间复杂度是O(1),尾部添加元素的时间复杂度也是O(1)。而ArrayList在列表的中间或头部插入和删除元素时,需要进行移位操作,所以性能较差,时间复杂度是O(n),n是列表的长度。
-
LinkedList在插入或删除元素时性能好(双向链表),在随机访问元素时性能较差。
-
Vector由于同步处理,性能较差,不推荐在非线程安全环境中使用。
示例:ArrayList、LinkedList、Vector的使用
package cn.z3inc.list;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 测试ArryList、LinkedList、Vector的使用
*
* @author 白豆五
* @version 2023/8/7
* @since JDK8
*/
public class ListDemo1 {
public static void main(String[] args) {
// ArrayList
List<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a", "b", "c", "d", "e", "f", "g"); // 批量添加
System.out.println("arrayList = " + arrayList); // arrayList = [a, b, c, d, e, f, g]
arrayList.remove(0);
System.out.println("arrayList = " + arrayList); // arrayList = [b, c, d, e, f, g]
// LinkedList
List<String> linkedList = new ArrayList<>();
Collections.addAll(linkedList, "a", "b", "c", "d", "e", "f", "g"); // 批量添加
System.out.println("linkedList = " + linkedList); // linkedList = [a, b, c, d, e, f, g]
linkedList.remove(0);
System.out.println("linkedList = " + arrayList); // arrayList = [b, c, d, e, f, g]
// Vector
List<String> vector = new Vector<>();
Collections.addAll(vector, "a", "b", "c", "d", "e", "f", "g"); // 批量添加
System.out.println("vector = " + vector); // vector = [a, b, c, d, e, f, g]
vector.remove(0);
System.out.println("vector = " + vector); // vector = [b, c, d, e, f, g]
}
}
运行结果:
小节:ArrayList底层基于动态数组实现,适用于随机访问遍历元素和尾部添加元素,但在中间插入和删除时性能较差;LinkedList底层基于双向链表实现,适用于插入和删除操作,但随机访问的性能较差;Vector与ArrayList类似但线程安全,性能较差,一般在多线程环境下使用。
4、Java集合的快速失败机制【fail-fast】
1)概念:
-
“fail-fast” 是Java集合类的一种错误检测机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如 某一个线程通过迭代器遍历集合时,如果其他线程对集合进行结构上的修改(添加、删除元素),则会抛出
ConcurrentModificationException
并发修改异常。 -
“fail-fast” 机制可以及时发现问题,避免后续操作基于错误的数据继续进行,但它并不能保证线程安全。因为它并不能阻止其他线程修改集合,只能在访问时进行检测。
2)原理:
-
“fail-fast” 机制的实现是通过记录集合在结构上被修改的次数来实现的。每个集合中都有一个
modCount
字段,用于记录集合的修改次数。每当对集合进行结构上的修改时,modCount
就会加1。而在进行迭代时,迭代器会存储一个expectedModCount
值,用于记录迭代器对集合进行修改的次数。 -
在迭代过程中,每次调用
hasNext()
和next()
方法时,会通过比较迭代器的expectedModCount和集合的modCount值来判断集合是否被修改过,如果两者不相等,则说明在迭代过程中有其他线程修改了集合,会立即抛出 ConcurrentModificationException异常。
示例:测试并发修改异常
package cn.z3inc.list;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* 测试并发修改异常
*
* @author 白豆五
* @version 2023/8/7
* @since JDK8
*/
public class ListDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d", "e");
// 获取list的迭代器
Iterator<String> it = list.iterator();
// 遍历
//it.hasNext()判断是否有下一个元素
//it.next()获取元素并将指针向后移动一位
while (it.hasNext()) {
System.out.println(it.next());
list.add("f");
}
}
}
运行结果:
5、List接口常用方法
添加元素:
boolean add(E e)
:向列表尾部添加一个元素。add(int index, E element)
:向列表的指定位置处插入一个元素。addAll(int index,Collection<? extends E> c)
:把另一个集合中的所有元素按照指定位置插入到当前列表中。
获取元素:
-
E get(int index)
:获取列表中指定索引处的元素。 -
int indexOf(Object o)
:获取列表中指定元素第一次出现的索引。 -
int lastIndexOf(Object o)
:获取列表中指定元素最后一次出现的索引。
删除元素:
E remove(int index)
:从列表中删除指定索引处的元素,返回值是被删除的元素。boolean remove(Object obj)
:从列表中删除指定的元素(只删除列表中首次出现的元素)。
修改元素:
E set(int index,E element)
:修改列表中指定索引处的元素,返回值是被修改的元素。
其他方法:
int size()
:返回列表中的元素个数。boolean isEmpty()
:用于判断列表是否为空。boolean contains(Object o)
:用于判断列表是否包含指定的元素。void clear()
:清除列表中的所有元素。Object[] toArray()
:把列表转成数组Iterator<E> iterator()
:获取用于遍历列表的迭代器。
6、List的三种遍历方式
Collection集合支持两种遍历方式:
- Iterator迭代器。
- 增强for循环。
List接口继承了Collection接口,所以以上两种遍历方式也适用于List,除次以外,List集合还支持for循环+get(索引)方式遍历。
- Iterator迭代器。
- 增强for循环。
- for循环+get(索引)。
方式一: 使用迭代器(Iterator)进行遍历
迭代器是一种设计模式,它提供了一种统一的方式来遍历集合中的元素,而无需暴露底层数据结构的细节。通过使用迭代器,我们可以按顺序访问集合中的每个元素,迭代器模式封装了集合的内部结构,使得遍历过程更加简单、安全和灵活。
迭代器模式在Java中被广泛应用,例如在集合类(如List、Set)和Map类中都提供了迭代器来遍历元素。同时,我们也可以自定义实现迭代器接口来支持自定义的数据结构的遍历。
示例:
package cn.z3inc.list;
import org.junit.Test;
import java.util.*;
/**
* List集合3种遍历方式
*
* @author 白豆五
* @version 2023/8/7
* @since JDK8
*/
public class ListNBForDemo1 {
@Test
public void testIterator() {
// 1.创建list集合
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c");
// 2.获取list集合的迭代器对象
Iterator<String> it = list.iterator();
// 3.遍历
// hasNext() 判断是否有下一个元素
// next() 获取下一个元素
while (it.hasNext()) {
String item = it.next();
System.out.println(item);
}
}
}
运行结果:
list.iterator()
:获取list集合的迭代器对象hasNext()
: 判断是否有下一个元素next()
: 获取下一个元素
方式二: 使用增强For进行遍历(foreach)
使用foreach遍历数组的底层实现是普通的for循环,而使用foreach遍历集合的底层实现是迭代器。
示例:
public class ListNBForDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c");
// 使用增强for遍历list集合
for (String item : list) {
System.out.println(item);
}
// 简写
//list.forEach(item -> System.out.println(item));
//list.forEach(System.out::println);
}
}
运行结果:
通过编译查看源码:javac -encoding utf-8 xxx.java
方式三:for循环+get(索引)方式遍历
public class ListNBForDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}