1、什么是集合
Java中的集合(Collections)框架是一个非常重要的部分,它提供了一套设计良好的支持对一组对象进行操作的接口和类。这些接口和类位于java.util
包中,使得我们可以以统一和灵活的方式处理对象集合。
集合体系
注释:
1.Collection接口是单列集合(一次只能添加一个数据),Map接口是双列集合(一次能添加一对数据 --- 键-值)
2.List系列的集合添加的元素是有序、可重复、有索引的(这里的有序是指存入和取出数据时的位置是相同的)
3.Set系列的集合添加的元素是无序、不重复、无索引的
2、Collection集合
Collection的基本方法
返回值类型 | 方法名 | 作用 |
---|---|---|
boolean | add(E e) | 将指定元素添加到集合中 |
void | clear() | 清空集合所有元素 |
boolean | remove(E e) | 将指定元素从集合中删除 |
boolean | contains(Object obj) | 判断集合中是否存在指定元素 |
boolean | isEmpty() | 判断当前集合是否为空 |
int | Size() | 得到集合当前元素的个数 |
Collection遍历方式
由于Collection集合是单列集合的顶级父类因此Collection的遍历方法在它子类中都是可以使用的
(1)迭代器遍历(iterator)
Collection集合获得iterator的方法
方法名 | 作用 |
Iterator<E> iterator() | 返回迭代器对象,并默认指向0索引 |
Iterator类的常用方法
返回值类型 | 方法名 | 作用 |
boolean | HasNext() | 判断当前位置是否为空,如果有元素返回ture,否则返回false |
E | next | 返回当前位置的元素,并将迭代器对象向后移 |
void | remove | 删除当前所指向集合的元素 |
注意:
若出现NoSuchElementException说明迭代器已指向集合最后一个元素的后面无法返回元素
在程序一次运行过程中迭代器遍历完毕后,指向元素不会复原
在一次循环中尽量不要出现俩次以上的next方法
迭代器在遍历过程中不能用集合的增删方法,但如果要在遍历中删除元素可以在循环遍历的过程中调用迭代器的删除(remove)方法
格式:
1、用Collection系列的集合调用iterator方法得到集合的迭代器对象
2、利用迭代器的hasNext方法作为while循环的条件,在每个循环中调用迭代器的next方法让迭代器返回当前的元素并将迭代器指向下一个元素
public static void main(String[] args)
{
//创建一个ArrayList集合
Collection<String> myCollection = new ArrayList<>();
//Collection的添加元素的方法
myCollection.add("A");
myCollection.add("B");
myCollection.add("C");
myCollection.add("D");
//创建一个迭代器,并用while循环遍历数据
Iterator<String> myiterator = myCollection.iterator();
while(myiterator.hasNext())
{
System.out.println(myiterator.next());
}
//结果:A B C D
}
在有迭代器遍历时删除元素
public static void main(String[] args)
{
//创建一个ArrayList集合
Collection<String> myCollection = new ArrayList<>();
//Collection的添加元素的方法
myCollection.add("A");
myCollection.add("B");
myCollection.add("C");
myCollection.add("D");
//创建一个迭代器,并用while循环遍历数据
Iterator<String> myiterator = myCollection.iterator();
while(myiterator.hasNext())
{
String a = myiterator.next();
if(a.equals("D")){
myiterator.remove();
}else{
System.out.println(a);
}
}
//结果:A B C
}
(2)增强for遍历
格式:
for(元素数据类型 元素名 : 集合名){}
例:
public static void main(String[] args){
//创建集合
Collection<String> list = new ArrayList<>();
//添加元素
list.add("A");
list.add("B");
list.add("C");
list.add("D");
//遍历数据
for(String s : list){
System.out.println(s);
}
//结果:A B C D
}
(3)Lambda表达式遍历
格式:
集合名.forEach(匿名内部类){}
例:
public static void main(String[] args){
//创建集合
Collection<String> list = new ArrayList<>();
//添加元素
list.add("A");
list.add("B");
list.add("C");
list.add("D");
//--------------------------------------------
list.forEach(new Consumer<String>(){
public void accept(String s){
System.out.println(s);
}
});
//--------------------------------------------
//可简化为一下式子
list.forEach(s->System.out.println(s));
//结果 A B C D
}
3、List集合
List继承与Collection拥有Collection中的全部方法也有自己特有的方法。
List集合方法:
返回值类型 | 方法名 | 作用 |
---|---|---|
void | add(int index , E element) | 将指定元素添加到指定位置 |
E | remove(int index) | 删除指定位置的元素,并返回 |
E | set(int index , E element) | 修改指定位置元素,并返回修改后的元素 |
E | get(int index) | 返回指定位置的元素 |
List的遍历方式
1.迭代器遍历
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
//结果 A B C D
}
2.列表迭代器遍历
ListIterator类
允许双向遍历列表(即可以向前也可以向后遍历),还可以修改遍历中的元素
方法介绍:
返回值类型 | 方法名 | 作用 |
void | add(E e) | 将指定元素添加到集合中 |
boolean | hasNext( ) | 判断集合中是否存在元素,有返回ture否则返回false |
boolean | hasPrevious( ) | 在以正向遍历列表时,如果迭代器之后还有元素返回ture,否则返回false |
E | next( ) | 将迭代器移到下一个位置,并返回元素 |
int | nextIndex( ) | 返回迭代器当前位置下一个元素的索引 |
E | previous( ) | 返回迭代器当前位置之后元素的索引 |
int | previousIndex( ) | 返回迭代器当前位置前一个元素的索引 |
void | remove( ) | 从列表中删除由next或previous返回的最后一个元素 |
void | set( ) | 从列表中修改由next或previous返回的最后一个元素 |
在遍历中增删元素
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
//得到listiterator迭代器
ListIterator<String> listIterator = list.listIterator();
while(listIterator.hasNext()){
String s = listIterator.next();
if(s.equals("B")) listIterator.remove();
if(s.equals("C")) listIterator.add("T");
}
System.out.println(list);
//结果 A C T D
}
3.增强for遍历
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for(String s : list){
System.out.println(s);
}
//结果 A B C D
}
4.Lambda表达式遍历
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//结果 A B C D
}
5.普通for遍历
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//结果 A B C D
}
List接口的实现类
(1)Array List集合
ArrayList集合的底层原理:
ArrayList集合的底层原理是数组这意味着它使用连续的内存空间来存储元素
调用无参构造创建集合
- 在Java 1.6及之前版本中,使用无参构造函数创建的
ArrayList
,其底层数组会有一个默认的初始容量,通常是10。但请注意,随着Java版本的更新,这一行为可能会有所变化。 - 在Java 1.7和1.8及更高版本中,使用无参构造函数创建的
ArrayList
,其底层数组初始时是一个空数组(即长度为0)。只有在添加第一个元素时,才会根据需要进行扩容,通常扩容到10个元素的空间。
调用带参构造创建数组
如果在创建ArrayList
时,通过带有初始容量参数的构造函数指定了初始容量,那么底层数组的大小就会直接被初始化为该容量值。例如,new ArrayList<>(100)
会创建一个初始容量为100的ArrayList
。
ArrayList的构造方法:
方法名 | 作用 |
ArrayList( ) | 空参 |
ArrayList(Collection<? extends E> c) | 创建一个指定的Collection的元素的列表 |
ArrayList(int initalCapacity) | 创建一个指定长度的列表 |
ArrayList的常用方法
返回值类型 | 方法名 | 作用 |
boolean | add(E e) | 将指定元素添加到集合的尾部 |
void | add(int index , E e) | 将指定元素插入列表的指定位置 |
void | clear( ) | 移除列表中的所有元素 |
boolean | contains(Object o) | 如果列表中有指定元素返回ture,否则返回false |
E | get(int index) | 返回列表中指定位置的元素 |
boolean | isEmpty( ) | 判断集合是否为空,如果为空返回ture,否则返回false |
boolean | remove(Object o) | 删除指定元素 |
E | set(int index , E e) | 修改指定位置的元素 |
int | size( ) | 得到列表的元素个数 |
public static void main(String[] args){
ArrayList<String> myArrayList = new ArrayList<>();
//添加元素
myArrayList.add("A");
myArrayList.add("B");
myArrayList.add("C");
myArrayList.add("D");
myArrayList.add("E");
//得到集合中的元素个数
int i = myArrayList.size();
System.out.println(i);
//添加元素到指定位置
myArrayList.add(5,"F");
System.out.println(myArrayList);
//判断集合中是否有指定元素
boolean s = myArrayList.contains("C");
System.out.println(s);
//返回列表中指定位置的元素
String e = myArrayList.get(2);
System.out.println(e);
//修改指定元素
myArrayList.set(4,"G");
System.out.println(myArrayList);
//删除指定元素
myArrayList.remove("A");
System.out.println(myArrayList);
//移除集合中的所有元素
myArrayList.clear();
//判断集合是否为空
System.out.println(myArrayList.isEmpty());
}
结果:
(2)LinkedList集合
LinedList集合的底层是双向链表特点是查询慢,增删快
常用方法:
返回中类型 | 方法名 | 作用 |
void | addFirst(E e) | 在列表头插入新的指定元素 |
void | addlast(E e) | 在列表尾插入新的指定元素 |
E | getFirst( ) | 返回第一个元素 |
E | getlast( ) | 返回最后一个元素 |
E | removeFirst( ) | 删除头元素,并返回 |
E | removelast( ) | 删除尾元素,并返回 |
4、Set集合
set集合的特点:
1.无序:元素的存取顺序不一致
2.不重复:集合中的元素都不相同
3.无索引:集合无法通过索引来找元素,所有不能用for循环来遍历
set集合的方法上基本与Collection的API一致
Set接口的实现类
(1)HashSet
特点:无序、不重复、无索引
底层是哈希表,具有快速插入、查找、及增删的操作
底层原理:
1.一开始在底层会创建一个长度为16的,默认加载因为0,75的数组
2.根据元素的哈希值根数组的长度计算出应存入的位置
3.判断当前的位置是否为null,如果是直接存入
4.如果当前位置不为null,表示有元素,则调用equals方法比较属性值
5.如果一致:不存入 不一致:存入数组,形成链表
在JDK8前:新元素存入数组,老元素和新元素形成链表
在JDK8后:新元素直接与老元素形成链表
public static void main(String[] args){
//创建集合
HashSet<String> myHashSetset = new HashSet<>();
//添加元素
myHashSetset.add("A");
myHashSetset.add("B");
myHashSetset.add("D");
myHashSetset.add("C");
System.out.println(myHashSetset);
//添加重复元素
myHashSetset.add("A");
System.out.println(myHashSetset);
}
结果:
(2)LinkedHashSet
特点:有序、不重复、无索引
底层原理:底层是哈希表,但多用了一个双向链表的机制记录存储的顺序
public static void main(String[] args){
//创建集合
HashSet<String> myHashSetset = new HashSet<>();
//添加元素
myHashSetset.add("A");
myHashSetset.add("B");
myHashSetset.add("D");
myHashSetset.add("C");
System.out.println(myHashSetset);
//添加重复元素
myHashSetset.add("A");
System.out.println(myHashSetset);
}
结果:
由于有序性输出的顺序与输入的顺序相同
(3)TreeSet
特点:可排序、无索引、不重复
可排序:按照元素的默认规则(从小到大)排序
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查都较好
TreeSet集合默认的排序规则
对于数值类型:Integer、Double,默认按照从小到大进行排序
对于字符、字符串类型:按照字符的ASCll码表的数字升序进行排列
补: "abc" "aaaa" 像这样的字符串类型比大小时是用一一比较的方式而不是用每个字符的ASCll码的总和来计算
5、Map集合
Map的概念
Java中的Map
集合是一种将键(Key)映射到值(Value)的对象,它可以存储键值对,其中每个键最多只能映射到一个值。Map接口的实现(如HashMap, TreeMap, HashTable等)提供了存储和操作键值对的不同机制。Map接口不是java.util.Collection
接口的子接口,因此Map接口的实现并不支持Collection接口中定义的任何集合视图。
特点:
1.双列集合一次需要存一对数据,分别为键和值
2.键不能重复,值可以
3.键和值是一一对应的,每一个键只能找到自己对应的值
Map的基本方法
返回值类型 | 方法名 | 作用 |
V | put(K key , V value) | 添加元素 |
V | remove(Object key) | 根据键删除键值对元素 |
void | clear( ) | 移除所有的键值对元素 |
boolean | containsKey(Object key) | 判断集合是否存在指定的键 |
boolean | containsValue(Object value) | 判断集合是否存在指定的值 |
boolean | isEmpty( ) | 判断集合是否为空 |
int | size( ) | 返回集合的长度 |
注意:
put方法细节
1.在添加数据时,如果要添加数据的键中本身不存在元素,那么将对象添加到Map集合的列表中并返回null
2.如果要添加数据的键中本身存在元素,那么新添加的对象的值会覆盖原本此键上的值并返回原本的值
Map集合的遍历方式
1.用键找值
public static void main(String[] args){
//创建Map集合的对象
Map<String,String> map = new HashMap<>();
//添加元素
map.put("A","AAA");
map.put("B","BBB");
map.put("C","CCC");
//遍历
//将map集合的所有键放到一个单列集合中
Set<String> keys = map.keySet();
//遍历单列集合
for(String key : keys){
String value = map.get(key);
System.out.println(key + " = " + value);
}
}
结果:
2.用值找键
public static void main(String[] args){
//创建Map集合的对象
Map<String,String> map = new HashMap<>();
//添加元素
map.put("A","AAA");
map.put("B","BBB");
map.put("C","CCC");
//遍历
//通过entrySet方法得到所有键值对对象
Set<Map.Entry<String , String>> entries = map.entrySet();
for(Map.Entry<String,String> entry : entries){
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+" = "+value);
}
}
结果:
Map的实现类
(1)HashMap
没有额外的特有方法,直接使用Map的方法
特点是由键决定的:无序、不重复、无索引
底层原理和HashSet一样是哈希表
依赖HashCode方法和equals方法保证键的唯一
如果键存储的是自定义对象,需要重写hashCode和equals方法,而如果是值则不用
(2)LindedHashMap
特点是由键决定的:有序、不重复、无索引
原理:底层是哈希表,在每个键值对元素又额外的多了一个双链表记录存储的顺序
主要特性
- 有序性:LinkedHashMap通过维护一个双向链表来记录元素的插入顺序或访问顺序(基于配置)。默认情况下,它按照元素的插入顺序进行迭代,但可以通过构造函数参数来指定是否按访问顺序(即最近访问的元素会移到链表的末尾)进行迭代。
- 快速访问:与HashMap一样,LinkedHashMap保持了较快的查找、插入和删除操作的时间复杂度O(1)。
- 可预测的迭代顺序:由于维护了链表结构,LinkedHashMap的迭代顺序是稳定的,可以根据需要选择插入顺序或访问顺序进行迭代。
应用场景
实现简单的缓存机制:通过设置accessOrder为true,可以实现LRU(最近最少使用)缓存。当缓存达到一定大小时,可以自动删除最久未使用的元素。
需要保持元素顺序的场景:例如在处理JSON数据时保持字段顺序,或者在需要按照特定顺序输出元素时,LinkedHashMap非常有用。
优缺点优点:
- 可以保持元素的插入顺序或访问顺序。
- 查找、插入和删除操作的时间复杂度较低。
- 相比普通的HashMap,LinkedHashMap需要额外的内存来维护链表结构,因此会占用更多的内存空间。
- 在插入和删除元素时,相对于普通的HashMap,性能略低。
public static void main(String[] args) {
LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
// 按插入顺序输出
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// 访问一下元素
map.get(2);
// 再次按插入顺序输出(如果未设置accessOrder为true)
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
(3)TreeMap
TreeMap 是 Java 集合框架中的一个类,它实现了 NavigableMap 接口,是基于红黑树(Red-Black Tree)数据结构实现的。红黑树是一种自平衡的二叉搜索树,它能够保证在插入、删除和查找等操作中的时间复杂度为 O(log n)。
主要特点
- 有序性:TreeMap 中的元素按照键的自然顺序(即实现 Comparable 接口)或通过提供的 Comparator 进行排序。这意味着 TreeMap 能够以排序的方式存储键值对,并提供与排序相关的操作。
- 动态更新:支持动态插入、删除和修改键值对操作,而且这些操作会保持元素的有序性。
- 范围查询:提供了一系列的方法来支持范围查询,如 headMap、tailMap 和 subMap 等,这些方法可以根据指定的范围获取子映射。
- 不允许空键:TreeMap 不允许存储 null 键,否则会抛出 NullPointerException,但允许值为 null。
- 线程不安全:与 HashMap 类似,TreeMap 不是线程安全的。如果需要在多线程环境中使用,可以使用 Collections.synchronizedSortedMap 方法获得一个同步的 TreeMap,或者使用 ConcurrentSkipListMap。
应用场景
- 当需要按照键的顺序访问和处理数据时,可以使用 TreeMap 来存储键值对,并利用排序特性方便地进行相关操作。
- TreeMap 提供了一系列的范围查询方法,适合用于需要根据键的范围来查询和操作数据的场景。
- TreeMap 结构适合存储时间轴数据,因为时间是有序的。
- TreeMap 可以用于实现基于 LRU(最近最少使用)算法的缓存,通过自定义比较器将访问顺序作为键的排序依据。