16-集合
什么是集合
定义:
集合一个存储对象的容器,面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,存储对象,那么集合是存储对象最常用的方式之一。
集合的出现就是为了持有对象。集合中可以存储任意类型的对象,而且长度可变,在程序中有可能无法预知需要多个对象,那么如果用数组存储的话,长度不好定义,集合刚好解决了这个问题。
集合和数组的区别
1、集合和数组都是容器
2、数组的长度是固定的,集合的长度是可变的
3、数组中只能存放单一的数据类型,而集合可以存储不同类型的对象【集合只能存储对象】。
4、数组对于CRUD(增删改查),操作复杂,但是集合相对方便很多。
集合的分类
Collection:单列集合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcRLICZw-1626698029329)(images/29.jpg)]
——Collection:单列集合
——List:有顺序地存储、可重复,是列表类集合
——ArrayList:数组列表实现,特性:查找快、增加删除慢。
原因:由于是数组实现,在增加和删除操作时会牵扯到数组扩容,以及拷贝元素,所以慢;但是数组可以按照索引查询,所以查询快。
——LinkedList:链表类型实现,特性:增加删除快,查找慢。
原因:由于链表实现,增加时只要让前一个元素记住自己就可以了,删除时让前一个记住后一个元素,这样增加删除就快,但是查询时需要一个一个遍历,所以查询效率会比较低。
——Vector:和ArrayList原理相同,实现和操作方式也相同,但线程安全。
——Set:无顺序存储、不可重复的
——HashSet:以哈希值存储,但是不保留顺序,并且它可以去掉重复元素。
——TreeSet:以树型结构存储,它会将元素排序,但是效率没有HashSet高。
——LinkedHashSet:既可以保留存储顺序,又可以过滤掉重复元素。
Map:键值对(key-value)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbtCKlAu-1626698029331)(images/30.jpg)]
——Map:键值对(key、value)
——HashMap:
——LinkedHashMap:
——TreeMap:
——HashTable:
**PS:**为什么会出现这么多个集合容器:是因为每一个容器对于数据的存储方式不同,而这种存储方式我们将其称为【数据结构】。
- List、Set元素在添加、修改是都会把类型转为Object,所以获取出来的类型也是Object,如果要操作获取出来的数据,需要进行一次类型强 转。
- Map中的key、value在添加、修改是都会把类型转为Object,所以获取出来的类型也是Object,如果要操作获取出来的数据,需要进行一次类型强转。
不同类型的集合的使用场景
Collection | 需要保留若干个对象的时候,使用该Connection |
---|---|
List | 需要保留存储顺序,并且保留重复元素,使用List |
—如查询较多,使用ArrayList | |
—如增删较多,使用LinkedList | |
—如要线程安全,使用Vector | |
Set | 不需要保留存储顺序,并且需要去除重复元素,使用Set |
—如需将元素排序,使用TreeSet | |
—如不需将元素排序,使用HashSet | |
—如需保留存储顺序,又要过滤重复元素,使用LinkedHashSet | |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSU9wWjp-1626698029332)(images/28.jpg)]
Collection共有的操作方法
操作名 | 方法名 | 方法描述 |
---|---|---|
增加 | add() | 将指定的对象存储到容器中,add方法的参数类型是object类型,便于接收任意对象 |
addAll() | 将指定集合的元素添加到调用该方法的集合中 | |
删除 | remove() | 将指定的对象从集合中移除 |
removeAll() | 将指定的集合中所有元素移除 | |
clear() | 清空集合中所有元素 | |
查询 | isEmpty() | 判断集合是否为空 |
contains() | 判断集合中是否包含指定的对象 | |
containsAll() | 判断集合中是否包含指定集合 | |
size() | 返回集合容器的大小 | |
转换为数组 | toArray() | 集合转换成数组 |
package package02;
import java.util.ArrayList;
import java.util.Collection;
/**
* @Description java集合:集合共性的操作方法
* @Author WENG Jun
* @Date 2021/7/12 - 15:33
*/
public class CollectionsTest {
public static void main(String[] args) {
Collection list = new ArrayList();
//add():将指定的对象存储到容器中
list.add("java");
list.add("html");
list.add("css");
System.out.println(list);//[java, html, css]
//addAll():将指定集合的元素添加到调用该方法的集合中
Collection list2 = new ArrayList();
list2.add("spring");
list2.addAll(list);
list2.add("spring boot");
System.out.println(list2);//[spring, java, html, css, spring boot]
//remove()将指定的对象从集合中移除
System.out.println(list2.remove("spring boot"));//true
System.out.println(list2);//[spring, java, html, css]
//removeAll()将指定的集合中所有元素移除
list2.removeAll(list);
System.out.println(list2);//[spring]
//clear()清空集合中所有元素
list2.clear();
System.out.println(list2);//[]
//isEmpty()判断集合是否为空
System.out.println(list2.isEmpty());//true
//size()返回集合容器的大小
System.out.println(list.size());//3
//contains()判断集合中是否包含指定的对象
System.out.println(list.contains("java"));//true
//containsAll()判断集合中是否包含指定集合
Collection list3 = new ArrayList();
// list3.add("hello");
list3.add("java");
System.out.println(list.containsAll(list·3));//true
}
}
List接口
List集合特有的方法
操作名 | 方法名 | 方法描述 |
---|---|---|
增加 | void add(int index,E element) | 在指定位置添加元素 |
boolean addAll(int index, Collection c) | 在指定位置添加集合 | |
删除 | E remove(int index) | 删除指定位置元素 |
修改 | E set(int index ,E element) | 返回的是需要替换的集合的元素,返回被操作的对象 |
查找 | E get(int index) | 根据元素索引查找,要注意角标越界,返回被操作的对象 |
int indexOf(Object obj) | 根据对象做匹配,如找到,返回索引,否则返回-1 | |
int lastIndexOf(Object obj) | 最后一个对象的位置 |
package package02;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Description List特有的方法、迭代器
* @Author WENG Jun
* @Date 2021/7/12 - 16:17
*/
public class ListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("java编程思想");
list.add("java核心技术");
list.add("java程序设计");
//void add(int index,E element)在指定位置添加元素
list.add(0, "格林童话");
System.out.println(list);//[格林童话, java编程思想, java核心技术, java程序设计]
ArrayList list2 = new ArrayList();
list2.add("西游记");
list2.add("水浒传");
list2.add("三国演义");
list2.add("红楼梦");
//boolean addAll(int index, Collection c)在指定位置添加集合
System.out.println(list.addAll(1, list2));//true
System.out.println(list);//[格林童话, 西游记, 水浒传, 三国演义, 红楼梦, java编程思想, java核心技术, java程序设计]
//E remove(int index)删除指定位置元素
list.remove(4);//true
System.out.println(list);//[格林童话, 西游记, 水浒传, 三国演义, java编程思想, java核心技术, java程序设计]
list.remove("三国演义");
System.out.println(list);//[格林童话, 西游记, 水浒传, java编程思想, java核心技术, java程序设计]
//E set(int index ,E element)返回的是需要替换的集合的元素
Object o = list.set(0, "深入理解JVM虚拟机");
System.out.println(o);//格林童话 返回被替换掉的元素
System.out.println(list);//[深入理解JVM虚拟机, 西游记, 水浒传, java编程思想, java核心技术, java程序设计]
//E get(int index)根据元素索引查找,要注意角标越界,返回被操作的对象
// Object o1=list2.get(10);//角标越界
Object o1 = list2.get(0);//角标越界
System.out.println(o1);//西游记
//int indexOf(Object obj)根据对象做匹配,如找到,返回索引,否则返回-1
System.out.println(list2.indexOf("西游记1"));//找不到,返回-1
//int lastIndexOf(Object obj)最后一个对象的位置
System.out.println(list.lastIndexOf("java核心技术"));//4
//迭代器
Iterator it = list2.iterator();
while (it.hasNext()) {
String str = (String) it.next();
System.out.print(str);//西游记水浒传三国演义红楼梦
}
}
}
ArrayList的实现原理
**原因:**因为ArrayList底层维护的是一个Object[]数组,用于存储对象,该数组的默认长度为10,也可以手动更改这个数组的长度,在初始化的时候:new ArrayList(20);
不设置长度没有关系,因为默认或指定的容量不足的时候,容量自动增长到原来的1.5倍((oldCapacity*3)/2+1)。
总结:已知ArrayList是数组实现,在增加和删除时会牵扯到数组扩容,以及拷贝元素,所以增删效率低下,但数组按照下标索引查找,所以查找时效率高。
LinkedList实现原理
原因:底层采用双向链表形式,在内存中的地址不是连续的,它是由上一个元素去记住下一个元素,所以每一个元素保存的有下一个元素的位置,虽然也有下标索引,查找时需要从头往下找,这个方式显然没有数组查找快,但是,链表在插入一个新元素的时候,只需要让前一个元素记住这个新元素,让新元素记住下一个元素就可以,所以增删很快。
操作名 | 方法名 | 方法描述 |
---|---|---|
添加 | addFirst(E element) | 添加在首个位置 |
addLast(E element) | 追加到末尾 | |
删除 | removeFirst() | 删除首个元素 |
removeLast() | 删除末尾元素 | |
查找 | getFirst() | 得到第一个元素 |
getLast() | 得到最后一个元素 | |
逆序输出迭代器对象 | descendingIterator() | 返回逆序的迭代器对象 |
package package02;
import java.util.Iterator;
import java.util.LinkedList;
/**
* @Description LinkedList特有方法
* @Author WENG Jun
* @Date 2021/7/12 - 16:17
*/
public class LinkedListTest {
public static void main(String[] args) {
LinkedList list2 = new LinkedList();
list2.add("java编程思想");
list2.add("java核心技术");
list2.add("java程序设计");
//removeFirst()删除首个元素
list2.removeFirst();
//removeLast()删除末尾元素
list2.removeLast();
//addFirst(E element)添加在首个位置
list2.addFirst("你好,生活!");
//addLast(E element)追加到末尾
list2.addLast("Hello Java!");
//getFirst()得到第一个元素
System.out.println(list2.getFirst());//你好,生活!
//getLast()得到最后一个元素
System.out.println(list2.getLast());//Hello Java!
//逆序迭代
Iterator it = list2.descendingIterator();
//迭代器遍历输出
while (it.hasNext()) {
System.out.print(it.next() + " ");//Hello Java! java核心技术 你好,生活!
}
}
}
Vector的实现原理
Vector:其就是一个线程安全的ArrayList。
ArrayList:单线程,效率高,但不安全,因为每一个用户进来都可以去抢。
Vector:多线程,线程安全,但效率低,因为需要等待。
操作名 | 方法名 | 方法描述 |
---|---|---|
添加 | void addElement(Object obj) | 在集合的末尾添加元素 |
查找 | E elementAt(int index) | 返回指定下标的元素 |
Enumeration elements() | 返回集合中的所有元素,封装到Enum对象中去 | |
Enumeration接口 | ||
boolean hasMoreElements() | 测试此枚举类是否包含元素 | |
E nextElement() | 如果此枚举对象至少还有一个可提供的元素,则返回此元素 |
package package02;
import java.util.Enumeration;
import java.util.Vector;
/**
* @Description Vector
* @Author WENG Jun
* @Date 2021/7/12 - 17:25
*/
public class VectorTest {
public static void main(String[] args) {
Vector v = new Vector();
v.addElement("aaa");
v.addElement("bbb");
v.addElement("ccc");
System.out.println(v);//[aaa, bbb, ccc]
//E elementAt(int index)返回指定下标的元素
System.out.println(v.elementAt(2));//ccc
//Enumeration接口:
//Enumeration elements()返回集合中的所有元素,封装到Enum对象中去
Enumeration ens = v.elements();
//boolean hasMoreElements()测试此枚举类是否包含元素
//迭代器遍历输出
while (ens.hasMoreElements()) {
//E nextElement()如果此枚举对象至少还有一个可提供的元素,则返回此元素
System.out.print(ens.nextElement() + " ");
}
//aaa bbb ccc
}
}
迭代器Iterator
描述:
为了方便地处理集合中的元素,java出现了一个对象,该对象专门用于处理集合中的元素,例如删除和获取集合中的元素,这个对象就是迭代器Iterator。
作用:
因为它更加符合面向对象的思想,就是专业的对象做专业的事情,那么迭代器就是专门取出集合元素的对象,但是该对象比较特殊,不能直接创建对象(不能通过new来实现),该对象是以内部类的形式存在于每个集合类的内部。
如何获取迭代器?
Collection接口中定义了获取迭代器的方法(Iterator();),所以所有的Collection体系的接口都可以获取自身的迭代器。
由于每一个容器都有取出元素的功能,但这些功能定义都一样,只不过实现的具体方法不相同(因为每一个容器的数据结构不一样),所以对于共性的取出功能进行了抽取,从而实现了Iterator接口,让每一个容器都在其内部对该接口进行了内部类的处理,也就是说将其取出的方法细节做了封装。
Iterable:
jdk1.5之后添加的新接口,也是Collection的父接口,实现了Iterable的类就是可以迭代的,并且增加了一个叫做支持增强for循环。该接口只有一个方法,就是获取迭代器的方法iterator(),可以获取每一个容器自身的迭代器。Collection集合容器在5.0之后又进行了一次抽取,它将获取容器迭代器的方法放到了Iterable接口中。
forEach
描述:
是一种简洁而有趣的迭代集合的方式,那么通过它的名字我们就能看出,for循环,Each每一个,那么我们看到forEach一般来说会和集合或者数组一起使用,但不是所有的forEach都适合,(forEach能实现的,for都可以实现,反之,不一定)
它也是Iterable的一个接口:
package package01;
import java.util.ArrayList;
import java.util.List;
public class ForEachTest {
public static void main(String[] args) {
List str = new ArrayList();
str.add("张三");
str.add("李四");
str.add("王五");
//普通for循环
for (int i = 0; i < str.size(); i++) {
System.out.println(str.get(i));
}
//forEach增强for循环
for (Object obj : str) {
System.out.println(obj);
}
//lambod表达式
str.forEach(s -> {
System.out.println(s);
});
//lambod的精简写法
str.forEach(System.out::println);
}
}
迭代器接口的定义方法:
操作名 | 方法名 | 方法描述 |
---|---|---|
判断 | boolean hashNext(); | 判断集合中是否有元素,如有元素返回true,一般结合循环使用 |
获取 | E next(); | 返回迭代的下一个元素,请注意:如果没有下一个元素时,强行调用next,会抛出一个异常,NoSuchElementsException异常 |
删除 | void remove(); | 从迭代器指向的集合中移除迭代器返回的最后一个元素 |
List str = new ArrayList();
str.add("张三");
str.add("李四");
str.add("王五");
while循环方式:
//while循环方式
while (it1.hasNext()) {
String s = (String) it1.next();
System.out.println(s);
}
for循环方式:
//for循环方式
for (Iterator it2 = str.iterator(); it2.hasNext(); ) {
String s = (String) it2.next();
System.out.println(s);
}
remove:
//remove方法
while (it1.hasNext()) {
String s = (String) it1.next();
//删除李四
if (s.equals("李四")) {
it1.remove();//必须先取值在删除,否则报错
}
}
注意细节:
1.如果迭代器的指针已经指向了集合的末尾,那么再调用next(),会报NoSuchelementException异常;
2.如调用remove之前没有调用next方法,这个操作对于迭代器来说是不合法的,会报IllegalStateexception异常;
3.在对集合进行迭代的过程中,不允许出现迭代以外对元素的操作,因为这样会产生安全隐患,java会报异常,并发修改异常(ConcurrentModificationException),普通迭代器只支持在迭代过程中的删除动作
List专有的迭代器ListIterator
**描述:**该接口是继承了Iterable
操作名 | 方法名 | 方法描述 |
---|---|---|
添加 | add(E e); | 将指定的元素插入列表,该元素直接插入到next返回的下一个元素前面(如果有的话) |
修改 | void set(E o); | 用于指定元素替换next或者previous返回的最后一个元素 |
获取 | Object previous(); | 获取列表中的上一个元素(如果有的话) |
查询 | boolean hasPrevious(); | 逆向遍历列表,列表迭代器有多个元素时,返回true |
package package01;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorTest {
public static void main(String[] args) {
List str = new ArrayList();
str.add("张三");
str.add("李四");
str.add("王五");
ListIterator it = str.listIterator();
//普通写法
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("--------------------------");
//逆序写法
while (it.hasPrevious()){
System.out.println(it.previous());
}
System.out.println("--------------------------");
//替换李四,正序输出
while (it.hasNext()) {
String s= (String) it.next();
if (s.equals("李四")){
it.set("秀儿");
}
}
System.out.println(str);
System.out.println("--------------------------");
//添加
while (it.hasNext()) {
String s= (String) it.next();
if (s.equals("李四")){
it.add("秀儿");
}
}
}
}
Iterator与ListIterator主要区别:
- 都有hasNext()、next()方法,可以实现顺序遍历。但ListIterator有boolean hasPrevious()、Object previous() 可以实现逆向遍历;
- ListIterator可以定位当前元素的索引尾椎,方法int nextIndex()、int previousIndex(),Iterator无此功能;
- ListIterator有add()方法,可以向List中插入元素,Iterator无此方法;
- 都可以删除元素,当ListIterator可以用 set(Object e) 修改元素对象,因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。
Set接口
描述
它注重的是独一无二的性质,该体系集合可以知道某个值是否已经存在于集合中,不会存储重复的元素,并且它也是存储无序(存入和取出的顺序不相同)的元素。
Set为什么不能存储重复的数据?
是因为对象的相等性,因为如果引用到堆上面,是同一个对象的话,那么它的两个引用是相等的。
首先会去比两个对象的hashCode值,比较的对象所有的类如果没有覆盖Object的hashCode()方法的话,那么hashCode()会返回每个对象特有的序号(java依据对象的内存地址值计算出次序号)。
也就是说,在不重写hashCode()前提下,两个对象的hashCode值是不可能相等的(极小概率可能出现相等)。
再用equals方法比较,如果两个对象的地址值也不相同,那么Set才会将值放入到我们的集合中。
Set集合中没有特有的方法,直接继承它的父类Collection。
HashSet
可以帮我们去除存储的数据中重复的元素,但是不能排序。
HashSet的底层是HashMap
package package02;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/*
【Set接口】
----Set接口:存储无序的、不可重复的数据------>高中讲的"集合"
----HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值
----LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加顺序输出
----TreeSet:可以按照添加对象的指定属性,进行排序
1.Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
2.要求:向Set中添加数据,其所在的类一定要重写hashCode()和equals()方法。
重写hashCode()和equals()方法尽可能保持一致性:相等的对象必须具有相等的散列码。
重写两个方法的小技巧:直接使用编译器生成的方法,哈哈哈哈!
*/
public class SetTest {
/*
一、Set:存储无序的、不可重复的数据
(以HashSet为例):
1.无序:不等于随机性,存储数据时的顺序不是添加的顺序,根据数据的Hash值决定位置
2.不可重复:保证添加的元素按照equals()判断时,不能返回true
即相同的元素只能出现一次,先比较Hash值在比较equals,两个都相同则不能添加
二、添加元素的过程:
(以HashSet为例):
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组的此位置上是否有元素:
如此位置上没有其他元素,则元素a直接添加成功。 ----情况1
如此位置有其他的元素b(或以链表形式存在多个个元素),则比较元素b和元素a的哈希值:
如hash值不相同,则元素a添加成功。 ----情况2
如hash值相同,进而还需要调用元素a所在类的equals()方法:
equals()返回false,元素a添加成功。 ----情况3
equals()返回true,元素a添加失败
注意:对于添加成功的情况2、3:
元素a与已经存在指定的索引位置上的数据以链表方式存储:
JDK7:元素a放在数组中,指向原来的元素 b<---a
JDk8:原来的元素在数组中,指向元素a a<---b
总结:7上8下
HashSet底层:数组+链表的结构
*/
public static void main(String[] args) {
Set set = new HashSet();
// set.add("张三");
// set.add("李四");
// set.add("王五");
//
// //取集合的大小
// System.out.println(set.size());
System.out.println(set.get(2));//没有get方法
//
// //不能添加重复元素
// System.out.println(set.add("张三"));//false
//
//
// //forEach遍历输出
// for (Object o : set) {
// System.out.println(o);
// }
//
// System.out.println();
//
// //迭代器遍历输出
// Iterator it = set.iterator();
// while (it.hasNext()) {
// System.out.println(it.next());
// }
set.add(new Person("张三", 18));
set.add(new Person("李四", 20));
for (Object o : set) {
System.out.println(o);
}
boolean flag = set.add(new Person("张三", 18));//不能添加重复的元素
System.out.println(flag);//false
}
}
TreeSet
是以树形结构作为排序方式,红黑树,红黑树是一种特定类型的二叉树
红黑树的算法规则:左小右大
遵循的原则:
1.存入的元素自身具有比较性(自然排序)
TreeSet ts = new TreeSet();
ts.add("CC");
ts.add("AA");
ts.add("DD");
ts.add("BB");
System.out.println(ts);//[AA, BB, CC, DD]
为什么使用TreeSet存入字符串是按照升序的顺序排列的呢?
因为TreeSet字符串实现了一个接口,这个接口叫做Comparable接口,重写了该接口的compareTo()方法,所以String对于该操作具有了比较性,同理,例如自定义的对象(Person、Order等类的实例),就需要实现该接口,也就是让自定义对象具有比较规则。
2.让存入的元素自定义比较规则(定制排序)
注意:在重写compareTo()和compare()方法时,必须要明确比较条件相等时,要比较的次要条件(假设:姓名和年龄一致的人为相同一个人,如果按照此方式,那么无法进行处理,不能直接return 0,所以就需要使用次要条件来判断)。
//重写compareTo()方法
@Override
public int compareTo(Object o) {
Person p = (Person) o;
//先比姓名
if (this.name.compareTo(p.name) == 0) {
//再比年龄
if (this.age == p.age) {
// 再比sid
if (this.sid.equals(p.sid)) {
//全部相等返回0,说明是同一个对象
return 0;
}
}
}
//否则返回-1,说明不是同一个对象
return -1;
}
共性的问题:
1、compareTo和equals的区别:
equals比较的是地址值,返回true和false;
compareTo()返回的是三个值,1,0,-1,是根据编码的数值求取差值,能够确定两个String在字典顺序上的前后性,根据这个排列,能够将这个差值返回回去。所以equals通常用在对象的比较,而compareTo用在String类型的比较,也就是,例如:
value1.compareTo(value2),当value1不是String类型时,会报错。
总结:
(1)**==:**基本上用于基本数据类型的比较,8个基本数据类型。
(2)**equals:**引用数据类型的比较(除了上面的8个),包括这8个的包装类、Object的子类、自定义类型 ,它比较的时地址值。
(3)**compareTo:**一般用于比较两个字符串并且得到顺序,按照字典顺序比较,该比较的是基于字符串和各个字符的Unicode编码值。
【练习】:使用TreeSet做一个将字符串排序的练习
例如:String str=”9 10 15 2 3 8“;
思路:
(1)将字符串切割,根据空格切割,切出来一个String类型的字符数组。
(2)for循环遍历,把数组中的元素添加到TreeSet中(需要转换成包装类)
(3)输出TreeSet。
package package02;
import java.util.TreeSet;
public class TreeSetExer {
public static void main(String[] args) {
String str = "9 10 15 2 3 8";
String[] s = str.split(" ");
TreeSet ts = new TreeSet();
for (int i = 0; i < s.length; i++) {
ts.add(Integer.parseInt(s[i]));
}
System.out.println(ts);
// ts.forEach(System.out::println);
}
}
LinkedHashSet
链表实现,可以保存存储时的顺序,也就是说,它是Set当中唯一的一个能保证怎么存就能么取的集合对象。
由于它也是HashSet的子类,所以也可以可以保证元素的唯一性,这个和HashSet原理相同。
单一列表Collection的总结
1.看到Array,就要想到下标
2.看到Linked,就要想到链表,first,last
3.看到Hash,就要想到hashCode和equals
4.看到Tree就要想到两个接口,comparable和comparator
Map接口
描述:
Map是一个依照键(key)和值(value)的形式来存储元素数据的容器。key很像一个下标,它可以存储任意类型的对象,不能有重复的key,每一个key都有一个对印的value,一个key和一个value构成了Map集合中的一个元素。
Map中的元素:
key/value,一般用对象作为键,一般也用对象作为值,键不可以重复,值可以重复。
(1)Map和Collection在集合框架中是并列存在的,但是Map不可以直接用循环来解析的。
(2)Collection是单列集合,Map是双列集合。
(3)Collection根据数据结构的不同,有些可以重复,有些不能重复,Map集合中的键要保证唯一。
(4)如果要取出Map当中的元素,是无法直接取出的,需要先转换成Set集合,再通过Set集合大方式,取出元素。
(5)Map一次存入一对元素,Collection一次存入一个元素。
作用:
interface Map<key,value>
key——映射所维护的键类型(通过key可以找到value)
value——映射的值类型
Map分类
(1)HashMap:采用哈希表实现,并且无序,线程不同步(线程不安全),可以存入null值
——LinkedHashMap:这个子类基于哈希表,又融入了链表,可以对Map集合进行增删,提高效率。
(2)TreeMap:对于键可以排序,底层是二叉树的数据结构,可以对Map集合中的键进行排序,需要使用comparable或者comparator进行比较排序,并且保持唯一性。
(3)HashTable:底层也是哈希表数据结构,线程是同步的(线程安全),但是不可以存入null值,效率较低。
Map共有的方法
操作名 | 方法名 | 方法描述 |
---|---|---|
添加 | put(key,value); | 可以赋值相同的key,但是添加的value值会覆盖前面value值(直接覆盖掉原有的值),也就是说,put方法不仅可以添加,还可以修改(覆盖) |
putAll(Map<key,value>); | 从指定映射中将所有映射关系复制到此映射中 | |
删除 | remove(); | 删除关联对象,需要指定key对象 |
clear(); | 清空集合对象 | |
获取 | value get(key); | 通过key获取value,也可以用于判断键是否存在的情况,当指定的键不存在时,返回的是null |
int size(); | 获取集合的长度 | |
判断 | boolean isEmpty(); | 判断长度是否小于等于0,返回true或者false |
containsKey(Object key); | 判断集合中是否包含指定的key | |
containsValue(Object value); | 判断集合中是否包含指定的value |
package package03;
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map m1 = new HashMap();
//添加put(key,value);可以赋值相同的key,但是添加的value值会覆盖前面value值
m1.put("曹操", 20);
m1.put("吕布", 18);
m1.put("刘备", 16);
m1.put("董卓", 13);
System.out.println(m1);//{董卓=13, 吕布=18, 刘备=16, 曹操=20}
Map m2 = new HashMap();
m2.put("赵云", 23);
m2.put("关羽", 22);
//putAll(Map<key,value>);从指定映射中将所有映射关系复制到此映射中
m1.putAll(m2);
System.out.println(m1);//{董卓=13, 关羽=22, 吕布=18, 刘备=16, 曹操=20, 赵云=23}
// //remove();删除关联对象,需要指定key对象
// System.out.println(m1.remove("董卓"));
// System.out.println(m1);//{关羽=22, 吕布=18, 刘备=16, 曹操=20, 赵云=23}
//清空 clear() 清空集合对象
m2.clear();
System.out.println(m2);//{}
//value get(key);通过key获取value,也可以用于判断键是否存在的情况,当指定的键不存在时,返回的是null
System.out.println(m1.get("董卓11"));//null
//int size();获取集合的长度
System.out.println(m1.size());//6
//boolean isEmpty();判断长度是否小于等于0,返回true或者false
System.out.println(m1.isEmpty());//false
//containsKey(Object key);判断集合中是否包含指定的key
System.out.println(m1.containsKey("董卓"));//true
//containsValue(Object value);判断集合中是否包含指定的value
System.out.println(m1.containsValue(22));//true
}
}
遍历Map的方式
操作名 | 方法名 | 方法描述 |
---|---|---|
通过取key来获取元素 | SetkeySet(); | 返回所有的key对象的set集合,再通过get方法获取key对应的value值 |
获取所有的value值 | Collection(v) values(); | 返回Map集合中所有的value值,但是不能获取到key对象 |
Map.Entry对象【重点】 | Set<Map.Entry<k,v>> entrySet(); | 将map集合中的映射关系,打包成一个对象,通过Map.Entry对象来获取集合中的元素 |
//Map的遍历方式
//第一种方式(keySet):Set<k>keySet();返回所有的key对象的set集合,再通过get方法获取key对应的value值
Set key = m1.keySet();
for (Object o : key) {
System.out.println(o + "=" + m1.get(o));
}
//第二种方式(values):Collection(v) values();返回Map集合中所有的value值,但是不能获取到key对象
Collection values = m1.values();
for (Object o : values) {
System.out.println(o);
}
//第三种【重点】(entrySet):Set<Map.Entry<k,v>> entrySet();
// Set set = m1.entrySet();
// for (Object o : set) {
// System.out.println(o);
// }
Set<Map.Entry> entry = m1.entrySet();
Iterator<Map.Entry> it = entry.iterator();
while (it.hasNext()) {
Map.Entry me = it.next();
System.out.println(me.getKey() + " " + me.getValue());
}
TreeMap
TreeMap的排序,其实就是对集合中的键进行排序,是如何排序的?
1.元素自身具备比较性
和TreeSet的原理相同,这种方式就是自然排序也是默认排序
2.容器具备比较性
需要定义一个类实现Comparator接口,重写compare方法,并将该接口的子类实现对象将参数传递给TreeMap集合的构造方法。
【练习】使用HashMap和TreeMap分别解析下面的数据,要求按照原样输出,顺序不许乱(答案见下面的HashMap自定义排序)
tm.put("1","箱包");
tm.put("1.1","皮包");
tm.put("1.2","行李箱");
tm.put("2","衣帽");
tm.put("2.1","上衣");
tm.put("2.2","裤子");
HashMap自定义排序
package package03;
import java.util.*;
/*
【HashMap自定义排序】
练习:使用HashMap和TreeMap分别解析下面的数据,要求按照原样输出,顺序不许乱
tm.put("1","箱包");
tm.put("1.1","皮包");
tm.put("1.2","行李箱");
tm.put("2","衣帽");
tm.put("2.1","上衣");
tm.put("2.2","裤子");
*/
public class TreeMapExer implements Comparator<Map.Entry<String, String>> { //实现Comparator接口,类型为Map.Entry<String k,String v>
public static void main(String[] args) {
//创建TreeMap集合
TreeMap tm = new TreeMap();
tm.put("1", "箱包");
tm.put("1.1", "皮包");
tm.put("1.2", "行李箱");
tm.put("2", "衣帽");
tm.put("2.1", "上衣");
tm.put("2.2", "裤子");
System.out.println("TreeMap输出:");
System.out.println(tm);
//创建HashMap集合
HashMap hm = new HashMap();
hm.put("1", "箱包");
hm.put("1.1", "皮包");
hm.put("1.2", "行李箱");
hm.put("2", "衣帽");
hm.put("2.1", "上衣");
hm.put("2.2", "裤子");
System.out.println("HashMap原始输出:");
System.out.println(hm);
//获取HashMap集合的所有"映射"的Set集合,这里规范每个映射的类型为Map.Entry<String K, String V>
Set<Map.Entry<String, String>> entrySet = hm.entrySet();
新建ArrayList集合,用于获取Set集合的所有元素("映射"对象)(顺序与Set集合一样)
List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(entrySet);
//通过“比较器(TreeMapExer)”,对ArrayList进行排序
Collections.sort(list, new TreeMapExer());
System.out.println("HashMap自定义排序输出:");
//获取List集合的迭代器,Map.Entry< String K, String V>为迭代元素的类型
Iterator<Map.Entry<String, String>> iter = list.iterator();
while (iter.hasNext()) {
//Map.Entry对象接收迭代器中下一个元素
Map.Entry item = iter.next();
// System.out.print(item.getKey() + "=" + item.getValue()+" ");
System.out.println(item);
}
// for (Map.Entry<String, String> item : list) {
// System.out.print(item.getKey() + "=" + item.getValue()+" ");
// }
}
//重写compare()方法
//传入Map.Entry<String, String> 类型的对象作为形参
@Override
public int compare(Map.Entry<String, String> item1, Map.Entry<String, String> item2) {
//按照key值升序排列:String作为api提供的类,实现了Comparable的compareTo方法被设计成小于、等于、大于分别返回负数、零、正数
return item1.getKey().compareTo(item2.getKey());
// //按照key值降序排列
// return item2.getKey().compareTo(item1.getKey());
// //按照value值升序排列
// return item1.getValue().compareTo(item2.getValue());
// //按照value值降序排列
// return item2.getValue().compareTo(item1.getValue());
}
}
HashTable
和HashMap相似,HashTable的底层也是哈希表数据结构,线程是同步的(线程安全),但是不可以存入null值,效率较低。
package package03;
import java.util.Hashtable;
/*
HashTable
*/
public class HashTableTest {
public static void main(String[] args) {
Hashtable ht=new Hashtable();
ht.put("张三",20);
// ht.put("李四",null);//不能存null,会报NullPointerException
ht.put("王五",21);
System.out.println(ht);
System.out.println(ht.get("李四"));
}
}
集合的工具类
Connections工具类
操作名 | 方法名 | 方法描述 |
---|---|---|
二分查找 | int binarySearch(list ,key); | 对list集合进行二分查找,前提是该集合必须有序,如果集合元素不是有序的,那么返回的结果是混乱的。 |
排序 | sort(list); | 对list集合进行排序,它其实就是封装了容器对于对象的compareTo方法 |
sort(list, comparator); | 根据指定的比较器进行排序 | |
取大小 | max(Collection); | 取最大值 |
min(Collection); | 取最小值 | |
反转 | reverse(list); | 对list集合进行反转 |
交换 | swap(list,x,y); | 对list集合中的元素进行位置的交换 |
替换 | replaceAll(list ,old,new); | 对list集合进行元素的替换,如要替换的元素不存在,则原集合不变 |
package package01;
import java.util.*;
/*
集合的工具类
Connections工具类
* @Author WENG Jun
* @Date 2021/7/17 - 9:36
*/
public class CollectionsTest {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(i);
}
System.out.println("原始数据:" + list);
//打乱list中的数据
Collections.shuffle(list);
System.out.println("打乱后的数据:" + list);
//排序
Collections.sort(list);
System.out.println("排序后的结果:" + list);
//二分查找
System.out.println("二分查找20的索引:" + Collections.binarySearch(list, 20));//-11,查找不到
System.out.println("二分查找5的索引:" + Collections.binarySearch(list, 5));//5
//最大最小值
System.out.println("最大值:" + Collections.max(list));
System.out.println("最小值:" + Collections.min(list));
//反转list
// Collections.reverse(list);
// System.out.println("反转后:"+list);
//交换
Collections.swap(list,1,2);
// Collections.swap(list,1,100);//角标越界
System.out.println("交换索引1和索引2位置的元素后:"+list);
//替换
Collections.replaceAll(list,0,999);
System.out.println(Collections.replaceAll(list, 111, 000));//false,数据不存在,替换失败,不改变原有集合数据
System.out.println(list);
}
}
Arrays工具类
操作名 | 方法名 | 方法描述 |
---|---|---|
二分查找 | binarySearch(数组类型); | 前提是该集合一定要是有序,如果需要查找的集合元素不是有序的,那么返回的结果是混乱的 |
数组排序 | sort(数组); | 对数组进行排序 |
转换 | toString(数组); | 将数组转换成字符串 |
复制数组 | copyOf(原数组,新数组长度); | 将需要的数组复制到另一个数组中 |
复制部分数组 | copyOfrange(原数组,起始索引,结束索引); | 复制所需的部分数组,从起始索引到结束索引,但是不包括结束索引 |
转变 | asList(T[]); | 将任意类型的数组转换成List集合,一旦通过此方法转换的集合,不能使用增删的方法,因为该长度是固定的,如果使用会出现UnsupportOperationExceptionl,但是可以使用contain和indexOf方法。 |
package package01;
import java.util.*;
public class ArraysTest {
public static void main(String[] args) {
int[] a = {2, 3, 16, 9, 1, 12};
//Arrays.toString()
System.out.println(Arrays.toString(a));//[2, 3, 16, 9, 1, 12]
//Arrays.copyOf()
int[] b = Arrays.copyOf(a, 4);
System.out.println(Arrays.toString(b));//[2, 3, 16, 9]
//Arrays.copyOfRange()
int[] c = Arrays.copyOfRange(a, 1, 3);//从索引1到索引3,不包括3
System.out.println(Arrays.toString(c));//[3, 16]
//Arrays.asList()
List<int[]> list = Arrays.asList(a);
for (int[] ints : list) {
System.out.println(ints);//[I@1540e19d
}
}
}