集合
集合和数组既然都是容器,它们有啥区别呢?
- 数组的长度是固定的。集合的长度是可变的。
- 数组中可以存储基本数据类型值,也可以存储对象,而集合中只能存储对象
- 集合有更加丰富的API对数据进行处理
集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对。
Collection接口
Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如 Set 和 List、Queue)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
Collection
1、添加元素
(1)add(E obj):添加元素对象到当前集合中
(2)addAll(Collection<? extends E> other):添加other集合中的所有元素对象到当前集合中,即this = this ∪ other
2、删除元素
(1) boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
(2)boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素。即this = this - this ∩ coll
(3)boolean retainAll(Collection<?> coll):从当前集合中删除两个集合中不同的元素,使得当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集;
Collection<Integer> arrayList = new ArrayList<>();
//[2, 5, 7, 17, 19, 29, 31, 37, 41, 47, 59, 61, 67, 71, 79, 89, 97]
Collection<Integer> list = new ArrayList<>();
//[74, 45, 47, 93, 56, 43, 38, 73, 76, 17]
System.out.println("交集为");
arrayList.retainAll(list);
//[17, 47]
System.out.println(arrayList);
3、查询与获取元素
(1)boolean isEmpty():判断当前集合是否为空集合。
(2)boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素。
(3)boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”。
(4)int size():获取当前集合中实际存储的元素个数
(5)Object[] toArray():返回包含当前集合中所有元素的数组
Iterator迭代器
Iterator主要用于迭代访问(即遍历)
Collection中的元素,因此
Iterator`对象也被称为迭代器。
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法如下:
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。void remove() ;
迭代器删除元素。
@Test
public void test01(){
Collection<String> coll = new ArrayList();
coll.add("王安石");
coll.add("李白");
coll.add("杜甫");
Iterator iterator = coll.iterator();//获取迭代器对象
while(iterator.hasNext()) {//判断是否还有元素可迭代
System.out.println(iterator.next());//取出下一个元素
}
}
提示:在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
使用Iterator迭代器删除元素
java.util.Iterator迭代器中有一个方法:
void remove() ;
iterator.remove();
那么,既然Collection已经有remove(xx)方法了,为什么Iterator迭代器还要提供删除方法呢?
因为在JDK1.8之前Collection接口没有removeIf方法,即无法根据条件删除。
例如:要删除以下集合元素中的偶数
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIteratorRemove {
@Test
public void test01(){
Collection coll = new ArrayList();
coll.add(1);
coll.add(2);
coll.add(3);
coll.add(4);
// coll.remove(?)//没有removeIf方法无法实现删除“偶数”
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
Integer element = (Integer) iterator.next();
if(element%2 == 0){
iterator.remove();
}
}
System.out.println(coll);
}
}
Iterable接口与Iterator接口
Java5(JDK1.5)中增加了java.lang.Iterable接口,实现这个接口允许对象成为 "foreach" 语句的目标。 Java 5时Collection接口继承了java.lang.Iterable接口,因此Collection系列的集合就可以直接使用foreach循环遍历。
foreach循环的语法格式:
for(元素类型 元素名 : 数组名等){
}
//这里元素名就是一个临时变量,自己命名就可以
对于集合类型来说,foreach循环其实就是使用Iterator迭代器来完成元素的遍历的。
foreach只是一种语法糖,Java中的数组也支持这种语法糖。
代码示例:
package com.atguigu.api;
public class TestForeach {
public static void main(String[] args) {
int[] nums = {1,2,3,4,5};
for (int num : nums) {
System.out.println(num);
}
System.out.println("-----------------");
String[] names = {"张三","李四","王五"};
for (String name : names) {
System.out.println(name);
}
}
}
Iterator迭代器的快速失败(fail-fast)机制
如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情。因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制。
1、ConcurrentModificationException异常
package com.atguigu.iterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestConcurrentModificationException {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("hello");
coll.add("world");
coll.add("java");
coll.add("haha");
coll.add("mysql");
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
String str = (String)iterator.next();
if(str.contains("a")){
coll.remove(str);//foreach遍历集合过程中,调用集合的remove方法
}
}
/*for (Object o : coll) {
String str = (String) o;
if(str.contains("a")){
coll.remove(o);//foreach遍历集合过程中,调用集合的remove方法
}
}*/
}
}
2、modCount变量
那么迭代器如何实现快速失败(fail-fast)机制的呢?
- 在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。
- 当我们给集合添加和删除操作时,会导致modCount++。
- 然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:
int expectedModCount = modCount;
,并且在迭代器每次next()迭代元素时,都要检查expectedModCount != modCount
,如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。
List接口(数组+链表)
Collection 层次结构中的根接口。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如 Set 和 List、Queue)实现。
List接口特点:
- List集合所有的元素是以一种线性方式进行存储的,例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)
- 它是一个元素存取有序的集合。即元素的存入顺序和取出顺序有保证。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口中常用方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法。
List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
1、添加元素
- void add(int index, E ele) //指定索引位置添加元素
- boolean addAll(int index, Collection<? extends E> eles) //指定索引位置添加集合
2、获取元素
- E get(int index) //获取指定元素
- List subList(int fromIndex, int toIndex) //截取指定元素集合
3、获取元素索引
- int indexOf(Object obj) //获取指定元素第一次出现的下标
- int lastIndexOf(Object obj) //获取指定元素的最后一次出现的下标
4、删除元素
- E remove(int index)
5、替换元素
- E set(int index, E ele) //替换指定下标 《==的 ==》元素内容
import java.util.ArrayList;
import java.util.List;
public class TestListMethod {
public static void main(String[] args) {
// 创建List集合对象
List<String> list = new ArrayList<String>();
// 往 尾部添加 指定元素
list.add("图图");
list.add("小美");
list.add("不高兴");
System.out.println(list);
// add(int index,String s) 往指定位置添加
list.add(1,"没头脑");
System.out.println(list);
// String remove(int index) 删除指定位置元素 返回被删除元素
// 删除索引位置为2的元素
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
// String set(int index,String s)
// 在指定位置 进行 元素替代(改)
// 修改指定位置元素
list.set(0, "三毛");
System.out.println(list);
// String get(int index) 获取指定位置元素
// 跟size() 方法一起用 来 遍历的
for(int i = 0;i<list.size();i++){
System.out.println(list.get(i));
}
//还可以使用增强for
for (String string : list) {
System.out.println(string);
}
}
}
在JavaSE中List名称的类型有两个,一个是java.util.List集合接口,一个是java.awt.List图形界面的组件,别导错包了。
List集合的遍历方式
ListIterator
List 集合额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 列表迭代器对象, ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:
* void add():通过迭代器添加元素到对应集合
* void set(Object obj):通过迭代器替换正迭代的元素
* void remove():通过迭代器删除刚迭代的元素
* boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
* Object previous():返回列表中的前一个元素。
* int previousIndex():返回列表中的前一个元素的索引
* boolean hasNext()
* Object next()
* int nextIndex()
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("钱七");
//从指定位置往前遍历
System.out.println("从后往前遍历:");
ListIterator<String> listIterator = list.listIterator(list.size());
while(listIterator.hasPrevious()){
int previousIndex = listIterator.previousIndex();
String previous = listIterator.previous();
System.out.println(previousIndex + ":" + previous);
}
}
增强for
List<String> list;
@Before
public void test00(){
list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("陈一");
}
@Test
public void test02(){
for (String s : list) {
System.out.println("增强for = " + s);
}
}
迭代器
List<String> list;
@Before
public void test00(){
list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("陈一");
}
@Test
public void test01(){
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String ele = iterator.next();
System.out.println("迭代器遍历 = " + ele);
}
}
普通for
List<String> list;
@Before
public void test00(){
list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
list.add("陈一");
}
@Test
public void test03(){
for (int index = 0; index < list.size(); index++) {
String ele = list.get(index);
System.out.println("ele = " + ele);
}
}
List的实现类
底层动态数组实现ArrayList和Vector
动态数组特点
- 申请内存:一次申请一大段连续的空间,一旦申请到了,内存就固定了。
- 存储特点:所有数据存储在这个连续的空间中,数组中的每一个元素都是一个具体的数据(或对象),所有数据都紧密排布,不能有间隔
- 查询快增删慢
ArrayList是List接口的典型实现类,底层使用长度可变的数组实现,常用方法都来自Collection和List接口。
ArrayList因为底层使用了数组存储数据,所以具有**查询快,增、删慢**的特点。
比较Vector类底层也使用数组,但是线程安全,效率低,不推荐使用。
ArrayList与Vector的区别(数组)
相同点
-
都是List接口的孩子,有共同父类AbstractList
-
底层都是object[]
不同点
-
ArrayList是新版的动态数组,线程不安全,效率高,Vector是旧版的动态数组,线程安全,效率低。
-
动态数组的扩容机制不同
ArrayList扩容为原来的1.5倍,
Vector 如果capacityIncrement为0 扩容增加为原来的2倍,
否则容量为 旧的长度+capacityIncrement。
-
数组的初始化容量不同
Vector的内部数组的初始容量默认为10,
ArrayList在JDK1.6及之前的版本也是10,JDK1.7之后的版本ArrayList初始化为长度为0的空数组,之后在添加第一个元素时,再创建长度为10的数组。
-
Vector因为版本古老,支持Enumeration 迭代器。但是该迭代器不支持快速失败(线程安全)。而Iterator和ListIterator迭代器支持快速失败。如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
LinkedList(链表)
LinkedList是List接口的另一个常用实现类。
LinkedList底层存储数据使用链表结构(双向链表),特点:增删快,查询慢。
LinkedList底层结构:
@Test
public void test01(){
//底层是链表
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
}
@Test
public void test01(){
//底层是链表
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println("list = " + list);
//添加头尾
list.addFirst("李白");
list.addLast("杜甫");
System.out.println("list = " + list);
//获取头尾
System.out.println("list.getFirst() = " + list.getFirst());
System.out.println("list.getLast() = " + list.getLast());
//删除头尾
list.removeFirst();
list.removeLast();
System.out.println("list = " + list);
}
链表与动态数组的区别
动态数组底层的物理结构是数组,因此根据索引访问的效率非常高。但是非末尾位置的插入和删除效率不高,因为涉及到移动元素。另外添加操作时涉及到扩容问题,就会增加时空消耗。
链表底层的物理结构是链表,因此根据索引访问的效率不高,但是插入和删除不需要移动元素,只需要修改前后元素的指向关系即可,而且链表的添加不会涉及到扩容问题。
栈与队列
栈
堆栈是一种先进后出(FILO:first in last out)或后进先出(LIFO:last in first out)的结构。
栈只是逻辑结构,其物理结构可以是数组,也可以是链表,即栈结构分为顺序栈和链式栈。
Deque,名称 *deque* 是“double ended queue(双端队列)”的缩写,通常读为“deck”。此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(`null` 或 `false`,具体取决于操作)。
Deque接口的实现类有ArrayDeque和LinkedList,它们一个底层是使用数组实现,一个使用双向链表实现。
双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack
类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque
方法,如下表所示:
堆栈方法 | 等效 Deque 方法 |
---|---|
push(e) |
addFirst(e) |
pop() |
removeFirst() |
peek() |
peekFirst() |
体现栈结构的操作方法:
- peek()方法:查看栈顶元素,不弹出
- pop()方法:弹出栈
- push(E e)方法:压入栈
@Test
public void test02(){
//模拟栈操作
Deque<String> deque = new LinkedList<>();
//入栈
deque.push("张三");
deque.push("李四");
deque.push("王五");
deque.push("赵六");
//删除栈顶元素 弹栈
deque.pop();
deque.pop();
//获取栈顶元素
Object peek = deque.peek();
System.out.println("peek = " + peek);
}
队列
队列(Queue)是一种(但并非一定)先进先出(FIFO)的结构。
队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。无论使用哪种排序方式,队列的头 都是调用 remove() 或 poll() 所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。
抛出异常 | 返回特殊值 | |
---|---|---|
插入 | add(e) |
offer(e) |
移除 | remove() |
poll() |
检查 | element() |
peek() |
@Test
public void test01(){
//模拟队列
//多态
Queue<String> queue = new LinkedList<>();
//入队
queue.add("张三");
queue.add("李四");
queue.add("王五");
queue.add("赵六");
//出队
queue.remove();
queue.remove();
queue.remove();
queue.remove();
queue.poll();
//获取队头
Object peek = queue.peek();
System.out.println("peek = " + peek);
}
Set集合
Set接口是Collection的子接口,set接口没有提供额外的方法。但是比Collection
接口更加严格了。
-
Set 集合不允许包含相同的元素,即元素唯一。
-
Set集合相较于Collection没有新增方法
-
Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。
Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。
HashSet(哈希表)
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
java.util.HashSet
底层的实现其实是一个java.util.HashMap
支持,然后HashMap的底层物理实现是一个Hash表。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet 集合判断两个元素相等的标准:1.两个对象通过 hashCode() 方法比较相等,2.并且两个对象的 equals() 方法返回值也相等。因此,存储到HashSet的元素要重写hashCode和equals方法。
重写equals和hashCode方法时,要保证满足如下要求:
-
①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
-
②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
-
③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false
存储特点:
- 无序: 非添加顺序
- 唯一:元素不能重复
LinkedHashSet(哈希表+链表)
LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护了结点的前后添加顺序。java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
存储特点:
- 有序: 添加顺序
- 唯一: 元素不能重复
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("张三");
set.add("李四");
set.add("王五");
set.add("张三");
System.out.println("元素个数:" + set.size());
for (String name : set) {
System.out.println(name);
}
运行结果:
元素个数:3
张三
李四
王五
TreeSet(红黑树)
红黑树是一种相对平衡的二叉树,查询效率高于链表。(平衡二叉树追求绝对平衡,左右子树差值不能超过1)**
存储特点:
- 有序 :排序顺序
- 唯一: 元素不能重复
如果存储自定义类型数据 需要指定比较规则 Comprable Comprator
注意:
如果TreeSet中的元素要实现元素唯一和排序,那么这些元素就必须是可以进行比较的
要么元素本身实现Comparable接口,从而实现可比较;要么给TreeSet容器传入一个实现了Comparator接口的比较器,使其可以对存入的元素进行比
元素实现Comparable接口
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值为0。
@Test
public void test1(){
TreeSet<String> set = new TreeSet<>();
set.add("zhangsan"); //String它实现了java.lang.Comparable接口
set.add("lisi");
set.add("wangwu");
set.add("zhangsan");
System.out.println("元素个数:" + set.size());
for (String str : set) {
System.out.println(str);
}
}
TreeSet传入实现Comparator实现类
如果放到TreeSet中的元素的自然排序(Comparable)规则不符合当前排序需求时,或者元素的类型没有实现Comparable接口。那么在创建TreeSet时,可以单独指定一个Comparator的对象。使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
public class Student{
private int id;
private String name;
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//......这里省略了name属性的get/set
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
@Test
public void test3(){
TreeSet<Student> set = new TreeSet(new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
return o1.getId() - o2.getId();
}
});
set.add(new Student(3,"张三"));
set.add(new Student(1,"李四"));
set.add(new Student(2,"王五"));
set.add(new Student(3,"张三风"));
System.out.println("元素个数:" + set.size());
for (Student stu : set) {
System.out.println(stu);
}
}
TreeSet存取元素规则:
TreeSet存储元素时根据当前加入元素与已有元素比较的结果决定元素加入的位置,结果为负数,元素放左边,结果为正数,元素放右边,结果为0,则覆盖原值。
取元素时,采用中序遍历方式,即左中右顺序取出二叉树中元素。
Collection系列的集合小结
Collection:集合根接口,存储一组对象。
- List:接口,特点是,元素可重复,有序(存取顺序一致)
- ArrayList:底层结构为数组,查询快,增删慢,线程不安全
- LinkedList:底层结构为链表,查询慢,增删快
- Vector:底层结构为数组,线程安全,效率低,不推荐使用
- Set:接口,特点是,元素唯一
- HashSet:底层结构为Hash表,查询和增删效率都高
- TreeSet:底层结构为红黑树,查询效率高于链表,增删效率高于数组,元素实现排序
- LinkedHashSet:底层结构为hash表+链表,链表保证元素的有序
Map<key,value>
现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射。Java提供了专门的集合类用来存放这种对象关系的对象,即java.util.Map<K,V>
接口。
我们通过查看Map
接口描述,发现Map<K,V>
接口下的集合与Collection<E>
接口下的集合,它们存储数据的形式不同。
Collection
中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。Map
中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。Collection
中的集合称为单列集合,Map
中的集合称为s。- 需要注意的是,
Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值(这个值可以是单个值,也可以是个数组或集合值)。
Map常用方法
1、添加操作
- V put(K key,V value)
- void putAll(Map<? extends K,? extends V> m)
2、删除
- void clear()
- V remove(Object key)
3、元素查询的操作
- V get(Object key)
- boolean containsKey(Object key)
- boolean containsValue(Object value)
- boolean isEmpty()
4、元视图操作的方法:
- Set
keySet() //获取所有键名 - Collection
values() //获取所有值 - Set<Map.Entry<K,V>> entrySet() //获取所有键值队
5、其他方法
- int size()
public class MapDemo {
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("黄晓明", "杨颖");
map.put("文章", "马伊琍");
map.put("邓超", "孙俪");
System.out.println(map);
//String remove(String key)
System.out.println(map.remove("邓超"));
System.out.println(map);
// 想要查看 黄晓明的媳妇 是谁
System.out.println(map.get("黄晓明"));
System.out.println(map.get("邓超"));
}
}
tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
Map集合的遍历
Collection集合的遍历:(1)foreach(2)通过Iterator对象遍历
Map的遍历,不能支持foreach,因为Map接口没有继承java.lang.Iterable
(1)分开遍历:
- 单独遍历所有key
- 单独遍历所有value
public class TestMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("许仙", "白娘子");
map.put("董永", "七仙女");
map.put("牛郎", "织女");
map.put("许仙", "小青");
System.out.println("所有的key:");
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println(key);
}
System.out.println("所有的value:");
Collection<String> values = map.values();
for (String value : values) {
System.out.println(value);
}
}
}
(2)成对遍历:
- 遍历的是映射关系Map.Entry类型的对象,Map.Entry是Map接口的内部接口。每一种Map内部有自己的Map.Entry的实现类。在Map中存储数据,实际上是将Key---->value的数据存储在Map.Entry接口的实例中,再在Map集合中插入Map.Entry的实例化对象。
public class TestMap {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("许仙", "白娘子");
map.put("董永", "七仙女");
map.put("牛郎", "织女");
map.put("许仙", "小青");
System.out.println("所有的映射关系");
Set<Map.Entry<String,String>> entrySet = map.entrySet();
for (Map.Entry<String,String> entry : entrySet) {
// System.out.println(entry);
System.out.println(entry.getKey()+"->"+entry.getValue());
}
}
}
Map的实现类们
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中HashMap是 Map 接口使用频率最高的实现类。
常用API
- put(K key, V value): 将指定的键值对添加到HashMap中。
- remove(Object key): 从HashMap中移除指定键的键值对。
- get(Object key): 返回指定键对应的值。
- containsKey(Object key): 检查HashMap中是否包含指定的键。
- containsValue(Object value): 检查HashMap中是否包含指定的值。
- size(): 返回HashMap中键值对的数量。
HashMap和Hashtable的区别与联系
同:
1.都是Map接口的孩子 存储的都是键值对
2.底层都是哈希表
不同:
1.null值处理
Hashtable: key value都不能为 null
HashtMap: key value都可以为 null
2.线程安全问题
Hashtable: 线程安全 效率低
HashMap: 线程不安全 效率高
3.初始化容量的指定:
HashMap: 创建对象时没有长度 第一次添加数据时开辟空间 长度为16 或者 设置的初始化容量 必须是2的次 幂值
Hashtable: 创建对象时默认长度为11
4.扩容机制:
HashMap: 容量变为原来的2倍
hashtable: 旧的容量*2 + 1
HashMap(键:哈希表)
HashMap构造方法:
- HashMap();
- HashMap(int initialCapacity);//指定初始化容量
key: 无序、唯一
value:满足泛型即可
注意:
如果key相同 新的value 会替换旧的value
public static void main(String[] args) {
HashMap<String,Double> map = new HashMap<>();
map.put("张三", 10000.0);
//key相同,新的value会覆盖原来的value
//因为String重写了hashCode和equals方法
map.put("张三", 12000.0);
map.put("李四", 14000.0);
//HashMap支持key和value为null值
String name = null;
Double salary = null;
map.put(name, salary);
Set<Entry<String, Double>> entrySet = map.entrySet();
for (Entry<String, Double> entry : entrySet) {
System.out.println(entry);
}
}
LinkedHashMap(键:哈希表+链表)
LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
key: 有序:添加顺序、唯一
value:满足泛型即可
注意:
如果key相同 新的value 会替换旧的value
public static void main(String[] args) {
LinkedHashMap<String,Double> map = new LinkedHashMap<>();
map.put("张三", 10000.0);
//key相同,新的value会覆盖原来的value
//因为String重写了hashCode和equals方法
map.put("张三", 12000.0);
map.put("李四", 14000.0);
//HashMap支持key和value为null值
String name = null;
Double salary = null;
map.put(name, salary);
Set<Entry<String, Double>> entrySet = map.entrySet();
for (Entry<String, Double> entry : entrySet) {
System.out.println(entry);
}
}
TreeMap(红黑树)
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
新api
firstKey()
: 返回TreeMap
中的第一个键。lastKey()
: 返回TreeMap
中的最后一个键。
key: 有序: 排序顺序、唯一
value:满足泛型即可
注意:
如果key相同 新的value 会替换旧的value
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.junit.Test;
public class TestTreeMap {
@Test
public void test1() {
TreeMap<String,Integer> map = new TreeMap<>();
map.put("Jack", 11000);
map.put("Alice", 12000);
map.put("zhangsan", 13000);
map.put("baitao", 14000);
map.put("Lucy", 15000);
//String实现了Comparable接口,默认按照Unicode编码值排序
Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
System.out.println(entry);
}
}
@Test
public void test2() {
//指定定制比较器Comparator,按照Unicode编码值排序,但是忽略大小写
TreeMap<String,Integer> map = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
map.put("Jack", 11000);
map.put("Alice", 12000);
map.put("zhangsan", 13000);
map.put("baitao", 14000);
map.put("Lucy", 15000);
Set<Entry<String, Integer>> entrySet = map.entrySet();
for (Entry<String, Integer> entry : entrySet) {
System.out.println(entry);
}
}
}
Set集合与Map集合的关系
Set的内部实现其实是一个Map。即HashSet的内部实现是一个HashMap,TreeSet的内部实现是一个TreeMap,LinkedHashSet的内部实现是一个LinkedHashMap。
咱们存到Set中只有一个元素,又是怎么变成(key,value)的呢?
以HashSet中的源码为例:
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
原来是,把添加到Set中的元素作为内部实现map的key,然后用一个常量对象PRESENT对象,作为value。
这是因为Set的元素不可重复和Map的key不可重复有相同特点。Map有一个方法keySet()可以返回所有key。
HashMap储值原理:
/*
1.创建对象后 成员变量的变化
将负载因子赋值为0.75f
底层数组为null
2.首次添加 key为null
底层数组开辟空间长度为16
key为null 会添加到 数组内下标为0的位置
3.非首次添加
3.1 非首次添加 指定位置没有值
直接添加
3.2 非首次添加 指定位置有值 key 相同
喜新厌旧: 新的value 替换旧的value 并将旧的value返回
3.3 非首次添加 指定位置有值 key不同 hash相同
七上八下: jdk7 头插法
jdk8 尾差法
4.扩容和树化
扩容: ++size > threshold
阈值和容量 变为原来的2倍
树化:
节点数量>8
底层数组长度>=64
反树化
节点长度<=6
树化:
当链表的长度超过 8 且底层数组的长度至少为 64 时,链表会被转换为红黑树,以提高性能。
反树化:
当树化后的链表长度减少到 6 及以下,HashMap 会将红黑树结构转换回链表,以减少空间消耗。
*/
集合框架
Collections工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法:
-
public static
boolean addAll(Collection<? super T> c,T... elements)将所有指定元素添加到指定 collection 中。 -
public static
int binarySearch(List<? extends Comparable<? super T>> list,T key)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。 -
public static
int binarySearch(List<? extends T> list,T key,Comparator<? super T> c)在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。 -
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
-
public static
T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者 -
public static void reverse(List<?> list)反转指定列表List中元素的顺序。
-
public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
-
public static <T extends Comparable<? super T>> void sort(List
list)根据元素的自然顺序对指定 List 集合元素按升序排序 -
public static
void sort(List list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 -
public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数
-
public static
void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中 -
public static
boolean replaceAll(List list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值 -
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
-
Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。