一、集合的简介
1.1 什么是集合
集合(Collection),也是一个数据容器,类似于数组,但是和数组是不一样的。集合是一个可变的容器,可以随时向集合集合中添加元素,也可以随时从集合中删除元素。另外,集合还提供了若干个用来操作集合中数据的方法。
集合里的数据,我们称之为元素(elements);集合只能用来存储集合只能用来存储引用类型的数据,不能存储八大基本数据类型的数据。
1.2 泛型的引入
Java SE 5.0以前,集合的元素只要是Object类型就行,那个时候任何对象都可以存放在集合内,但是从集合中获取对象后,需要进行正确的强制类型转换。但是,Java SE 5.0 以后,可以使用新特性”泛型”,用来指定要存放在集合中的对象类型。避免了强制转换的麻烦。
1.3 集合与数组
1. 数组是定长的容器,一旦实例化完成,长度不能改变。集合是可变长的,可以随时的进行增删操作。
2. 数组中可以存储基本数据类型和引用数据类型的元素,集合中只能存储引用数据类型的元素。
3. 数组的操作比较单一,只能通过下标进行访问。集合中提供了若干个方便对元素进行操作的方法。
小贴士:存储引用类型时,集合与数组,存储的其实都是对象的地址。
1.4 集合框架体系图
二、Collection接口
2.1 简介
Collection接口是子接口List/Set/Queue的父接口,里面提供了子类共同的常用方法。
既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
作为父接口,其子类集合的对象,存储元素的特点,可能是无序的,也可能是有序的,因此在父接口中并没有定义通过下标获取元素的方法功能。
2.2 常用方法
1)boolean add(E e) : 向集合中添加元素。
//使用多态的向上造型创建一个子类型对象
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
System.out.println(c1);
System.out.println(c1.add("D"));//true
运行结果:
[A, B, C]
true
2)boolean isEmpty():判断一个集合是不是空的。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
boolean empty = c1.isEmpty();
System.out.println("empty: "+empty);
运行结果:false
3)int size():获取集合里有几个元素。
int index = c1.size();
System.out.println("index: "+index);
4) boolean addAll(Collection c) : 向集合中添加另一个集合的全部。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
Collection<String> c2 = new ArrayList<>();
c2.add("B");
c2.add("C");
c2.add("D");
c1.addAll(c2);
System.out.println(c1);
运行结果:[A,B,C,B,C,D]
5)boolean contains(Object o):查看是否包含子字符串o。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
boolean c = c1.contains("B");
System.out.println("是否包含字符串B: "+c);
运行结果:是否包含字符串B: true
6)boolean containsAll(Collection c):查看集合是否包含 一个子集合。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
Collection<String> c2 = new ArrayList<>();
c2.add("B");
c2.add("C");
c2.add("D");
boolean d = c1.containsAll(c2);
System.out.println("c1集合是否包含c2集合: "+d);
运行结果:c1集合是否包含c2集合: false
7)boolean equals(Object o):判断两个集合是否相等。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
Collection<String> c2 = new ArrayList<>();
c2.add("B");
c2.add("C");
c2.add("D");
boolean e = c1.equals(c2);
System.out.println("c1和c2相同吗? "+e);
运行结果:c1和c2相同吗? false
8)boolean remove(Object o): 移除指定元素,找到返回true。找到第一个满足条件的进行移除,不再继续向下找。
Collection<String> c3 = new ArrayList<>();
c3.add("A");
c3.add("B");
c3.add("C");
c3.add("B");
c3.add("D");
c3.add("D");
boolean d1 = c1.remove("D");
System.out.println("移除元素D: " + d1);
System.out.println("移除D元素后的c3: " + c3);
运行结果:
移除元素D: true
移除D元素后的c3: [A,B,C,B,D]
9)boolean removeAll(Collection<?> c):从集合中删除子集中所具有每个的元素。
Collection<String> c3 = new ArrayList<>();
c3.add("A");
c3.add("B");
c3.add("C");
c3.add("B");
c3.add("D");
c3.add("D");
Collection<String> c4 = new ArrayList<>();
c4.add("B");
c4.add("C");
c3.removeAll(c4);
System.out.println("移除c4后的c3: "+c3);
运行结果:
移除c4后的c3:[A,D,D]
10)boolean retainAll(Collection c):保留两个集合的交集部分。
Collection<String> c3 = new ArrayList<>();
c3.add("A");
c3.add("B");
c3.add("C");
c3.add("B");
c3.add("D");
c3.add("D");
Collection<String> c5 = new ArrayList<>();
c5.add("A");
c5.add("E");
c3.retainAll(c5);
System.out.println("保留c5后的c3: "+c3);
运行结果:[A]
11)void clear():清空集合里的所有元素。
Collection<String> c1 = new ArrayList<String>();
c1.add("A");
c1.add("B");
c1.add("C");
c1.clear();
System.out.println(c1);
System.out.println(c1.size());
运行结果:
[]
0
12)String toString():将集合以字符串的形式输出。
2.3 集合的迭代
1. 集合的迭代,就是指集合的遍历操作。
2. 几乎所有的集合子类型都实现了迭代接口Iterable。
3. 迭代接口提供了多个方式用于迭代。
- boolean hasNext(): 用于判断集合中是否有下一个元素。
可以形象的认为有指针,指针的最初位置在第一个元素前面。
- E next(): 用于返回指针指向的那个元素。然后指针就会移动到下一个元素,为下一次的判断做准备。
- E remove(): 用于删除指针指向的那个元素。
public class CollectionDemo02 {
public static void main(String[] args) {
Collection<Integer> c1 = new ArrayList<>();
c1.add(1);
c1.add(2);
c1.add(3);
c1.add(4);
c1.add(5);
/**
* 使用增强for循环遍历(foreach): 底层原理就是迭代器
*/
for (Integer i : c1) {
System.out.println(i+" ");
}
/**
* 使用迭代器iterator进行遍历
*/
//第一步:获取要遍历的集合对象的迭代器对象
Iterator<Integer> it = c1.iterator();
//第二步:问是否有下一个元素。
while (it.hasNext()) {
Integer a = it.next();
System.out.println("a="+a);
}
}
}
注意:在使用迭代器时,不可以调用集合自己的remove(Object o)方法。
要调用迭代器的remove()方法。
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> c1 = new ArrayList();
c1.add("a");
c1.add("b");
c1.add("c");
c1.add("c");
c1.add("d");
c1.add("d");
/**
* 在使用迭代器时,如果发现有c元素,就将其删除
*/
Iterator<String> it = c1.iterator();
while (it.hasNext()) {
String element = it.next();
if (element.equals("a")) {
//使用集合自己的删除方法 会报ConcurrentModificationException,表示不应该修改元素
//c1.remove(element);
//可以使用迭代器自己的方法remove
it.remove();
}
}
System.out.println(c1);
}
}
三、List子接口
3.1 简介
1. List是一个元素有序并且可以重复的集合。集合中的每个元素都有其对应的顺序索引,从0开始。
2. List允许使用重复的元素,可以通过索引来访问指定位置的集合元素。
3. List默认按元素的添加顺序设置元素的索引。
4. List集合里添加了一些根据索引来操作集合元素的方法。
3.2 ArrayList和LinkedList
这两个类都是List接口的实现类(子类)。
两者在实现上的底层原理对比:
- ArrayList:实现了基于动态数组的数据结构,对象存储在连续的位置上。
- LinkedList:实现了双向链表的数据结构,链表中的每个节点都包含了前一个和后一个元素的引用。
对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
对于插入和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
基于List接口,两个实现类的方法基本一致。
Vector:是一个古老的集合, JDK1.0版本时候出现,现在已经被ArrayList和LinkedList替代。
是线程安全的。
Stack:是一个古老的集合, JDK1.0版本时候出现,模拟栈结构存储数据。是线程安全的。
3.3 常用方法
1)添加元素
1. boolean add(E e):向列表末尾添加指定的元素。
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
//添加两个元素
adds.add("beijing");
adds.add("shanghai");
System.out.println(adds);
运行结果:
[beijing, shanghai]
2. void add(int index, E element) : 在指定下标处插入元素,下标范围 [0,size()]。
//创建一个ArrayList集合对象
List<String> names = new ArrayList<>();
names.add("michael");
names.add(1,"bob");
System.out.println(names);
运行结果:
[michael,bob]
3. boolean addAll(Collection c):向一个集合的末尾,添加另一个集合。
//创建一个ArrayList集合对象
List<String> names = new ArrayList<>();
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
names.add("michael");
names.add(1,"bob");
//地址集合添加两个元素
adds.add("beijing");
adds.add("shanghai");
names.addAll(adds);
System.out.println(names);
运行结果:
[michael,bob,beijing, shanghai]
4. boolean addAll(int index,Coolection c):在指定下标处插入一个集合,下标范围[0,size()]。
//创建一个ArrayList集合对象
List<String> names = new ArrayList<>();
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
names.add("michael");
names.add(1,"bob");
//地址集合添加两个元素
adds.add("beijing");
adds.add("shanghai");
names.addAll(1,adds);
System.out.println(names);
运行结果:
[michael, beijing, shanghai, bob]
2)获取元素
E get(int index): 获取指定索引处的元素。
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
adds.add("beijing");
adds.add("shanghai");
String ele = adds.get(1);
System.out.println("ele:"+ele);
运行结果:
ele:shanghai
3)查找元素
1.int indexOf(Object o):返回指定元素的第一个下标,如果没有返回-1。
2.int lastIndexOf(Object o): 返回指定元素的最后一个下标,如果没有返回-1。
//创建一个ArrayList集合对象
List<String> names = new ArrayList<>();
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
names.add("michael");
names.add(1,"bob");
//地址集合添加两个元素
adds.add("beijing");
adds.add("shanghai");
names.addAll(1,adds);
int index = names.indexOf("bob");
System.out.println("index:"+index);
index = names.lastIndexOf("john");
System.out.println("index:"+index);
运行结果:
index:3
index:-1
4)移除元素
1. E remove(int index):移除指定下标处的元素,并返回。
2. boolean remove(Object obj):移除该元素,如果有返回true。
//创建一个LinkedList集合对象
List<String> adds = new LinkedList<>();
adds.add("beijing");
adds.add("shanghai");
boolean f = names.remove("lucy");
System.out.println("f:"+f);
String address = adds.remove(0);
System.out.println("address:"+address);
System.out.println(adds);
运行结果:
f:false
address:beijing
[shanghai]
5)修改元素
E set(int index,E e): 将指定位置上的元素替换成形参e,并返回指定位置上的元素。
List<String> names = new ArrayList<>();
names.add("michael");
names.add("beijing");
names.add("shanghai");
names.add(1,"bob");
String add1 = names.set(2, "深圳");
System.out.println("add1:"+add1);//shanghai
System.out.println("names:"+names); //[michael, beijing, 深圳, bob]
String add2 = names.set(0,names.set(1,"沈阳"));
System.out.println("add2:"+add2);// michael
System.out.println("names:"+names);// [beijing, 沈阳, 深圳, bob]
6)截取子集
List subList(int fromIndex,int endIndex):用于截取集合的一部分,包前不包后
[fromIndex,endIndex),返回截取的子集。
List<Integer> nums = new ArrayList<>();
for (int i = 1; i < 11; i++) {
nums.add(i);
}
System.out.println(nums);//[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
List<Integer> sub = nums.subList(3, 7);
System.out.println(sub);//[4, 5, 6, 7]
3.4 ListIterator
ListIterator是Iterator接口的子接口,继承到了Iterator中所有的方法,同时自己也添加了若干个方法。 允许使用ListIterator在进行元素迭代的时候,对集合中的数据进行修改,或者对集合的长度进行修改。 同时,使用ListIterator还可以进行倒序的迭代。
注意:在进行迭代的过程中,允许修改集合。但是要注意,这个修改集合,只能通过迭代器接口中的方法进行,并不能够通过集合中的方法进行修改。
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
// 向后指向一位,并返回当前指向的元素
String ele = iterator.next();
if (ele.equals("Vertu")) {
// 在迭代的过程中,删除这个元素
iterator.remove();
// 在迭代的过程中,添加一个元素(加在当前迭代的这个元素的后面)
iterator.add("HTC");
// 在迭代的过程中,对当前迭代的元素进行修改
iterator.set("Nokia");
}
}
四、Queue子接口
4.1 Queue子接口的简介
Queue:队列,也是Collection的一个子接口。
1. 用来描述一种先进先出(First Input First Output)的数据存储结构。
2. 可以理解为是一种特殊的线性表,只能从一头添加(offer)元素,另一头取出(poll)元素。
3. 对应的实现类LinkedList。
其主要方法如下:
1. boolean offer(E e): 将一个对象添加到队尾,如果添加成功返回true。
2. E poll(): 从队列的头部出来 ,并返回这个元素。
3. E peek(): 查看队列头部的元素。
//创建一个名字队列
Queue<String> names = new LinkedList<>();
//调用offer方法,入队
names.offer("michael");
names.offer("张三");
names.offer("李四");
System.out.println(names);
//查看队列的头部对象
String head = names.peek();
System.out.println(head);
//让头部元素出队
while (names.peek() != null) {
//如果想要移除头部元素,最后先查看是否有头部元素,如果有再移除。
String oldHead = names.poll();
System.out.println(oldHead);
System.out.println(names);
运行结果:
[michael, 张三, 李四]
michael
michael
[张三, 李四]
张三
[李四]
李四
[]
4.2 Deque
Deque:Queue的子接口,实现了双端队列的存储结构。对应的实现类,还是LinkedList。
双端队列:即从队列的两头都可以入队(offer)和出队(poll)。
提供的方法:
1. offerFirst:从队首进入。
2. offerLast:从队尾进入。
3. pollFirst:从队首出来。
4. pollLast:从队尾出来。
public static void main(String[] args) {
//创建一个双端队列
Deque<String> names = new LinkedList<>();
//添加第一个元素
names.offer("小明");
//第二个元素从队列的头部进入
names.offerFirst("小红");
//第三元素从队列的尾部进入 offer() offerLast()
names.offerLast("小芳");
System.out.println(names);
//移除元素
//移除队列的头部 poll()或pollFirst()
String head = names.poll();
System.out.println("head:" + head);
//移除队列的尾部
String tail = names.pollLast();
System.out.println("tail:" + tail);
System.out.println(names);
}
运行结果:
[小红, 小明, 小芳]
head:小红
tail:小芳
[小明]
如果将Deque限制为只能一端出队和入队,那么就可以实现“栈(Stack)”的数据结构,
对于栈来说,入栈被称为push,出栈被称为pop。遵循先进后出(First Input Last Output 简称FILO)
的原则。
使用Deque来模拟栈的存储结构:
public static void main(String[] args) {
//创建一个存储结构
Deque<String> stack = new LinkedList<>();
//从栈的出口进入, 对应的方法push(), 表示将元素推进去。
stack.push("michael"); //推入到栈的底部
stack.push("lucy"); //推入,在最底层的上一层
stack.push("lily");
stack.push("tom");
System.out.println(stack);
//出栈(弹出): 对应的方法pop() 将元素弹出栈结构。
while (stack.size() > 0) {
String el = stack.pop();
System.out.println(el);
}
}
运行结果:
[tom, lily, lucy, michael]
tom
lily
lucy
michael
五、Set子接口
5.1 Set子接口的简介
Set接口:是Collection接口的子接口。Set接口里的方法都是Collection接口的方法。
1. Set集合用来存储不重复的元素。
添加新元素时的去重原理:
1. 先计算要添加元素的hash值,然后确定哈希表中该值的位置上是否存在元素。
2. 如果不存在,可以添加,如果存在,就需要调用equals进行比较。
3. 如果equals返回true,说明不能在存入了。如果equals返回false,说明可以存入。
注意:每个hash值对应的位置都维护了一个单向链表。Set集合可以存储hash值相同,equals不同的元素。
2. Set集合中的元素是无序的(取出的顺序和存入的顺序无关)。
元素一旦存入,在存储结构里的顺序就固定了,和存入的先后顺序无关。
public static void main(String[] args) {
//创建一个Set接口变量
Set<String> names = new HashSet<String>();
names.add("michael");
names.add("lucy");
names.add("john");
names.add("tom");
//每次打印的结果都一样
System.out.println("names: " + names);
System.out.println("names: " + names);
}
运行结果:
names: [michael, tom, john, lucy]
names: [michael, tom, john, lucy]
5.2 实现类
5.2.1 HashSet
HashSet是实现Set接口的最最最经典的子类型。大多数时候使用 Set 集合时都使用这个实现类。
底层的存储方式使用的是Hash算法(哈希表),因此具有很好的存取和查找性能。
HashSet的特点:
-
1) 不能保证元素的排列顺序。
-
2) HashSet 不是线程安全的。
-
3) 集合元素可以是 null,且只能是一个。
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,依然可以添加成功。
public static void main(String[] args) {
//创建一个Set接口变量
Set<String> names = new HashSet<String>();
names.add("michael");
names.add("lucy");
names.add("john");
names.add("tom");
//每次打印的结果都一样
System.out.println("names: " + names);
System.out.println("names: " + names);
/**
* 打印一下四个元素的哈希值
*/
System.out.println("michael".hashCode()%16);
System.out.println("tom".hashCode()%16);
System.out.println("john".hashCode()%16);
System.out.println("lucy".hashCode()%16);
/*
* 添加元素michael
* 因为数据结构中已经存在michael,所以新的michael是添加不进来的。
*/
names.add("michael");
System.out.println(names);
System.out.println(names.size());
}
运行结果:
names: [michael, tom, john, lucy]
names: [michael, tom, john, lucy]
7
2
11
15
[michael, tom, john, lucy]
4
5.2.2 HashSet的遍历
1. set是无序的,因此不能使用for i 遍历。
2. 使用增强for循环(for each)遍历。
3. 使用迭代器进行遍历。
public static void main(String[] args) {
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(20);
set.add(30);
set.add(40);
set.add(50);
System.out.println(set);
for (Integer i : set) {
System.out.println(i);
}
Iterator<Integer> it = set.iterator();
while (it.hasNext()) {
Integer i = it.next();
System.out.println(i);
}
}
运行结果:
[50, 20, 40, 10, 30]
50
20
40
10
30
50
20
40
10
30
5.2.3 LinkedHashSet
1. LinkedHashSet是HashSet的子类。
2. LinkedHashSet 集合根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护 元素的次序,这使得元素看起来是以插入顺序保存的。
这意味着LinkedHashSet具有以下两种特性:
- 有序性(Order):LinkedHashSet会保持元素的插入顺序,即元素被添加到集合中的顺序就是它们在集合中的顺序。
- 唯一性(Uniqueness):与
HashSet
一样,LinkedHashSet
保证元素的唯一性,不允许重复元素。
3. LinkedHashSet 性能插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
4. LinkedHashSet 不允许集合元素重复。
public static void main(String[] args) {
Set<String> set = new LinkedHashSet<String>();
set.add("a");
set.add("b");
set.add("c");
set.add("bob");
System.out.println(set);
//使用迭代器遍历
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
运行结果:
[a, b, c, bob]
a
b
c
bob
5.2.4 TreeSet
1. TreeSet 是SortedSet接口的实现类。
2. TreeSet集合的元素是有序的,即内部维护了一个二叉树算法的排序方式。
3. TreeSet集合里的元素也不能重复。
4. TreeSet默认是升序排序,如果想要改变默认默认的排序方式,可以自定义一个比较器,传入构造器中。
public static void main(String[] args) {
Set<String> set = new TreeSet<String>();
set.add("a");
set.add("h");
set.add("b");
set.add("f");
set.add("d");
set.add("bob");
set.add("bab");
System.out.println(set);
Comparator<Integer> c1 = new Comparator<Integer>(){
public int compare (Integer o1,Integer o2){
return o2 - o1;
}
};
Set<Integer> nums = new TreeSet<>(c1);
nums.add(10);
nums.add(1);
nums.add(20);
nums.add(2);
System.out.println(nums);
}
运行结果:
[a, b, bab, bob, d, f, h]
[20, 10, 2, 1]
5.2.5 常用方法
Set接口中没有新增的方法,所有的方法都是从父接口Collection中继承到的。
5.2.6 Set的去重原理
1)HashSet 、LinkedHashSet
1. 先计算要添加元素的hash值,然后确定哈希表中该值的位置上是否存在元素。
2. 如果不存在,可以添加,如果存在,就需要调用equals进行比较。
3. 如果equals返回true,说明不能在存入了。如果equals返回false,说明可以存入。
2) TreeSet
如果元素的比较规则中,两个对象的比较结果是0,则视为是同一个元素,去重。
六、List排序
6.1 Comparable接口
自定义的类,如果想要排序,不管是在数组中,还是在集合中。要求元素必须是Comparable的子类型,因为底层需要调用Comparable的CompareTo方法。所以,自定义的类,如果想要排序,那么必须实现Comparable接口,以及重写CompareTo方法。
6.2 工具类提供的排序方法
集合工具类:Collections。
1. 集合工具类和数组工具类一样,都提供了很多常用的方法,来操作集合对象。
- void sort(List<E> list): 对集合元素进行升序排序。
- void sort(<List<E>,Comparator<E> c): 改变集合元素的排序规则。
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
//将10个随机数存入到ArrayList集合中
for (int i = 0; i < 10; i++) {
list.add((int)(Math.random()*100));
}
System.out.println(list);
/**
* 利用工具类里的排序方法,默认升序排序。
*/
Collections.sort(list);
System.out.println(list);
/**
* 由于集合工具类里的sort方法是默认升序排序,
* 现在想要重新指定排序规则:降序。
* 那么就需要调用另外一个方法sort(<List<E>,Comparator<E> c)
*/
Comparator<Integer> c = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Collections.sort(list, c);
System.out.println(list);
}
运行结果:
[78, 84, 67, 92, 28, 83, 53, 52, 43, 77]
[28, 43, 52, 53, 67, 77, 78, 83, 84, 92]
[92, 84, 83, 78, 77, 67, 53, 52, 43, 28]
6.3 Compatator比较器接口
如果想要重新指定排序方式,不应该修改Comparable接口里的CompareTo方法的逻辑 ,而是应该使用Comparator比较器接口,来定义一个新的比较规则。调用集合或者数组的相关重载方法,可以传入一个比较器的这样的方法,进行新的排序。
6.4 Collections
Collections 是一个操作 Set、List 和 Map 等集合的工具类,提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
1)排序操作
1. reverse(List list):反转List中的元素顺序。
List<String> names = new LinkedList<String>();
names.add("michael");
names.add("peter");
names.add("mark");
names.add("john");
names.add("jane");
//打印集合,为添加元素时的顺序
System.out.println(names);
//调用集合工具类里的反转方法,将集合元素颠倒过来。
Collections.reverse(names);
System.out.println("反转后的结果:"+names);
运行结果:
[michael, peter, mark, john, jane]
反转后的结果:[jane, john, mark, peter, michael]
2. shuffle(List list):对List集合元素进行随机排序。
//将集合里的元素顺序打乱
Collections.shuffle(names);
3. swap(List list, int i, int j):将指定List集合中的i处元素和j处元素进行交换。
//将第二个和倒数第二个做一下交换
Collections.swap(names, 1, names.size()-2);
4. sort(List list):将List集合里的元素升序排序。
5. sort(List list,Comparator c):根据自定义的比较规则,对List集合里的元素进行排序。
2)查找、替换
1. Object max(Collection c):根据元素的自然排序,返回给定集合中的最大元素。
2. Object max(Collection c,Comparator c):根据自定义的规则进行排序。
注意:上述两种方法,返回的都是排序后的最后一个元素。
//调用max方法: 底层使用的是自然排序,找到最大的
String ele = Collections.max(names);
3. Object max(Collection c):根据元素的自然排序,返回给定集合中的最小元素。
4. Object max(Collection c,Comparator c):根据自定义的规则进行排序。
注意:上述两种方法,返回的都是排序后的第一个元素。
//调用min方法: 底层使用的是自然排序,找到最小的。
String ele2 = Collections.min(names);
5. int frequency(Collection c,Object o):返回指定集合中指定元素出现的次数。
//调用frequency方法,找出michael在names集合里出现的次数。
int count = Collections.frequency(names,"michael");
6. boolean replaceAll(List list, Object oldValue,Object newValue):使用新值替换List集合对象里所有的旧值。
//将集合里的所有michael替换成张三。
Collections.replaceAll(names,"michael","张三");
6.5 数组与集合之间的转换
6.5.1 集合转数组
1. Object[] toArray(): Object[] 一旦想要使用数组中元素的自己的方法和属性。
还需要强转: 将数组中的元素转成集合指定的类型。
List<String> names = new ArrayList<String>();
names.add("michael");
names.add("peter");
names.add("mark");
Object[] arr1 = names.toArray();
Object obj = arr1[1];
if(obj instanceof String ) {
String str = (String) obj;
char ch = str.charAt(1);
}
2. T[] toArray(T[] a):将集合转成数组。 只需要传入一个元素类型的数组对象,就可以返回元素类型的数组。
List<String> names = new ArrayList<String>();
names.add("michael");
names.add("peter");
names.add("mark");
String[] arr = new String[0];
String[] arr2 = names.toArray(arr);
6.5.2 数组转集合
1. 数组转成的集合对象,不能修改长度。
2. 如果想要进行增删操作,需要放入另外一个空集合里。
调用Arrays.asList方法,将数组转成集合。
String[] names = new String[5];
names[0] = "michael";
names[1] = "tom";
names[2] = "lucy";
names[3] = "lily";
names[4] = "john";
List<String> list = Arrays.asList(names);
/**
* 向集合中添加新元素,张三和李四,然后将张三和第一个元素交换位置。
*/
List<String> list1 = new ArrayList<>(list);
list1.add("张三");
list1.add("李四");
for (int i = 0; i < list1.size(); i++) {
if (list1.get(i).equals("张三")) {
Collections.swap(list1,0,i);
}
}
七、Map接口
7.1 Map简介
1. Map接口,是集合框架中的另一个父接口。
2. Map存储的数据的特点: 一对一的关系映射, 称之为Key-Value-Pair(键值对)。
3. Map接口最常用的两个子类,是HashMap和TreeMap。
- HashMap: 底层使用了Hash表和红黑树的数据结构(jdk1.8以前使用的是hash表+单向链表)。
- TreeMap: 底层使用了二叉树。
4. Map的Key不能重复,但是可以为null。value可以重复。
5. Map的Key可以理解为是value的索引,总能通过一个Key找到一个具体的value。
6. Map里的元素也是无序的(在结构中的(存储)顺序和存入顺序无关)。
7. key和value必须是引用类型的数据。
7.2 常用方法
1. V put(Key key,Value value):添加元素,像Map集合添加一个键值对。
注意:key值可以为null,但是只能有一个。
小贴士:put方法,如果添加key相同,value不同的元素,新的value会覆盖掉原来的value。
//创建一个Map集合(散列表)
Map<String,Integer> map = new HashMap<>();
map.put("张三", 100);
map.put("李四", 98);
map.put("王五", 97);
map.put("赵六", 99);
System.out.println(map);
map.put("张三",60);
System.out.println(map);
map.put(null,0);
map.put(null,10);
System.out.println(map);
运行结果:
{李四=98, 张三=100, 王五=97, 赵六=99}
{李四=98, 张三=60, 王五=97, 赵六=99}
{null=10, 李四=98, 张三=60, 王五=97, 赵六=99}
2. V get(Object key):返回指定键所映射的值。
注意:如果指定键key不存在,返回null。
Integer score = map.get("张三");
System.out.println("张三的成绩:" + score);
//取出小八的成绩。因为key不存在,所以返回null。
Integer s1 = map.get("小八");
System.out.println("小八的成绩:"+s1);
运行结果:
张三的成绩:60
小八的成绩:null
3. boolean isEmpty():判断集合是否为空。
boolean a = map.isEmpty();
4. boolean containsKey (Object o):判断集合是否包含指定的key。
boolean b = map.containsKey("张三");
5. boolean containsValue(Object o):判断集合是否包含指定的Value。
boolean c = map.containsValue(96);
6. V remove(K k):通过key移除一个键值对。
course.remove("李四");
7. int size():查看Map集合里键值对的个数。
int count = map.size();
8. void clear():清空所有键值对。
map.clear();
7.3 Map的遍历
1)第一种方式:使用keySet():返回所有的key的Set集合形式。
Set<String> keys = scores.keySet();
for(String key:keys){
//通过key获取value
Integer value = scores.get(key);
System.out.println(key+"="+value);
}
2)第二种方式:使用entrySet(): 返回Entry对象的Set集合。
Entry: 是Map的内部类,Entry对象就是封装了一个键值对。
Set<Map.Entry<String,Integer>> es = scores.entrySet();
for(Map.Entry<String,Integer> entry:es){
//每一个entry都是一个键值对
//System.out.println(entry);
/**
* Entry类型提供了getKey()和getValue()方法
*/
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"="+value);
}
3)第三种方式:使用values():返回所有value的Collection集合形式。
Collection<Integer> values = scores.values();
for(Integer value:values){
System.out.println(value);
}
7.4 HashMap的实现原理
7.4.1 原理
HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。
图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。
7.4.2 装载因子及其HashMap优化
capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小。
initial capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量为16,也可以使用特定容量。
size:大小,当前散列表中存储数据的数量。
load factor:加载因子,默认值0.75也就是75%,当向散列表增加数据时,如果size/capacity的值大于loadfactor,则发生扩容并且重新散列(rebash)。
性能优化:加载因子较小时,散列查找性能会提高,但是也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash能提供性能。
7.5 HashMap与HashTable
HashMap和HashTable是Map接口的两个典型实现类。
区别:
1. HashTable是一个古老的Map实现类,不建议使用。
2. HashTable是一个线程安全Map实现类,但HashMap是线程不安全的。
3. HashTable不允许使用null作为key和value,而HashMap可以。
与 HashSet 集合不能保证元素的顺序的顺序一样,HashTable 、HashMap 也不能保证其中 key-value 对的顺序。
7.6 LinkedHashMap
LinkedHashMap是HashMap的子类。
使用链表维护了元素的插入顺序,迭代顺序与键值对的插入顺序一致。
7.7 TreeMap
1. 使用二叉树对key进行排序,来维护整个集合的键值对的顺序。
2. 默认升序。可以通过比较器进行自定义排序。
public static void main(String[] args) {
Comparator c1 = new Comparator<String>() {
public int compare(String o1, String o2) {
return -o1.compareTo(o2);//按照key的值降序
}
};
Map<String,Integer> map = new TreeMap<>(c1);
map.put("A", 100);
map.put("a", 90);
map.put("b", 80);
map.put("C", 90);
System.out.println(map);
}
运行结果:
{b=80, a=90, C=90, A=100}
7.8 Properties
1. 是HashTable的子类型,比较常用,一般用于加载配置文件里的KEY和VALUE。
2. 因为配置文件里都是字符串,因此properties里面的KEY和VALUE也都是String类型。
3. 该对象的key和value都不可以为null。
1)Properties的遍历
//创建一个配置文件属性对象
Properties prop = new Properties();
//添加几个key和value
prop.setProperty("url","jdbc:mysql://localhost:3306/mydb");
prop.setProperty("username","root");
prop.setProperty("password","root");
prop.setProperty("driver","com.mysql.jdbc.Driver");
for (Map.Entry<Object,Object> entry : prop.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
2)getProperty(Key key)
通过指定的key,获取对应的value值,如果key不存在,返回null。
String url = prop.getProperty("url");
System.out.println("url的值是: " + url);
运行结果:
url的值是: jdbc:mysql://localhost:3306/mydb
3)getProperty(Key key,Value defaultValue)
通过指定的key,获取对应的value值,如果该key不存在,就返回默认值defaultValue。
String p1 = prop.getProperty("school","哈尔滨工业大学");
System.out.println(p1);
运行结果:
哈尔滨工业大学