文章目录
1 .概述
Java的集合类是一个特别有用的工具类,用于存储数量不等的多个对象,并可以实现常用的数据结构,如栈、队列。Java集合大致分为:Set、List和Map三种体系。Set:无序、不可重复。List:有序、可以重复。Map:具有映射关系的集合。JDK1.5之后又增加了Queue体系集合,代表队列集合实现。
集合类注意负责保存、盛装其他数据,因此集合类也称容器类。所有集合位于java.util包下。
Java的集合类主要由两个接口派生而出。Collection和Map是Java集合的根接口。两个接口又保安韩了一些子接口或实现类。
2.Collection和Iterator接口
Collection接口是List、Set和Queue接口的父接口,该接口里面定义的方法既可以操作Set集合,也可以操作List和Queue集合。
-
Collection接口定义操作如下:
-
boolean add(Object o);
:添加一个元素,添加成功返回true。
-
boolean addAll(Collection c);
:把集合c里所有元素添加到指定集合里,如果集合对象被添加操作改不了返回true。
-
void clear();
:清除集合里的所有元素,将集合长度变为0.
-
boolean contains(Object o);
:返回集合是否包含指定元素。
-
boolean containsAll(Collection c);
:返回集合里是否包含集合c里所有元素。
-
boolean isEmpty();
:返回集合是否为空,当长度为0时返回true;否则false。
-
Iterator iterator();
:返回一个Iterator对象,用于遍历集合里的元素。
-
boolean remove(Object o);
:删除集合中指定元素o,当集合中包含了一个或者多个元素o时,这些元素将会被删除,该方法返回true。
-
boolean removeAll(Collection c);
:删除指定集合里的所有元素,如果删除一个或一个以上的元素,返回true。
-
boolean retainAll(Collection c);
:从集合中删除集合c里不包含的元素,如果该操作改变了调用该方法的集合,返回true。
-
int size();
:返回集合里的元素个数。
-
Object[] toArray();
:该方法把集合转换成一个数组,所有集合元素都变成对于的数组元素。
使用Iterator接口遍历集合元素
- 使用Iterator遍历集合主要用到下面三种方法:
-
boolean hasNect();
:如果被迭代得集合元素还没有被遍历,返回true。
-
Object next();
:返回集合里下一个元素。
-
void remove();
:删除集合里上一次next方法返回的元素。
Collection books=new HashSet();
books.add("格林童话");
books.add("安徒生童话");
Iterator it=books.iterator();
while(it.hasNext){
String book=(String)it.next();
System.out.println(book);
}
Iterator必须依附于Collection对象,Iterator提供了2个方法来迭代访问Collection集合里的元素,并可以通过remove方法删除集合中上次next方法返回的集合元素。
使用Iterator迭代块内,也就是Iterator迭代Collection集合过程中修改Collection集合,会反射依次。Iterator迭代器采用的是快速失败机制(fail-fast),一旦在迭代过程中,检测到该集合已经被修改(通常程序中其他线程被修改),程序立即引发ConcurrentModificationException异常。 .
-
使用foreach循环变量集合元素
- JDK1.5之后提供了foreach循环来迭代访问集合元素更加便捷。
Collection books=new HashSet();
books.add("格林童话");
books.add("安徒生童话");
for(Object obj: books){
String book=(String)obj;
System.out.println(book);
}
从语法上看,使用foreach循环来迭代访问Collection集合元素更简洁,这也是JDK1.5之后的foreach集合循环带来的优势。与Iterator迭代访问集合想类似的是,foreach循环的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋给迭代变量,因此,在foreach循环中修改迭代变量的值也没有任何意义。
同样地,在foreach循环访问集合元素时,该集合也不能被改变,否则会引发ConcurrentModificationException异常。
1.1 Set接口
Set集合类似于一个罐子,把对象添加到Set集合是,Set集合无法记住添加元素的顺序,所以Set集合里面的元素不能重复。如果把两个相同的元素加入同一个set集合里,则添加操作失败,add方法返回false,且新元素不会被加入。
Set判断两个对象相同不是使用==
而是根据equals
,如果两个对象用equals方法比较返回true,Set就不会接受这两个对象;反之,只要这两个对象的用equals返回的是false,Set就会接受这两个对象(甚至这两个对象是同一个对象,Set也可以把他们当做两个对象来处理)。
Set books=new HashSet();
// 添加一个字符串对象
books.add(new String("安徒生童话"));
// 再次添加一个字符串对象
// 因为两个字符串对象通过equals方法比较相等,所以添加失败,返回false。
boolean result=books.add(new String("安徒生童话"));
//输出结合,只有一个元素
System.out.println(books);
上面这段代码只输出一个元素,显然上面两个被添加的对象使用==
返回的是false,使用equals
返回true,所以添加失败了。
1.1.1 HashSet类
HashSet是Set接口的典型实现,大多数时候使用这个实现类。HashSet按Hash算法来存储集合中的元素,因此有很好的存取和查找性能。
-
HashSet具有以下特点:
-
- 不能保证元素的排列顺序,顺序可能发生变化。
-
- HashSet不是同步的,如果多个线程同时访问一个Set集合,如果多个线程同时访问一个HashSet,如果2条或2条以上线程同时修改一个HashSet集合时,必须通过代码保证其同步。
-
- 集合元素值可以是null。
HashSet判断两个元素是否相等的标准是比使用equals()
和hashCode()
判断相等。
当想HashSet存入一个数据时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值来决定该对象在HashSet中存储位置。如果两个元素通过equals方法比较返回true,但是他们的hashCode值不同,HashSet也会将他们存储在不同的位置,也可以成功添加。
public class HashSetTest {
public static void main(String[] args) {
HashSet books=new HashSet();
//分别向books集合中添加两个A对象。两个B对象,两个C对象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
System.out.println(books);
}
}
class A{
@Override
public boolean equals(Object obj) {
return true;
}
}
class B{
@Override
public int hashCode() {
return 1;
}
}
class C{
@Override
public int hashCode() {
return 2;
}
@Override
public boolean equals(Object obj) {
return true;
}
}
运行结果:
如果两个对象通过hashCode()方法返回相同的值,但是通过equals()比较返回true,这将导致HashSet将会把这两个对象保存在HashSet的不同位置,从而两个对象都可以添加成功,这与Set集合的规则有点出入。如果HashSet中有两个hashCode值相同,将会导致性能下降。
hashCode方法对于HashSet的作用是什么?
我们先要理解hash(也被翻译为哈希、散列)算法的功能:它能保证通过一个对象快速找到另一个对象。hash算法的价值在于速度,它可以保证查询得到快速执行。当需要查询集合中某个元素时,hash算法可以直接根据该元素的值得到还元素保存在何处,从而可以让程序员快速找到该元素。为了理解,我们先看数组(数组是所有能存储一组元素最快的数据结构):数组可以包含多个元素,每个元素也有索引,如果需要访问某个数组元素,只需要提供该元素的索引,该索引即指出了该元素再数组内存区里的存储位置。
表面上看,HashSet集合里的元素都没有索引,实际上当程序向HashSet集合中添加元素时,HashSet会根据该元素的hashCode值来决定它的存储未知子——也就是说,每个元素的hashCode就是它的“索引”。
为什么不直接使用数组,还需要使用HashSet呢?因为数组元素的索引是连续的,而且数组长度是固定的,无法自由添加数组的长度。而HashSet就不一样了,HashSet采用每个元素的hashCode作为其索引,从而可以自由增加HashSet的长度,并可以根据元素的hashCode值访问元素。因此,从头HashSet中访问元素时,HashSet先计算该元素的hashCode值(也及时调用对象的hashCode()方法的返回值),然后直接到该hashCode对应的位置去取出该元素——这就是HashSet速度很快的原因。
HashSet中每个能存储元素的“槽位(slot)”通常称为“桶(bucket)”,如果有多个元素的hashCode相同,但他们通过equals方法比较返回false,就需要在一个“桶”里放多个元素,从而导致性能下降。
-
重写hashCode()方法的基本原则:
-
- 当两个对象通过equals方法比较返回去true时,这两个对象的hashCode应该相等。
-
- 对象中用作equals比较标准的属性,都应该用来计算hashCode值。
属性类型 | 计算方式 |
---|---|
boolean | hashCode=(f?0:1); |
整数类型(byte、short、char、int) | hashCode=(int)f; |
long | hashCode=(int)(f^(f>>>32)); |
float | hashCode=Float.floatToIntBits(f); |
double | long l=Double.doubleToLongBits(f); hashCode=(int)(l^(l>>>32)); |
普通引用类型 | hashCode=f.hashCode(); |
如果像一个HashSet里面添加两个相同类的对象时,添加的时候这两个类的属性值不一样,添加完之后,修改属性值,使得这两个对象的属性值完全相同(即两个对象通过equals方法比较返回true,两个对象的hashCode值也相等),这就有可能导致HashSet中包含两个相同的对象。
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new D(6));
hs.add(new D(9));
hs.add(new D(7));
hs.add(new D(-2));
//打印HashSet集合,集合元素没有重复
System.out.println(hs);
//取出第一个元素
Iterator it=hs.iterator();
D first= (D) it.next();
//修改第一个元素的属性值,修改为与地三个元素值相同,因为我们重写了hashCode方法所以也就是说它们的hashCode也会相等
first.count=7;//(1)
//再次输出HashSet集合,有重复元素
System.out.println(hs);
//删除count为7的D对象
hs.remove(new D(7));
//输出删除后的元素,
System.out.println(hs);
}
}
class D{
public int count;
public D(int count) {
this.count = count;
}
@Override
public String toString() {
return "D{" +
"count=" + count +
'}';
}
@Override
public boolean equals(Object o) {
if (o instanceof D){
D d=(D)o;
if (this.count==d.count){
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.count;
}
}
运行结果:
当向HashSet中添加可变对象时,必须十分小心。如果修改HashSet集合中的对象,有可能导致该对象与集合中其他对象相等,从而导致HashSet无法准确访问集合里的元素
LinkedHashSet
LinkedHashSet是HashSet的子类,LinkedHashSet也是根据元素hashCode值来决定元素存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是一插入顺序保存的。也就是说,当遍历LinkedHashSet集合里元素时,HashSet将会按元素的添加顺序来访问集合里的元素。
1.1.2 TreeSet
TreeSet是SortedSet接口的唯一实现,正如SortedSet名字暗示一样,TreeSet可以确保元素处于排序状态。
-
TreeSet几个方法:
-
Comparator comparator()
:返回当前Set使用的Comparator,或者返回null,表示以自然方式排序。
-
Object first()
:返回集合中第一个元素。
-
Object last()
:返回集合中最后一个元素。
-
Object lower(Object e)
:返回集合中位于指定元素(即小于指定元素的最小元素)。
-
Object higher(Object e)
:返回集合中位于指定元素之后的元素(即大于指定元素的最小元素)。
TreeSet不是根据元素的的插入顺序排序的,而是根据元素的世界值排序。
HashSet集合采用hash算法来决定元素的存储位置不同。TreeSet采用红黑树的数据结构队元素进行排序。
TreeSet支持两种排序方法,自然排序和定制排序:
自然排序
自然排序:TreeSet会调研集合元素的compareTo(Object obj)来比较元素之间的大小,然后将集合按升序排序。
Comparable接口定义了一个int compareTo(Object obj)
方法,实现该接口的类必须实现该方法。
obj1.compateTo(obj2);
返回0:表示obj1与obj2相等。
返回负整数:表明obj1<obj2
返回正整数:表明obj1>obj2
-
Comparable接口常用类
- BigDecimal、BigInteger、所有数值型对于包装类:按它们对应数值的大小进行比较。
- Character:按字符的UNICODE值比较。
- String:按字符串中的字符的UNICODE值比较。
- Boolean:true对应的包装类实例大于false对应的包装类实例。
- Date、Time:后面的时间、日期>前面的时间、日期
public class TestTreeSet {
public static void main(String[] args) {
TreeSet ts1=new TreeSet();
ts1.add("hello");
ts1.add(" world");
ts1.add("love");
System.out.println(ts1);
TreeSet ts2=new TreeSet();
ts2.add(new Err());//(1)
}
}
class Err{
}
运行结果:
其中第19行如下:
在上面代码中,向一个TreeSet集合添加一个字符串对象,操作正常。但是当添加一个我们自定义的引用对象时,却失败了。这是因为TreeSet是一个有序集合,添加时需要通过Comparable的compareTo(Object obj)方法比较大小。
大部分类实现compareTo(Object obj)方法,都需要将被比较对象强制转为相同类型,因为只有相同类的两个实例才会比较大小。因此,TreeSet只允许添加同一个类的实例,也就是说TreeSet中添加1的应该是同一个类的对象,否则也会引发ClassCastException异常。
当一个对象被加入TreeSet集合中时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑算法决定它的存储位置。如果两个对象通过compareTo(Object obj)比较相等,TreeSet认为他们应该存储在同一个位置。
如果向TreeSet中添加一个可变对象之后,并且后面程序修改了该对象的属性,是得它与其他对象大小顺序发生了改变,但TreeSet不会再调整它们的顺序,甚至可能导致TreeSet中保存这两个对象。当试图删除对象是,TreeSet也会删除失败。TreeSet可以删除没有被修改属性,且不与其他属性的对象重复的对象;但是不能修改意见修改过的重复的属性的对象。
定制排序
TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列。如果需要实现定制排序,例如降序排序,则可以使用Comparator
接口。该接口里有一个int compare(T o1,T o2)
方法,用于比较o1和o2大小。
- 返回0,相等
- 返回负整数,o1<o2
- 返回正整数,o1>o2
使用Comparator对象来实现TreeSet的定制排序时,异常不可以向TreeSet中添加类型不同的对象,否则也会引发ClassCastException异常。使用定制排序时,TreeSet对集合元素排序时,不过集合本身的大小,而是Comparator对象负责集合元素的排序规则。
-
HashSet和TreeSet的选择:
-
HashSet和TreeSet是Set的两个典型实现,如何选择HashSet和TreeSet?HashSet的性能总是比TreeSet好(特别是常用的添加、查询元素等操作),因为TreeSet需要额外的红黑数算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通插入、删除操作,LinkedHashSet比HashSet要略微慢一点;这是由于维护链表所带来的额外开销所造成的,不过,因为有了链表,变量LinkedHashSet会更快。
1.1.3 EnumSet类
EnumSet是一个专门为枚举类设计的集合类,EnumSet中所有值必须都是指定枚举类型的枚举值,该枚举类型在创建EnumSet时,显式地或隐式地指定顺序。EnumSet集合元素也是有序的,EnuSet一枚举类在该Enum类的定义顺序来决定集合元素的顺序。
EnumSet不予许加入null元素,如果传入null元素将会抛出NullPointerException异常,TreeSet也不能加入null元素,但是Set和HashSet可以加入null元素。
EnumSet类没有保留任何构造器来创建该类的实例。
-
常用的static方法创建EnumSet对象:
-
static EnumSet allOf(Class elementType);
创建一个保护指定枚举类的所有枚举值的EnumSet集合。
-
static EnumSet complementOf(EnumSet s);
:创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet。
-
static EnumSet copyOf(Collection c)
:使用一个普通集合来创建EnumSet集合。
-
static EnumSet copyOf(EnumSet s)
:创建一个与指定EnumSet具有相同元素类型,相同集合元素的EnumSet。
-
static EnumSet noneOf(Class elementType)
:创建一个元素类型为指定枚举类型的空EnumSet。
-
static EnumSet of(E first,E...rest)
:创建一个保护一个或者多个枚举值的EnumSet,传入的枚举值必须属于同一个枚举类。
-
static EnumSet range(E from,E to)
:创建一个包含从from枚举值,到to枚举值范围内所有枚举值的EnumSet集合。
EnumSet是所有Set实现类中性能最好的,但是它只能保存同一个枚举类的枚举值作为集合元素。
Set的三个实现类HashSet、TreeSet、EnumSet都是线程不安全的。如果有多条线程同时访问一个Set集合,并且超过一条线程修改该Set集合,则必须动手保证该Set集合的同步性。通常可以通过Collection工具类的synchronizedSortedSet方法来“包装”该Set集合。此操作最好在创建时进行,防止对Set集合的意外非同步访问。
1.2List
List集合像一个数组,它可以记住每次添加元素的顺序,只是List集合的长度可变。List集合允许使用重复元素,List集合默认是按元素的添加顺序设置元素的索引。
1.2.1 List接口和ListIterator接口
List集合作为Collection接口的子接口,可以使用Collection里的所有方法。List是有序集合,增加的一些方法:
void add(int index,Object element)
:将元素element插入在List集合的index处。boolean addAll(int index,Collection c)
:将集合c里的所有元素插入在List集合的index处。Object get(int index)
:根据索引取元素值。int indexOf(Object o)
:返回对象o在List集合中的位置索引。int lastIndexOf(Object o)
:返回对象o在List集合中最后一次出现的位置索引。Object remove(int index)
:删除并返沪index索引所出的元素。Object set (int index,Object element)
:将index索引处的元素替换成element对象返回新元素。List subList(int fromIndex, int toIndex)
:返回索引fromIndex(包含)到索引toIndex(不包含)所处集合元素组成的子集合。范围:[fromIndex,toIndex)。
List额外提供了一个ListIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口基础上增加了:
boolean hasPrevious()
:返回迭代器关联的集合时候还有上一个元素。Object previous()
:返回迭代的上一个元素。void add()
:在指定位置插入一个元素。
public class TestListIterator {
public static void main(String[] args) {
String[] books={"格林童话","安徒生童话"};
List bookList=new ArrayList();
for (int i=0;i<books.length;i++){
bookList.add(books[i]);
}
//使用ListIterator,迭代输出
ListIterator listIterator=bookList.listIterator();
System.out.println("==============正向迭代");
while (listIterator.hasNext()){
System.out.println(listIterator.next());
}
System.out.println("============反向迭代============");
while (listIterator.hasPrevious()){
System.out.println(listIterator.previous());
}
}
}
运行结果:
1.2.2 ArrayList和Vector实现类
ArrayList和Vector是List的两个典型实现类。它们都是基于数组实现的List类,它们的对象都有用一个capacity属性,这个属性表示他们所封装的Object[]数组长度。当想ArrayList和Vector添加剂元素时,他们的capacity都会自动增加。当需要这两个集合中添加大量元素时,可以使用ensureCapacity方法一次性增加capacity,这样可以减少分配空间的次数,提高性能。
如果知道ArrayList或者Vector集合需要保存多个个元素,可以在创建它们时就指定它们的capacity大小。如果创建空的ArrayList或者Vector集合不指定capacity属性,capacity属性默认值是10。
void ensureCapacity(int minCapacity)
:将集合的capacity增加minCapacity。void trimToSize()
:调整集合的capacity当前大小。可以调用该方法减少没有使用的存储空间。
Vector里有些重复的方法,这些方法中方法名更短的属于后面新增的方法,方法名长的属于原来的方法。Java改写原来的Vector方法,将其方法名缩短是为了简化编程。而ArrayList开始就作为List的主要实现类,因为没有那些方法名很长的方法。实际上,Vector具有很多缺点,尽量少用Vector实现类。
ArrayList和Vector的区别
? ArrayList是线程不安全的,而Vector是线性安全的。当多条线程访问同一个ArrayList集合时,超过一条线程修改了ArrayList集合,就必须手动保存集合的同步性。因为Vector是线程安全的,无需程序保证集合的同步性,所有Vector的性能要比ArrayList性能低。
Vector的子类Stack
还提供了一个Stack类,模拟数据结构中的“栈”,“栈”是一种“先进后出(后进先出)”(LIFO,Last In First Out)的一种容器。最后“push”进栈的元素,最先被“pop”出栈。
-
Stack类的几个方法
-
Object peek()
:返回”栈“的第一个元素,但是并不让该元素”pop“出栈。
-
Object push()
:返回栈顶的第一个元素,并且将该元素”pop“出栈。
-
void push(Object item)
:将一个元素"push"进栈,最后一个进”栈“的元素总是为与栈顶。
固定长度的List
操作数组的一个工具类:Arrays,该工具提供了asList(Object… a)方法,该方法可以将一个数组或一个指定个数的对象转换成一个List集合,这个List集合既不是ArrayList的实现类的对象,也不是Vector实现类的对象,而是Arrays的内部类ArrayList的对象。
Arrays.ArrayList是个固定的长度的List集合,只能遍历,不能删除,添加。若修改这个集合,会引发UnsupportedOperationException异常。
1.3 Queue接口
模拟数据结构的队列,“先进先出”(First In First Out)。
-
常用方法:
-
void add(Object e)
:将制定元素加入此队列的尾部。
-
Object element()
:获取队列头部元素,但不删除该元素。
-
boolean offer(Object e)
:将指定元素加入此队列的尾部。当使用有限容量限制的队列时,此方法通常比add(Object e)方法更好。
-
Object peek()
:获取队列头部元素,但不是删除该元素。如果队列为空,返回null。
-
Object poll()
:获取队列头部的元素,并删除该元素。如果队列为空,返回null。
-
Object remove()
:获取队列头部的元素,并删除该元素。
1.3.1 LinkedList实现类
LinkedList即是List接口的实现类,也是Deque接口的实现类,而Deque接口又是Queue的子接口。这意味着LinkedList既是一个链表,又是一个双向队列。
-
常用方法
-
void addFirst(Object e)
:将指定元素插入该双向队列的开头。
-
void addLast(Object e)
:将指定元素插入到双向队列的末尾。
-
Iterator descendingIterator()
:返回该双向队列对于的迭代器,该迭代器以逆向顺序迭代队列元素。
-
Object getFirst()
:获取,但不删除队列中第一个元素。
-
Object getLast()
:获取,但不删除双向队列的最后一个元素。
-
boolean offerFirst()
:将指定元素插入该队列开头。
-
boolean offerLast()
:将指定元素插入该队列的末尾
-
Object peekFirst()
:获取,但不删除该队列开头的元素,如果队列为空,返回null。
-
Object peekLast()
:获取,但不删除该队列末尾的元素,如果队列为空,返回null。
-
Object pollFirst
:获取并删除该队列开头的元素,如果队列为空,返回null。
-
Object pollLast
:获取并删除该队列末尾的元素,如果队列为空,返回null。
-
Object pop()
:pop出该双向队列所表示栈中第一个元素。
-
void push(Object e)
:将一个元素push进该双向队列所表示的栈中(即该双向队列的头部)。
-
Object removeFirst()
:获取并删除该双向队列的第一个元素。
-
Object removeFirstOccurrence(Object o)
:删除该双向对队列的第一次出现的元素o。
-
Object removeLast()
:获取并删除该双向队列的最后一个元素。
-
Object removeLastOccurrence(Object o)
:删除该双向对队列的最后一次出现的元素o。
从上面可以看出LinkedList不仅可以当作双向队列使用,也可以当成“栈”使用,因为还包含pop和push方法。除此之外还可以当成List集合使用,因为实现了List接口。
LinkedList:链表形式保存集合中元素,插入、删除占优势,随机访问集合性能较差。
ArrayList、Vector:以数组形式保存集合中元素,随机访问集合元素有较好性能,插入性能较差。Vector实现线程同步,所以各方面性能都有所下降。
& | 实现机制 | 随机访问排名 | 迭代操作排名 | 插入操作排名 | 删除操作排名 |
---|---|---|---|---|---|
数组 | 连续内存区保存元素 | 1 | 不支持 | 不支持 | 不支持 |
ArrayList | 内部以数组保存 | 2 | 2 | 2 | 2 |
Vector | 内部以数组保存 | 3 | 3 | 3 | 3 |
LinkedList | 内部以链表保存 | 4 | 1 | 1 | 1 |
1.3.2 PriorityQueue实现类
PriorityQueue是一个比较标准的队列实现类,之所以说它是比较标准的队列实现,而不是绝对标准的队列实现是因为:PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排列。因此当调用peek方法或者poll方法来取队列中的元素时,并不是取出最先进入队列的元素,而是取出最小的元素。
PriorityQueue不允许插入null元素,它还需要对队列元素进行排序。
-
两种排序方式:
-
- 自然排序:采用自然排序必须实现Comparable接口,且一个集合中应该是同一个类的对实例,否则会产生ClassCastException异常。
-
- 定制排序:创建PriorityQueue队列时,传入一个Comparator对象,该对象赋值对队列中所有元素排序。采用定制排序不需要实现Comparable接口。
1.4 Map
Map集合也像一个罐子,只是它里面的每项数据都由两个值组成(Key-value)。
官方文档
6
-
常用方法:
-
void clear()
:删除该Map对象中所有key-value对。
-
boolean containsKey(Object key)
:查询Map中是否包含一个key,若包含,返回true。
-
boolean containsValue(Object value)
:查询Map中是否包含一个或者多个value,若包含,返回true。
-
Set entrySet()
:返回Map中所包含的key-value对所组成的Set集合,每个集合都是Map.Entry(Entry是)Map的一个内部类。
-
Object put(Object key,Object value)
:添加一个key-value对,如果当前Map中已有一个与该Key相等的Key-value对,则新的key-value对会覆盖原来的key-value对。
-
void putAll(Map m)
:指定Map中的key-value对复制到本Map中。
-
Object remove(Object key)
:删除指定key的key-value对,并返回被删除key所关联的value,如果该key不存在,返回null。
-
int size()
:返回Map中的key-value对数。
-
Collection values()
:返回Map所有的value组成的Collection。
Map接口的一个内部接口Entry,三个方法:
-
K getKey
:返回Entry包含的key值。
-
V getValue()
:返回Entry包含的value值。
-
-
V setValue(V value)
:设置该Entry里面包含的value值,并返回新设置的value值。
-
1.4.1 HashMap和Hashtable实现类
HashMap和Hashtable都是Map接口的典型实现类,他们完全类似于ArrayList和Vector的关系:Hashtable(Vector)是个古老的类,Hashtable从JDK1.0就出现了,我们从这个类的命名就可以看出很不规范,但是后面因为太多地方用到Hashtable了,改动会影响很大,因此并没有使用驼峰命名。
-
Hashtable和HashMap存在两点典型区别:
-
- Hashtable是一个线程安全的Map实现的,但HashMap是线程不安全的,因此HashMap性能会比Hashtable更好些。
-
- Hashtable不允许使用null作为key和value,如果使用null放入Hashtable中,会引发NullPointerException异常;但HashMap语序存在一个为null的Key,多个为null的value。
判断相等
-
- HashMap和Hashable判断key相等的标准是通过equals比较返回true,并且hashCode值也相等。
-
- HashMap和Hashable判断value相等的标准是通过equals比较返回true即可。
尽量不要使用可变对象作为HashMap、Hashtable的key,如果一定要这么做,就不会修改这个key的可变对象。
HashMap有一个子类LinkedHashMap,这个类使用双向链表维护key-value对的次序,迭代顺序与key-value对插入的顺序一致,也因为这个它比HashMap性能低。
Properties类是Hashtable类的子类,该类在处理文件属性(Windows操作平台上的ini文件就是一直属性文件),也可以吧Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入文件属性,也可以吧属性文件中的属性名=属性值加载到Map对象中由于属性文件的属性名、属性值只能是字符串类型,所以Properties里的key和value都是字符串类型。
Properties三个方法:
修改
String getProperty(String key)
Searches for the property with the specified key in this property list.String getProperty(String key, String defaultValue)
Searches for the property with the specified key in this property list.Object setProperty(String key, String value)
Calls the Hashtable method put.
读写
1.4.2 SortedMap接口和TreeMap实现类
SortedMap接口是Map接口的一个子接口,TreeMap是SortedMap的一个的实现类。
-
TreeMap的两种排序方式
-
- 自然排序:TreeMap的Key必须实现Comparable接口,且所有Key为1同类型,否则出现ClassCastException。
-
- 定制排序:创建TreeMap时,传入一个Comparator对象,赋值对TreeMap中所有key进行排序。定制排序不要求实现Comparable接口。
源码构造器:
-
构造器官方文档
-
详细方法及其他信息查看 TreeMap官方文档
1.4.3 WeakHashMap实现类
WeekHashMap是Map的一个实现类,同时继承了AbstractMap类。HashMap的key保留是对实际对象的强引用,也就是说这个对象不会被销毁,也不会自动删除。而WeakHashMap是一种弱引用,这意味着如果该HashMap对象所有Key所引用的对象没有被其他类强引用时,很有可能会被垃圾回收机制回收,也可能会自动删除这些key对应的key-value对象。
如果使用WeakHashMap的key保留对象的弱引用,就不要让该对象有其他的强引用了,不然也就失去了WeakHashMap的意义了。
1.4.4IdentityHashMap实现类
Identity类时Map接口的一个实现类,这个类与HashMap相似。不同点在于HashMao批判两个对象相等是通过equals
判断,而IdentityHashMap则是通过==
判断两个对象相等。
1.4.5 EnumMap实现类
EnumMap是AbstractMap这个抽象类的子类。这个类的所有key都是同个枚举类中的枚举值。创建EnumMap时,必须显式或者隐式指定它对应的枚举值。
EnumMap内部以数组的形式保存,所以这种实现形式十分紧凑、高效。
根据key的自然排序(枚举值在枚举类中定义的顺序)来维护key-value的顺序。可以通过keySet()
,entrySet()
,values()
方法遍历。
EnumMap不允许使用null作为key值,但是可以运行使用null作为value值。
-
构造方法官方文档
-
从文档可以看出,在创建EnumMap对象时,就必须显式或者隐式指定枚举值关联对象。
1.5 HashSet和HashMap的性能选择
hash表里面可以存储元素的位置称为“桶(bucket)”,一般情况下,一个桶是存储一个元素的。当然在发生hash冲突(存在相同的hash值)时,一个桶就会存储多个元素。实际上,hash算法根据hashCode值直接找到桶的存储位置,继而才桶中取出该元素,因此,一个桶中只存取一个元素性能是最好的。
HashSet、HashMap、Hashtable都是使用hash算法决定其元素的存储的(对于HashMap来说是key的存储)。
-
hash表包含的属性:
-
- 容量(capacity):创建hash表时,桶的数量。
-
- 初始化容器(initial capacity):创建hash表时,桶的数量。HashMap和HashSet都运行在构造器中指定初始化量。
-
- ** 尺寸(size)**:当前hash表中记录的数量。
-
- 负载因子(load factor):负载因子=size/capacity。负载因子为0,表示该hash表为空。轻负载的hash表具有冲突少、适宜插入与查询的特点。
1.6 操作集合工具类Collections
Collections是一个操作Set、List、Map等集合的工具类。有排序,查询,修改等操作。这些操作方法都是静态的。
1.6.1排序操作
- reverse(List<?> list)
Reverses the order of the elements in the specified list.- shuffle(List<?> list)
Randomly permutes the specified list using a default source of randomness.- sort(List list)
Sorts the specified list into ascending order, according to the natural ordering of its elements.- static void sort(List list, Comparator<? super T> c)
Sorts the specified list according to the order induced by the specified comparator.- static void swap(List<?> list, int i, int j)
Swaps the elements at the specified positions in the specified list.
其他操作参考官方文档
1.6.2 同步控制
Collections类提供了多个synchronizedXxx方法,返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题。
HashSet、ArrayList、HashMap线程不安全的。如果超过一条线程试图访问就会出错。
//示例创建同步集合对象
List list=Collections.synchronizedCollection(new ArrayList());
1.6.4 设置不可变集合
- emptyXxx():返回一个空的不可变的集合对象。
- singletonXxx():返回一个包含指定对象(只有一个或一项元素)的、不可变的集合对象,可以是List、Set、Map。
- unmodifyableXxx():返回指定集合对象的不可变视图,可以是List、Set、Map。
1.7 繁琐的接口Enumeration
Enumeration即可是Iterator迭代器的古老版本,JDK1.0就开始存在,(Iterator是JDK1.2出现的)。
-
只有两个方法:
-
hasMoreElements:如果此迭代器还有剩下的元素,返回true。
nextElement返回迭代器的下一个元素。如果还有的话,没有抛出异常。
- Enumeration保留是因为照顾以前那些古老的程序。
- ArrayList、HashMap等不支持Enumeration迭代器操作,Enumeration迭代器只适合变量Vector、Hashtable这样古老的集合。