Java编程基础倒数第三篇,感谢没有放弃的自己。
学习笔记参考书籍《Java编程基础》主编 张焕生。本书内容比较适合没有什么基础的入门小白,学完一章还有习题,比较适合初学者。
自律、积极、勤奋以及先从一个怎么样都不可能不会实现的小目标开始。
本文已收录于[ 专栏 ]
🌳《Java入门》系列🌳
前面[ 章节 ]:
🎉 Java必备基础十六——三万字总结输入与输出流
😀Java必备基础十五——万字长文总结异常处理基本知识点
👋Java必备基础十四——内部类详细知识点归纳
🤞Java必备基础十三——接口详细知识点归纳
😁Java必备基础十二——抽象类
✨Java必备基础十一——多态详解以及多态原则
🙌Java必备基础十——类的继承详解
😊Java必备基础九——包装类
😎Java必备基础八——Math类、Random类、日期时间类
👌Java必备基础七——字符串的基本常用方法
😜Java必备基础六——一维数组
👏Java必备基础五——类的封装
🤳Java必备基础四——成员变量和局部变量
👍 Java必备基础三——方法的声明与调用,递归
😘Java必备基础二——类和对象
👍Java必备基础一——Java语言基础
一、Java集合介绍
1.1 什么是集合?
Java是面向对象的语言,我们在编程的时候自然需要存储对象的容器,数组可以满足这个需求,但是数组初始化时长度是固定的,但是我们往往需要一个长度可变化的容器,因此,集合出现了。
Java集合在java.util
包中,这些集合可以看作是容器,用来存储、获取、操纵和传输具有相同性质的多个元素。
现实中的容器主要是添加对象、删除对象、清空对象等。衣柜里面的衣服,可以放入和取出,也可以有序摆放,以便快速的查找,Java集合也是如此,有些是方便插入和删除的,有些是为了方便查找数据。
1.2 集合类的继承关系
Collection
接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。
Map
是Java.util包中的另一个接口,它和Collection
接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。
Java中常用的集合类有List
集合、Set
集合和Map
集合。其中List
集合和Set
集合继承了Collection
接口(Java5后新增了队列Queue
),List
接口的实现类为ArrayList
,LinkedList
;
Map
接口有两个实现类,HashMap
,HashTable
,如下图:
1.3 集合和数组的区别
-
长度区别:数组是静态的,一个数组实例长度不可变,一旦创建了就无法改变容量;集合是可以动态扩展容量,集合长度可变,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求;在编写程序时不知道对象个数时,在空间不足的情况下要做到自动扩增容量,优先使用集合来实现,数组此时不适用
-
内容区别:集合不声明可存储元素类型,集合可存储不同类型元素;数组声明了它容纳的元素的类型且只可存放单一类型元素
-
元素区别:集合只能存储引用类型元素,数组可存储引用类型,也可存储基本类型
-
数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
下面就详细介绍各个集合类。
二、Collection接口和Iterator接口
2.1 Collection接口
上面说过,Collection
接口是List
接口、Set
接口和的父接口,该接口中定义的方法可用于Set
和List
集合。Collection
接口中定义了一些操作集合的方法。
2.1.1 Collection接口的常用方法
方法 | 说明 |
---|---|
boolean add(Object o) | 向集合中加入指定对象o,增加成功返回true,失败false |
boolean addAll(Collection c) | 将指定集合c内的所有元素添加到该集合内,增加成功返回true,否则返回false |
void clear() | 删除集合内的所有元素 |
boolean contains(Object o) | 判定集合内是否包含指定元素,是返回true |
boolean containsAll(Collection c) | 判定集合内是否包含集合c的所有元素 ,是返回true |
boolean isEmpty() | 判定是否为空集合,是返回true |
Iterator iterator() | 返回一个Iterator对象,可以用来遍历集合中的元素 |
boolean remove(Object o) | 从集合中删除指定元素 o,成功则返回true |
boolean removeAll(Collection c) | 删除该集合中包含集合c的所有元素,若删除了1个及以上的元素则返回true |
boolean retainAll(Collection c) | 删除该集合中除集合c中元素以外的元素,若调用该方法的集合元素改变了,则返回true |
int size() | 返回集合中元素的数目 |
Object[] toArray() | 返回一个数组,该数组中包括集合中的所有元素 |
2.1.2 Collection常用方法操作集合举例
public class Example8_1 {
public static void main(String[] args) {
Collection collection = new ArrayList();//将子类对象赋值给一个父类的引用变量
collection.add("1");
collection.add("2");//向集合中添加元素
System.out.println("collection集合的元素个数为:"+collection.size());
collection.clear();//删除所有元素
System.out.println("collection集合是否没有任何元素:"+collection.isEmpty());
collection.add("3");
collection.add("4");
System.out.println("collection集合中是否包含\"4\"字符串:"+collection.contains("4"));
Collection collection1 =new ArrayList();
collection1.add("3");
collection1.add("4");
collection1.add("c");
collection1.retainAll(collection);//从collection1删除collection中不包含的元素
System.out.println("collection1集合的元素:"+collection1);
System.out.println("collection集合是否完全包含collection1集合:"+collection.containsAll(collection1));
System.out.println("collection集合的元素:"+collection);
}
}
编译和运行后的结果为:
collection集合的元素个数为:2
collection集合是否没有任何元素:true
collection集合中是否包含"4"字符串:true
collection1集合的元素:[3, 4]
collection集合是否完全包含collection1集合:true
collection集合的元素:[3, 4]
2.2 Iterator接口
2.2.1 Iterator接口作用?
如果要遍历集合中的元素,在学数组时我们常用for
循环遍历数组中的元素,在集合中也可使用传统循环方法实现,此外,集合中也可以使用到Iterator
对象来遍历集合的数组,此方法更简单。
Iterator
通过遍历(迭代访问)集合中的元素来获取或删除某元素,仅用于遍历集合,可以应用于Set、List、Map
以及其子类,Iterator对象也被称为迭代器。
ListIterator
在Iterator
基础商进行了扩展,允许双向遍历列表,而Iterator
只能向后迭代,但ListIterator
只能用于List
及其子类。
2.2.2 Iterator接口的常用方法
方法 | 接口类型 | 说明 |
---|---|---|
boolean hasNext() | Iterator | 还有元素可以迭代返回true |
Object next() | Iterator | 返回下一个元素 |
void remove() | Iterator | 删除当前元素 |
void add(Object o) | ListIterator | 将指定元素o插入集合,该元素在下一次调用next()方法时被返回 |
boolean hasNext() | ListIterator | 存在下一个元素时返回true |
boolean hasPrevious() | ListIterator | 存在前一个元素时返回true |
Object next() | ListIterator | 返回列表中的下一个元素 |
Object previous() | ListIterator | 返回列表中的前一个元素 |
int nextIndex() | ListIterator | 返回列表下一个元素的下标,如果不存在下一个元素,则返回列表的大小 |
int previousIndex() | ListIterator | 返回列表前一个元素的下标,如果不存在前一个元素,则返回 -1 |
void remove() | ListIterator | 删除当前元素 |
void set(Object o) | ListIterator | 将o赋值给当前元素,即上一次调用next方法或previous方法后返回的元素 |
2.2.3 Iterator常用方法举例
1.利用Iterator进行集合元素的输出
class Example8_2 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
System.out.println("集合的内容为:");
Iterator iterator = arrayList.iterator();//iterator()方法返回一个Iterator对象
while (iterator.hasNext()){
Object o = iterator.next();//循环输出
System.out.println(o);
if(o.equals("b")){
iterator.remove();//将当前元素删除
}
}
System.out.println("删除b元素后,集合的内容为:"+arrayList);
}
}
编译和运行后的结果为:
集合的内容为:
a
b
c
删除b元素后,集合的内容为:[a, c]
2.利用Iterator进行反向输出
class Example8_3 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
ListIterator listIterator = arrayList.listIterator();//返回ListIterator对象
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
//将列表反向输出
while (listIterator.hasPrevious()){
Object o= listIterator.previous();
System.out.println(o);
}
}
}
编译和运行后的结果为:
a
b
c
c
b
a
此外,利用foreach
循环迭代访问集合中的元素也可以达到相同的效果:
for(Object obj : arrayList){
System.out.println(obj);
}
编译和运行后的结果为:
a
b
c
三、List集合
List
集合中元素保持一定的顺序,并且允许元素重复。List集合主要有两种实现类:ArrayList
类和LinkedList
类。
3.1 List接口
List
接口实现了Collection
接口,因此List接口有Collection接口提供的所有方法,而且List接口还提供了一些其他的方法。
3.1.1 List接口的常用方法
方法 | 说明 |
---|---|
boolean add(int index, Object element) | index为对象element要加入的位置,其他对象的索引位置相对后移1位,索引位置从0开始 |
E remove(int index) | 移出列表中指定位置元素 |
E set(int index, E element) | 用指定元素替换列表中指定位置元素 |
E get(int index) | 返回列表中指定位置元素 |
int indexOf(Object o) | 返回列表中指定元素位置的索引。存在多个时,返回第一个的索引位置,不存在返回-1 |
int lastIndexOf(Object o) | 返回列表中指定元素位置的索引。存在多个时,返回最后一个的索引位置,不存在返回-1 |
ListIterator<> listIterator() | 返回此列表元素的列表迭代器(按适当顺序) |
ListIterator<> listIterator(int index) | 返回此列表元素的列表迭代器(按适当顺序),从列表的指定位置开始 |
List <> subList(int fromIndex, int toIndex) | 返回一个指定区域的List集合对象,指定区域从索引fromIndex(包括)到索引toIndex(不包括) |
从表中可以看出,List
接口提供的适合于自身的常用方法均与索引有关,这是因为List集合为列表类型,以线性方式存储对象,可以通过对象的索引操作对象。
List
接口的常用实现类有ArrayList和LinkedList,在使用List集合时,通常情况下声明为List类型,实例化时根据实际情况的需要,实例化为ArrayList
或LinkedList
,例如:
List<String> l = new ArrayList<String>();// 利用ArrayList类实例化List集合
List<String> l2 = new LinkedList<String>();// 利用LinkedList类实例化List集合
3.1.2 List接口的常用方举例
1. List常用方法举例
class Example8_4{
public static void main(String[] args) {
String a = "A", b = "B", c = "C", d = "D", e = "E";
List<String> list = new LinkedList<String>();
list.add(a);
list.add(e);
list.add(d);
list.set(1, b);// 将索引位置为1的对象e修改为对象b
list.add(2, c);// 将对象c添加到索引位置为2的位置
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
在控制台将输出如下信息:
A
B
C
D
3.2 ArrayList类
3.2.1 ArrayList类的构造方法
方法 | 说明 |
---|---|
ArrayList() | 构造一个初始容量为10的空数组 |
ArrayList(Collection<? extends E> c) | 构造一个包含Collection的元素的数组,这些元素按照Collection迭代器返回他们的顺序排列的 |
ArrayList(int initialCapacity) | 构造一个具有指定初始容量的空数组 |
ArrayList
采用动态对象数组实现,默认构造方法创建一个初始容量为10的空数组,之后的扩容算法为:原来数组的大小+原来数组的一半。建议创建ArrayList
时给定一个初始容量。
3.2.2 ArrayList类的常用方法举例
1. 利用ArrayList实现了List接口,ArrayList可以使用List接口常用方法。
class Example8_5{
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
System.out.println("arrayList的元素为:"+arrayList);
arrayList.set(0,"c");//将索引为0的位置对象a修改为对象c
System.out.println("arrayList的元素为"+arrayList);
arrayList.add(1,"e");//将对象e添加到索引为1的位置
System.out.print("arrayList的元素为:");
for (int i=0;i<arrayList.size();i++){
System.out.print(arrayList.get(i));//for循环迭代arrayList集合元素
}
System.out.println("");
System.out.println("arrayList指定元素c位置的索引为"+arrayList.indexOf("c"));//返回列表中指定元素c位置的索引
System.out.println("arrayList指定元素c最后位置的索引为"+arrayList.lastIndexOf("c"));//返回列表中指定元素c最后位置的索引
System.out.println("arrayList的指定区域为"+arrayList.subList(1,2));//返回列表中指返回一个指定区域的List集合对象[1,2)
}
}
编译和运行后的结果为:
arrayList的元素为:[a, b, c]
arrayList的元素为[c, b, c]
arrayList的元素为:cebc
arrayList指定元素c位置的索引为0
arrayList指定元素c最后位置的索引为3
arrayList的指定区域为[e]
2. 利用ArrayList实现了List接口,ArrayList可以使用List接口常用方法。
class Person{
private String name;
private long id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
class Example8_6 {
public static void main(String[] args) {
List<Person> list = new ArrayList<Person>();
String[] names = {"李白","杜甫"};
long[] id ={20222,22222};
for (int i=0;i<names.length;i++){
Person person = new Person();
person.setName(names[i]);
person.setId(id[i]);
list.add(person);
}
for (int i=0;i<list.size();i++){
Person person = list.get(i);
System.out.println(person.getName()+person.getId());
}
}
}
编译和运行后的结果为:
李白20222
杜甫22222
3.3 LinkedList类
3.3.1 LinkedList类的常用方法
方法 | 说明 |
---|---|
void addFirst(E e) | 将指定元素插入此列表的开头 |
void addLast(E e) | 将指定元素插入此列表的结尾 |
E getFirst() | 返回列表开头的元素 |
E getLast() | 返回列表结尾的元素 |
E removeFirst() | 移除列表开头的元素 |
E removeLast() | 移除列表结尾的元素 |
3.3.2 LinkedList类的常用方法举例
1.使用上表中的方法编写程序
public class Example8_7 {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
linkedList.add("d");
//获得并输入列表开头的对象
System.out.println("列表开头元素为:"+linkedList.getFirst()+"列表结尾元素为:"+linkedList.getLast());
linkedList.addFirst("rr");//向列表开头添加一个对象
System.out.println("列表中所有元素:"+linkedList);
linkedList.removeLast();//移除列表结尾元素
System.out.println("列表结尾元素为:"+linkedList.getLast());//获取并输出列表结尾的对象
}
}
编译和运行后的结果为:
列表开头元素为:a列表结尾元素为:d
列表中所有元素:[rr, a, b, c, d]
列表结尾元素为:c
3.4 List与ArrayList、LinkedList的比较
首先我们知道,List
是一个接口,ArrayList
、LinkedList
是实现该接口的类。
纯属个人想法部分
我在学习时对比了几个例子,看到了几行代码,于是产生了一些问题:
List list; //正确 list=null;
List list=new List(); // 是错误的用法
ArrayList arrayList = new ArrayList();
List list = new ArrayList();
List<String> list = new ArrayList<String>();
首先List
是一个接口, 因此,List接口不能被构造,也就是我们说的不能创建实例对象,所以List list=new List()
是错误的用法;List list
写法正确,构造了一个List引用;
其次ArrayList
是List接口的一个实现类,ArrayList有自己的构造方法,所以ArrayList arrayList = new ArrayList()
是正确的写法,构造了一个ArrayList对象,此对象保留ArrayList的所有属性;
而List list = new ArrayList()
这句创建了一个ArrayList
的对象后向上转型为List
。此时它是一个List对象了,有些ArrayList有但是List没有的属性和方法,它就不能再用了。(想到多态的一些解释:接口和抽象类都不能被实例化,但是它们可以创建一个指向自己的对象引用,它们的实现类或子类就在充当这样的角色,这就是面向对象编程中多态的优势)
前面学到多态性的原则:list拥有List的所有属性和方法,不会拥有其实现类ArrayList的独有的属性和方法。 如果List
与ArrayList
中有相同的属性(如int i),有相同的方法(如void f()), 则list.i
是调用了List
中的i
, list.f()
是调用了ArrayList
中的f()
;
再进一步,List list = new ArrayList()
,为什么要用 List list = new ArrayList()
,而不用 ArrayList alist = new ArrayList()
呢? 问题就在于List接口有多个实现类,现在你用的是ArrayList,也许哪一天你需要换成其它的实现类,如 LinkedList或者Vector等等,这时你只要改变这一行就行了: list = new LinkedList();
,这样的形式使得list
这个对象可以有多种的存在形式,其它使用了list地方的代码根本不需要改动。假设开始用ArrayList alist = new ArrayList(),
那么对于使用了ArrayList实现类特有的方法和属性的地方全部都要修改。
List<String> list = new
ArrayList<String>()
用到了泛型和多态:多态上面已经解释,泛型下一节会写到。
3.5 ArrayList与LinkedList的比较
ArrayList
和数组类似,也是线性顺序排列,可理解成可变容量的数组。如果需要根据索引位置访问集合中的元素,那么线性顺序存储方法的效率较高,但如果向ArrayList中插入和删除元素,则数度较慢,因为在插入(删除)指定索引位置上的元素时,当前元素及之后的元素都要相应的向后移动一位(或之后的元素向前移动一位),从而影响对集合的操作效率。
LinkedList
在实现中采用链表数据结构,相比于ArrayList
,LinkedList
访问速度较慢,但插入和删除熟读块,主要原因在于插入和删除元素时,只需要修改相应的链接位置,不需要移动大量的元素。
Vector
是Java旧版本中集合的实现,它与ArrayList的操作几乎一样,但Vector是线程安全的动态数组。
四、Set集合
4.1 Set接口
List
集合按照对象的插入顺序保存对象,Set
集合的对象不按照顺序保存对象,可以说是不完全无序状态。
Set
集合中的对象没有按照特定的方式排序,仅仅简单地将对象加入其中,但是集合中不能存放重复对象。由于Set
接口实现了Collection接口,所以Set接口有Collection接口提供的所有常用方法。
4.2 HashSet类
HashSet
是Set集合最常用的实现类,它按照Hash算法来存储集合中的元素,根据对象的哈希码确定对象的存储位置,具有良好的存取和查找性能。HashSet
类的主要方法有:
4.2.1 HashSet类的常用方法
方法 | 说明 |
---|---|
boolean add(E e) | 向集合中添加集合中没有的元素 |
void clear() | 移除集合中所有的元素 |
boolean contains(Object o ) | 如果集合中包含指定元素,返回true |
boolean isEmpty() | 如果为空集合,则返回true |
Iterator iterator() | 返回此集合中元素进行迭代的迭代器 |
boolean remove(Object o) | 删除指定元素 |
int size() | 返回集合中元素数量 |
4.2.2 HashSet类的常用方法举例
HashSet类的实现:
public class Example8_8 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("a");
hashSet.add("null");
hashSet.add("b");
hashSet.add("c");
hashSet.add("d");
System.out.println("集合中的元素为:"+hashSet);
hashSet.remove("b");
System.out.println("集合中是否包含b元素:"+hashSet.contains("b"));
Object[] objects = hashSet.toArray();
System.out.print("数组中的元素为:");
for (Object o:objects){
System.out.print(o);
}
hashSet.clear();
System.out.println("\n"+"集合中是否不包含任何元素:"+hashSet.isEmpty());
}
}
编译和运行后的结果为:
集合中的元素为:[a, b, c, null, d]
集合中是否包含b元素:false
数组中的元素为:acnulld
集合中是否不包含任何元素:true
可以看到HashSet
中的元素没有按照顺序进行存储,并且可以为null,null的个数只有一个;重复向HashSet中添加元素,其值只显示一次。
Set
集合中不允许重复的元素存在,当向集合中插入对象时,如何判别在集合中是否已经存在该对象?
查看源码可知:当向HashSet对象添加新对象时,Java系统先调用对象的hashCode()方法来获取该对象的哈希码,如何根据哈希码找到对应的存储区域。如果该存储区域已经有了对象,则调用equals()方法与新元素进行比较,相同就不保存,不同就放在其它地址。
针对用户自行定义的对象,需要重写hashCode()
和equals()
方法才能避免重复添加对象,保证程序的正常运行。
4.3 TreeSet接口
4.3.1 TreeSet类的常用方法
TreeSet
集合中的元素处于排序状态,主要按照红黑树的数据结构来存储对象。TreeSet
提供了一些额外的方法。
方法 | 类型 | 说明 |
---|---|---|
TreeSet() | 构造 | 构造一个空Set对象,Set根据元素的自然顺序进行排序 |
TreeSet(Collectio c) | 构造 | 用类c的与那苏初始化Set |
TreeSet(Comparator comparator) | 构造 | 按照comparator指定的比较方法进行排序 |
TreeSet(SortedSet s) | 构造 | 构造了一个包含s元素的set |
Object first() | 普通 | 返回Set中排序为第一个的元素 |
Object last() | 普通 | 返回Set中排序为最后一个的元素 |
E lower(E e) | 普通 | 返回此Set中严格小于给定元素的最大元素,如果不存在则返回null |
E higher(E e) | 普通 | 返回此Set中严格大于给定元素的最大元素,如果不存在则返回nul |
SortedSet subSet(E fromElement, E toElement) | 普通 | 返回有序集合,其元素范围为[fromElement,toElement) |
SortedSet headSet(E toElement) | 普通 | 返回此Set的部分集合,其元素范围为[, toElement)小于toElement |
SortedSet tailSet(E fromElement) | 普通 | 返回此Set的部分集合,其元素范围为[fromElement ,]大于等于toElement |
4.3.2 TreeSet类的常用方法举例
利用TreeSet方法对集合中的元素进行操作。
class Example8_9 {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
TreeSet treeSet1 = new TreeSet();
treeSet.add(10);
treeSet.add(3);
treeSet.add(8);
treeSet.add(0);
treeSet1.add("b");
treeSet1.add("k");
treeSet1.add("z");
treeSet1.add("a");
//输出集合元素,看到集合已经处于排序状态
System.out.println("treeSet集合中的元素"+treeSet);
System.out.println("treeSet1集合中的元素"+treeSet1);
System.out.println("treeSet集合中的第一个元素:"+treeSet.first()+",treeSet集合中的最后一个元素:"+treeSet.last());
System.out.println("treeSet1集合中的第一个元素:"+treeSet1.first()+",treeSet1集合中的最后一个元素:"+treeSet1.last());
//返回集合中小于5的集合,不包含5;
System.out.println(treeSet.headSet(5));
//返回集合中小于c的集合
System.out.println(treeSet1.headSet("c"));
//返回大于等于8的集合
System.out.println(treeSet.tailSet(8));
//返回大于等于9,小于11的集合
System.out.println(treeSet.subSet(9,11));
}
}
编译和运行后的结果为:
treeSet集合中的元素[0, 3, 8, 10]
treeSet1集合中的元素[a, b, k, z]
treeSet集合中的第一个元素:0,treeSet集合中的最后一个元素:10
treeSet1集合中的第一个元素:a,treeSet1集合中的最后一个元素:z
[0, 3]
[a, b]
[8, 10]
[10]
从上面的运行结果来看,TreeSet
根据元素的实际大小进行排序,不同于List
是按照元素插入顺序进行排序。
TreeSet
排序通用规则:若为数值,按照大小;若为字符,按照字符对应的Unicode值排序;如果是日期、时间,按照时间顺序排序;如果是boolean,true大于false
4.4 Set实现类性能分析
TreeSet
集合中元素唯一且已经排好序,采用额外的红黑树算法进行排序,如果需要一个保持顺序的集合时,应该选择TreeSet
;
HashSet
集合中元素唯一且已经排好序,它按照Hash算法进行排序,如果经常对元素进行添加、查询操作,应该选择HashSet
;
LinkedHashSet
是HashSet
的一个子类,具有HashSet
的特性,也是根据元素的hashCode值来确定元素的存储位置,它使用链表维护元素的次序,因此插入和删除性能比HashSet
低,迭代访问集合中全部元素时性能高。
五、Queue队列
5.1 Queue接口
Queue
接口是一个先入先出(FIFO
)的数据结构,继承Collection接口,LinkedList(双向链表)实现了List和Deque接口。
5.2 Deque接口和ArrayDeque类
Deque
接口是Queue
接口的子接口, Deque
接口的实现类主要是LinkedList
类(前面已学习)和ArrayDeque
类。
ArrayDeque
类是使用可变循环数组来实现双端队列,该容器不允许放入null
元素。
5.2.1 Deque接口和ArrayDeque类常用方法
方法 | 说明 |
---|---|
boolean add(E e) | 将元素e插入此双端队列末尾 |
void addFirst(E e) | 将元素e插入此双端队列开头 |
void addLast(E e) | 将元素e插入此双端队列末尾 |
void clear() | 删除队列中所有元素 |
boolean contains(Object o) | 判断是否包含指定元素 |
element() | 获取队列中的头 |
getFirst() | 获取队列中第一个元素 |
getLast() | 获取队列中最后一个元素 |
boolean offer(E e) | 将元素e插入此双端队列末尾 |
boolean offerFirst(E e) | 将元素e插入此双端队列开头 |
boolean offerLast(E e) | 将元素e插入此双端队列末尾 |
removeFirst() | 获取并且删除队列中第一个元素 |
removeLast() | 获取并且删除队列中最后一个元素 |
int size() | 返回队列中所有元素数量 |
5.2.2 Deque接口和ArrayDeque类常用方法举例
class Example8_10 {
public static void main(String[] args) {
ArrayDeque arrayDeque = new ArrayDeque();
arrayDeque.addFirst("b");
arrayDeque.offerFirst("k");
arrayDeque.addLast("z");
arrayDeque.add("a");
//输出集合元素,看到集合已经处于排序状态
System.out.println("arrayDeque队列中的元素"+arrayDeque);
System.out.println("arrayDeque队列中是否包含c:"+arrayDeque.contains("c"));
System.out.println(arrayDeque.removeFirst());//删除第一个元素
System.out.println("arrayDeque队列的首元素:"+arrayDeque.element());
System.out.println("arrayDeque队列的首元素:"+arrayDeque.getFirst());
System.out.println("arrayDeque队列的元素个数:"+arrayDeque.size());
}
}
编译和运行后的结果为:
arrayDeque队列中的元素[k, b, z, a]
arrayDeque队列中是否包含c:false
k
arrayDeque队列的首元素:b
arrayDeque队列的首元素:b
arrayDeque队列的元素个数:3
5.3 PriorityQueue类
PriorityQueue
(优先队列)是Queue接口的实现类,其底层是堆实现的。每次插入或删除元素后都对队列进行调整,队列始终构成最小堆或最大堆。
5.3.1 PriorityQueue类常用方法
方法 | 说明 |
---|---|
boolean add(E e) | 插入指定元素到此优先队列 |
boolean offer(E e) | 插入指定元素到此优先队列 |
void clear() | 删除所有元素 |
boolean contains(Object o) | 是否包含指定元素 |
peek() | 获取此优先队列的头,队列为空返回null |
poll() | 获取并且删除此优先队列的头,队列为空返回null |
boolean remove(Object o) | 删除指定元素 |
int size() | 返回此优先队列元素个数 |
5.3.2 PriorityQueue类常用方法举例
class Example8_11 {
public static void main(String[] args) {
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add(3);
priorityQueue.offer(7);
priorityQueue.add(1);
priorityQueue.add(4);
System.out.println("priorityQueue队列中的元素" + priorityQueue);
System.out.println("priorityQueue队列中是否包含2:" + priorityQueue.contains(2));
System.out.println("priorityQueue队列的首元素:" + priorityQueue.peek());
System.out.println("priorityQueue队列的首元素:" + priorityQueue.poll());
System.out.println("删除元素后,arrayDeque队列的元素:"+priorityQueue);
priorityQueue.remove(7);
System.out.println("删除元素后,arrayDeque队列的元素:"+priorityQueue);
System.out.println("arrayDeque队列的元素个数:" + priorityQueue.size());
}
}
编译和运行后的结果为:
priorityQueue队列中的元素[1, 4, 3, 7]
priorityQueue队列中是否包含2:false
priorityQueue队列的首元素:1
priorityQueue队列的首元素:1
删除元素后,arrayDeque队列的元素:[3, 4, 7]
删除元素后,arrayDeque队列的元素:[3, 4]
arrayDeque队列的元素个数:2
六、Map集合
6.1 Map接口
Map
集合用于保存具有映射关系的数据,在Map集合中保存着两组值,一组值是key键,另一组值是value值。key和value之间存在一对一的关系
,通过指定的key键
就能找到唯一的value值。Map中的key键不允许重复(key键唯一
),value值可以重复
。
6.1.1 Map接口常用方法
方法 | 说明 |
---|---|
put(K key, V value) | 向集合中添加指定的键——值映射关系 |
void putAll(Map<? extends K, ? extends V> m) | 向集合中添加指定集合中所有的键——值映射关系 |
boolean containsKey(Object key) | 判断此集合中是否包含指定的键映射关系 |
boolean containsValue(Object value) | 判断此集合中是否包含指定的值映射关系 |
V get(Object key) | 返回指定的键所映射的值,否则返回null |
Set keySet() | 以Set集合的形式返回该集合中的所有键对象 |
Collection values() | 以Collection集合的形式返回该集合中的所有值对象 |
V remove(Object key) | 删除并返回键对应的值对象 |
void clear() | 清空集合中所有的映射关系 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 返回集合中键——值映射关系的个数 |
boolean equals(Object o) | 比较指定的键——值映射关系是否相等 |
6.1.2 Map接口常用方法举例
class Example8_12 {
public static void main(String[] args) {
Map map1 = new HashMap<>();
Map map2 = new HashMap<>();
Map map3 = new HashMap<>();
map1.put(1001, "Bill");
map1.put(1002, "a");
map1.put(null, null);
map2.put(1003, "B");
map2.put(100, "C");
map3.put(100, "C");
map3.put(1003, "B");
System.out.println("map1的对象为:" + map1);
System.out.println("map2的对象为:" + map2);
map1.putAll(map2);
System.out.println("map1的对象为:" + map1);
System.out.println("map1是否包含键对象null:" + map1.containsKey(null));
System.out.println("map1是否包含\"a\"值:" + map1.containsValue("a"));
System.out.println("map1的键1001的对象为:" + map1.get(1001));
System.out.println("map1的键对象为:" + map1.keySet());
System.out.println("map1的值对象为:" + map1.values());
System.out.println("删除1003键对象后,map1的值对象为:" + map1.remove(1003) + "," + map1);
map1.clear();
System.out.println("map1的值对象为:" + map1);
System.out.println("map1是否为null:" + map1.isEmpty());
System.out.println("map2的大小:" + map2.size());
System.out.println("map2和map3是否是一个对象:" + map2.equals(map3));
}
}
编译和运行后的结果为:
map1的对象为:{null=null, 1001=Bill, 1002=a}
map2的对象为:{100=C, 1003=B}
map1的对象为:{null=null, 100=C, 1001=Bill, 1002=a, 1003=B}
map1是否包含键对象null:true
map1是否包含"a"值:true
map1的键1001的对象为:Bill
map1的键对象为:[null, 100, 1001, 1002, 1003]
map1的值对象为:[null, C, Bill, a, B]
删除1003键对象后,map1的值对象为:B,{null=null, 100=C, 1001=Bill, 1002=a}
map1的值对象为:{}
map1是否为null:true
map2的大小:2
map2和map3是否是一个对象:true
可以看到:在Map
集合中,值对象可以是null
,并且不限制个数,因此get()
方法的返回值为null时,可能在该集合中没有该键对象,也可能键对象没有对应的映射值。要判断是否存在某个键时,要使用containsKey
()方法。
6.2 HashMap类
6.2.1 HashMap介绍
HashMap
实现了Map
接口,因此HashMap
有Map接口提供的所有常用方法。同HashSet
类似,HashMap
不能保证元素的顺序。
HashMap
的底层实现采用了哈希表,JDK1.8之前,HashMap的底层是采用数组+链表的方法,即用链表处理哈希冲突。
键值对的存放和查询采用的是Hash算法,当添加严格元素(key-value)时,首先计算元素key的Hash值
,以此确定插入数组中的位置。有可能存在同一Hash值
的元素已经被放在数组的同一位置了,这时就添加到同一Hash值元素的后面
,他们在数组的同一位置
,但是形成了链表,同一个链表上的Hah值时相同的,所以说数组存放的是链表。
JDK8中,转换红黑树有个大前提,就是当前hash table
的长度也就是HashMap的capacity(不是size)不能小于64.小于64就只是做个扩容,满足这个前提后,当链表的长度大于8
时,链表就转换成为红黑树
,这样就提高了查找元素的效率。
6.2.2 HashMap类常用方法举例
public class Example8_13 {
public static void main(String[] args) {
Map map = new HashMap();
map.put(109, "PS");
map.put(107, "c语言");
map.put(108, "Js");
map.put(99, "数据结构");
map.put(99, "Java程序设计");//存在相同的key时,后插入的会被覆盖
System.out.println(map);
Map map1 = new HashMap<>();
map1.put(1001, "Bill");
map1.put(1001, "Bi");//存在相同的key时,后插入的会被覆盖
System.out.println(map1);
System.out.println(map.containsKey(99));
for(Object o:map.keySet()){
System.out.println(o+"-->"+map.get(o));
}
}
}
编译和运行后的结果为:
{99=Java程序设计, 107=c语言, 108=Js, 109=PS}
{1001=Bi}
true
99-->Java程序设计
107-->c语言
108-->Js
109-->PS
6.3 TreeMap类
6.3.1 TreeMap类常用方法
TreeMap
是Map
接口的主要实现类,TreeMap
存放的是有序数据,按照key
进行排序。TreeMap
底层采用红黑树(红黑树的每个节点就是一个key-value对)对key-value进行排序。
TreeMap
类的主要方法如下:
方法 | 类型 | 说明 |
---|---|---|
TreeMap() | 构造方法 | 使用键的自然顺序构造一个新的、空的树映射 |
TreeMap(Map<? extends K, ? extends V> m) | 构造 | 构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序排序 |
TreeMap(Comparator<? super K> comparator) | 构造 | 构造一个新的、空的树映射,该映射根据给定的比较器进行排序 |
TreeMap(SortedMap<K, ? extends V> m) | 构造 | 构造一个与给定有序映射具有相同映射关系和相同排序顺序的新的树映射 |
Comparator<? super K> comparator() | 普通 | 返回对此映射中的键进行排序的比较器;如果此映射使用键的自然顺序,则返回null |
K firstKey() | 普通 | 返回此映射中当前第一个键 |
K lastKey() | 普通 | 返回此映射中当前最后一个键 |
SortedMap<K,V> headMap(K toKey) | 普通 | 返回此映射中部分视图,其键值小于toKey |
SortedMap<K,V> subMap(K fromKey, K toKey) | 普通 | 返回此映射中部分视图,其键值小于toKey大于等于fromKey |
SortedMap<K,V> tailMap(K fromKey) | 普通 | 返回此映射中部分视图,其键值大于等于fromKey |
6.3.2 TreeMap类常用方法举例
public class Example8_14{
public static void main(String[] args) {
TreeMap treeMap = new TreeMap();
treeMap.put(10,"a");
treeMap.put(1,"a");
treeMap.put(9,null);
treeMap.put(5,"c");
treeMap.put(3,null);
System.out.println(treeMap);
System.out.println(treeMap.lastKey());
System.out.println(treeMap.headMap(2));
System.out.println(treeMap.tailMap(2));
TreeMap treeMap1 = new TreeMap(treeMap);
System.out.println(treeMap1);
}
}
输出结果为:
{1=a, 3=null, 5=c, 9=null, 10=a}
10
{1=a}
{3=null, 5=c, 9=null, 10=a}
{1=a, 3=null, 5=c, 9=null, 10=a}
6.4 TreeMap和HashMap性能比较
HashMap
没有按照键值大小输出,如果需要对key-value进行插入、删除操作,优先使用
HashMap
;
TreeMap
按照键值大小输出,,针对需要排序的Map,优先使用TreeMap
。
七、总结各集合类之间对比
Arraylist | LinkedList | HashSet | TreeSet | ArrayDeque | PriorityQueue | HashMap | TreeMap | |
---|---|---|---|---|---|---|---|---|
是否有序 | 有序 | 有序 | 无序 | 有序 | 有序 | 有序 | 无序 | 有序 |
存储结构 | 集合 | 集合 | 集合 | 集合 | 集合 | 集合 | 键值对 | 键值对 |
线程安全 | 否 | 否 | 否 | 否 | 否 | 否 | 否 | 否 |
元素是否有重复 | 是 | 是 | 否 | 否 | 是 | 是 | 键值唯一,相同覆盖 | 键值唯一,相同覆盖 |
元素是否可为null | 是 | 是 | 是 | 是 | 否 | 否 | 是 | 是 |
底层数据结构 | 数组 | 链表 | 哈希表 | 二叉树 | 循环数组 | 堆 | 哈希表 | 红黑树 |
特点 | 查询快,增删慢 | 查询慢,增删快 | 快速查找 | 快速排序 | 查询快,增删慢 | / | 插入、删除和定位元素快 | 按自然顺序或自定义顺序遍历快 |