Java容器
容器类是Java以类库的形式供用户开发程序时可直接使用的各种数据结构。所谓数据结构就是以某种方式将数据组织在一起,并存储在计算机中。数据结构不仅可以存储数据,还支持访问和处理数据的操作。在面向对象思想里,一种数据结构被认为是一个容器。数组是一种简单的数据结构,除数组外Java还以类库的形式提供了许多其他数据结构。这些数据结构通常称为容器类或称集合类。
Java容器框架
Java容器框架中有两个名称分别为Collection
和Set
的接口,本书将Collection
译为容器,而将Set
译为集合。Java容器框架提供了一些现成的数据结构可供使用,这些数据结构是可以存储对象的集合,在这里对象也称为元素。从JDK 5开始,容器框架全部采用泛型实现,且都存放在java.util包中。容器框架中的接口及实现这些接口的类的继承关系如图12.1所示。Java容器框架结构由两棵接口树构成,第一棵树根节点为Collection接口,它定义了所有容器的基本操作,如添加、删除、遍历等。它的子接口Set、List等则提供了更加特殊的功能,其中Set的对象用于存储一组不重复的元素集合,而List的对象用于存储一个由元素构成的线性表。第二棵树根节点为Map接口,它保持了“键”到“值”的映射,可以通过键来实现对值的快速访问。
容器框架中的接口和实现接口的类的继承关系:
容器接口Collection
容器接口Collection
通常不能直接使用,但该接口提供了添加元素、删除元素、管理数据的方法。由于Set
接口和List
接口都继承了Collection
接口,因此这些方法对集合Set
与列表List
是通用的。下面给出了Collection<E>
接口的常用方法,其中的方法默认为public abstract
。由于容器框架全部采用泛型实现,所以我们以泛型的形式给出相应的方法,即带类型参数。
List <E>接口的常用方法
列表接口List
列表接口List
是Collection
子接口,它是一种包含有序元素的线性表,其中的元素必须按顺序存放,且可重复,也可以是空值null。元素之间的顺序关系可以由添加到列表的先后来决定,也可由元素值的大小来决定。List
接口使用下标来访问元素。下标范围为0~size()-1
。List接口新增了许多方法,使之能够在列表中根据具体位置添加和删除元素。List<E>
接口的主要方法如表所示。方法默认为public abstract
。
List <E>接口的常用方法:
实现List接口的类主要有两个:链表类LinkedList
和数组列表类ArrayList
。它们都是线性表。
LinkedList链表类采用链表结构保存对象,使用循环双链表实现List。这种结构向链表中任意位置插入、删除元素时不需要移动其他元素,链表的大小是可以动态增大或减小的,但不具有随机存取特性。
ArrayList数组列表类使用一维数组实现List,该类实现的是可变数组,允许所有元素,包括null。具有随机存取特性,插入、删除元素时需要移动其他元素,当元素很多时插入、删除操作的速度较慢。在向ArrayList中添加元素时,其容量会自动增大,但不能自动缩小,但可以使用trimToSize()方法将数组的容量减小到数组列表的大小。
ArrayList数组列表类使用一维数组实现List,该类实现的是可变数组,允许所有元素,包括null。具有随机存取特性,插入、删除元素时需要移动其他元素,当元素很多时插入、删除操作的速度较慢。在向ArrayList中添加元素时,其容量会自动增大,但不能自动缩小,但可以使用trimToSize()方法将数组的容量减小到数组列表的大小。
下面分别给出了LinkedList<E>类和ArrayList<E>类的构造方法。
LinkedList <E>类的构造方法
LinkedList <E>类的构造方法
使用线性表时通常声明为List <E>类型,然后通过不同的实现类来实例化列表。如:
List<String> List1 = new LinkedList<String>();
List<String> List2 = new ArrayList<String>();
LinkedList <E>类与ArrayList <E>类大部分方法是继承其父类或祖先类,除此之外还各自定义了自己的方法,下面分别给出了LinkedList<E>类与ArrayList<E>类的常用方法。
LinkedList <E>类的常用方法:
ArrayList <E>类的常用方法:
利用LinkedList <E>类构造一个先进后出的堆栈。
package com.demo.seckill;
import java.util.LinkedList;
import java.util.Scanner;
class StringStack {
private LinkedList<String> Id = new LinkedList<String>();//创建LinkedList对象Id
public void push(String name) { //入栈操作
Id.addFirst(name); //将name添加到链表的头
}
public String pop() { //出栈操作
return Id.removeFirst(); // 获取并移出堆栈中的第一个元素
}
public boolean isEmpty() { //判断堆栈是否为空
return Id.isEmpty();
}
}
public class Map {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
StringStack stack = new StringStack();
System.out.println("请输人数据(quit结束)");
while (true) {
String input = sc.next(); //从键盘输入数据
if (input.equals("quit"))
break;
stack.push(input); //入栈
}
System.out.println("先进后出的顺序:");
while (!stack.isEmpty())
System.out.print(stack.pop() + " "); //出栈
}
}
集合接口Set
Set是一个不含重复元素的集合接口,它继承自Collection接口,并没有声明其他方法,它的方法都是从Collection接口继承来的。Set集合中的对象不按特定的方式排序,只是简单地把对象加入集合中即可,但加入的对象一定不能重复。集合中元素的顺序与元素加入集合的顺序无关。实现Set接口的两个主要类是哈希集合类HashSet及树集合类TreeSet。
哈希集合类HashSet
哈希集合对所包含元素的访问并不是像线性表一样使用下标,而是根据哈希码来存取集合中的元素。为此先介绍一下哈希码的概念。哈希集合是在元素的存储位置和元素的值k之间建立一个特定的对应关系f,使每个元素与一个唯一的存储位置相对应。因而在查找时,只要根据元素的值k,计算f(k)的值即可,如果此元素在集合中,则必定在存储位置f(k)上,因此不需要与集合中的其他元素进行比较便可直接取得所查的元素。称这个对应关系f为哈希函数,按这种关系建立的表称为哈希表,也称散列表。
HashSet集合类是基于哈希表的Set接口实现的。HashSet根据哈希码来确定元素在集合中的存储位置(即内存地址),因此可以根据哈希码来快速地找到集合中的元素。HashSet集合不保证迭代顺序,但允许元素值为null。在比较两个加入哈希集合HashSet中的元素是否相同时,会先比较哈希码方法hashCode()的返回值是否相同,若相同则再使用equals()方法比较其存储位置(即内存地址),若两者都相同则视为相同的元素。之所以在比较了哈希码之后,还要通过equals()方法进行比较,是因为对不同元素计算出的哈希码可能相同。因此,对于哈希集合来说,若重写了元素对应类的equals()方法或hashCode()方法中的某一个,则必须重写另一个,以保证其判断的一致性。
分别给出了HashSet<E>集合类的构造方法和常用方法。
HashSet <E>集合类的构造方法
说明:构造方法中的上座率也称装填因子,上座率的值为0.0~1.0表示集合的饱和度。当集合中的元素个数超过了容量与上座率的乘积,容量就会自动翻倍。
HashSet <E>集合类的常用方法
说明:输出哈希集合元素时并不一定是按元素的存储顺序输出,因为哈希集合中的元素是没有特定顺序的,若一定要让元素有序输出,则需要使用LinkedHashSet类。
树集合类TreeSet
树集合类TreeSet不仅实现了Set接口,还实现了java.util.SortedSet接口。TreeSet的工作原理与HashSet相似,但TreeSet增加了一个额外步骤,以保证集合中的元素总是处于有序状态。因此,当排序很重要时,就选择TreeSet,否则应选用HashSet。TreeSet<E>类的大多数方法继承自其父类或祖先类。
TreeSet <E>类的构造方法
TreeSet <E>类的新增的方法
映射接口Map
Map是另一种存储数据结构的对象,Map接口与List接口和Set接口有明显的区别。Map中的元素都是成对出现的,它提供了键(key)到值(value)的映射。值是指要存入Map中的元素(对象),在将元素存入Map对象时,需要同时给定一个键,这个键决定了元素在Map中的存储位置。一个键和它对应的值构成一个条目,真正在Map中存储的是这个条目。键很像下标,但在List中下标是整数,而在Map中键可以是任意类型的对象。如果要在Map中检索一个元素,必须提供相应的键,这样就可以通过键访问到其对应元素的值。Map中的每个键都是唯一的,且每个键最多只能映射到一个值。由于Map中存储元素的形式较为特殊,所以Map没有继承Collection接口。表12.13给出Map<K,V>接口的常用方法,其中K表示键的类型,V表示值的类型。因为Map是接口,所以其方法默认为public abstract。
Map <K,V>接口的常用方法
映射接口Map常用的实现类有哈希映射HashMap和树映射TreeMap。HashMap映射是基于哈希表的Map接口的实现类,所以HashMap通过哈希码对其内部的映射关系进行快速查找,因此对于添加和删除映射关系效率较高,并且允许使用null值和null键,但必须保证键的唯一性;而树映射TreeMap中的映射关系存在一定的顺序,如果希望Map映射中的元素也存在一定的顺序,应该使用TreeMap类实现的Map映射,由于TreeMap类实现的Map映射中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null。
HashMap映射的方法大多是继承自Map接口。
HashMap <K,V>映射常用的构造方法
分别给出了TreeMap<K,V>映射的构造方法和常用方法。
TreeMap <K,V>映射的构造方法
TreeMap <K,V>映射的常用方法
总结
-
容器是存储对象的数据结构的集合。容器框架中定义的所有接口和类都存储在java.util包中。
-
从容器的当前元素获取其后续元素进行访问的过程称为迭代,迭代也称为遍历。
-
List的对象用于存储一个由元素构成的线性表;Set的对象是存储一组不重复的元素集合;Map的对象保持了键到值的映射。
-
List是一种包含有序元素的线性表,其中的元素必须按顺序存放,且可重复,也可以是空值null。实现List接口的类主要有链表类LinkedList和数组列表类ArrayList。
-
LinkedList是实现List接口的链表类,采用双向链表结构保存元素,访问元素的时间取决于元素在表中所处的位置,但对链表的增长或缩小则没有任何额外的开销。
-
ArrayList是实现List接口的数组列表类,它使用一维数组实现List,支持元素的快速访问,但在数组的扩展或缩小时则需要额外的系统开销。
-
Set是一个不含重复元素的集合接口。实现Set接口的两个主要类是哈希集合类HashSet及树集合类TreeSet。
-
HashSet的工作原理是在哈希集合中元素的“值”与该元素的存储位置之间建立起一种映射关系,这种映射关系称为哈希函数或散列函数,由哈希函数计算出来的数值称为哈希码或散列索引。虽然HashSet中的元素是无序的,但由于HashSet特性还是可以快速地添加或访问其中的元素。
-
因为对不同元素计算出的哈希码可能相同,所以判断哈希集合中的元素是否相同时需要同时使用hashCode()方法和equals()方法。
-
TreeSet类对象中的元素总是有序的,所以当插入元素时需要一定的开销。
-
Map中的元素都是成对出现的,它提供了键(key)到值(value)的映射。
-
映射接口Map常用的实现类有HashMap和TreeMap。HashMap类与TreeMap类的关系如同HashSet与TreeSet的关系一样。
-
HashMap类是基于哈希表的Map接口的实现,允许使用null值和null键,但必须保证键的唯一性,HashMap是无序的。
-
TreeMap类中的映射关系存在一定的顺序,不允许键对象是null。TreeMap是有序的。