java的集合是一种特别有用的工具类,大体上分为:Set、List和Map三种体系。其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合(键值对)。JDK 1.5以后,Java增加了Queue体系集合,代表一种队列集合实现。
集合就是一种用来存放多个对象引用的容器。在JDK 1.5之前,会丢失存入集合中对象的数据类型,把所有对象都当做Object处理。1.5引入泛型之后,java集合便可以记住存入容器中的对象类型了(不用再强制转换)
概述
有人会说我们可以用数组来保存多个对象,但数组长度是不可变化的,因此就不适用于保存个数变化的数据,而且数组也无法保存具有映射关系(键值对)的数据。这时候我们就可以用到集合类,所有集合类都位于java.util包下。当数组被声明为某一类型时,只能存入此类型的值,而在集合中如果没有使用泛型便没有这一限制。
数组元素既可以存入基本类型的值,也可以存入对象的引用,而集合里只能保存对象的引用变量。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,它们包含了一些子接口或实现类。
Collection接口、子接口及其实现类:
Set、List接口是Collection接口派生的两个子接口,它们分别代表了无序集合和有序集合;Queue是java提供的队列实现,有点类似于List。
Map体系继承树:
Map中的Key是不可重复的,Value可以重复。
对于Set、List、Map三种集合,最常用的实现类为HashSet、ArrayList、HashMap。
Collection和Iterator接口
Collection接口定义:
package java.util;
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
//返回一个Iterator对象,用于遍历集合中的元素
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean removeAll(Collection<?> c);
//从集合中删除集合c中不包含的元素
boolean retainAll(Collection<?> c);
void clear();
boolean equals(Object o);
int hashCode();
}
Iterable接口定义:
package java.lang;
import java.util.Iterator;
/**
* @since 1.5
*/
public interface Iterable<T> {
Iterator<T> iterator();
}
Collection接口继承了Iterable接口,该接口返回一个Iterator(迭代器),用于遍历集合中的元素。 Iterator只能向后迭代。
示例:
//警告信息提示用户没有使用泛型来限制集合中元素的类型
@SuppressWarnings("unchecked")
public static void testCollection() {
Collection c = new ArrayList();
c.add("a");
//虽然集合中不能存入基本类型的值,但java支持自动装箱
c.add(9);
System.out.println(c.size()); //2
c.remove("a");
System.out.println(c.size()); //1
//所有Collection实现类都重写了toString()方法,用于输出集合中的所有元素
System.out.println(c.toString()); // [9]
System.out.println(c.toArray().length); //1
System.out.println(c.contains(9)); //true
c.add("深圳");
System.out.println(c); // [9, 深圳]
Collection c2 = new HashSet();
c2.add(8);
c2.add("深圳");
System.out.println("c2集合是否完全包含c集合:" + c2.containsAll(c)); //false
//c2集合减去c集合中的元素
c2.removeAll(c);
System.out.println(c2); // [8]
c2.clear();
System.out.println(c2); // []
//保留此c集合中那些也存在于c2集合中的元素
System.out.println(c); //[9, 深圳]
c.retainAll(c2);
System.out.println(c); // []
}
注意:普通情况下,当我们把对象存入集合中后,集合会忘记这个对象的类型,把所有集合元素都当成Object处理,JDK 1.5引入泛型之后,可以使用泛型来限制集合中元素的类型。
遍历集合元素的两种方法:
1、使用Iterator接口遍历集合元素
Iterator接口也是Java集合框架的成员,主要用于迭代访问Collection集合中的元素。
Iterator接口定义:
package java.util;
public interface Iterator<E> {
//是否有下一个元素
boolean hasNext();
//返回下一个元素
E next();
//删除集合里上一次next方法返回的元素
void remove();
}
示例:
@SuppressWarnings("unchecked")
public static void testIterator() {
//创建一个集合
Collection c = new HashSet();
c.add("中国");
c.add("广东");
c.add("深圳");
//获取c集合对应的迭代器
Iterator iter = c.iterator();
while(iter.hasNext()) {
String address = (String)iter.next();
//广东 深圳 中国 从输出结果可以看到Set中的元素是无序的
System.out.println(address);
if(address.equals("广东")) {
iter.remove();
}
//Iterator并不是把集合元素本身传给了替代变量,修改替代变量的值对集合元素没有任何改变
address = "香港";
}
System.out.println("------------------------");
System.out.println(c);
}
注意:使用Iterator迭代集合过程中,不可修改集合元素,否则会引发异常。并且Iterator只能向后迭代
2、使用Foreach循环遍历集合元素
@SuppressWarnings("unchecked")
public static void testForeach() {
Collection c = new HashSet();
c.add(1);
c.add(2);
c.add(3);
//与Iterator迭代访问类似,此处的迭代变量也不是集合元素本身
for(Object o : c) {
Integer i = (Integer)o;
System.out.println(i);
//使用Foreach循环迭代集合元素时,该集合也不能被改变,否则引发ConcurrentModificationException异常
// if(i.equals(2)) {
// c.remove(i);
// }
}
}
Set接口
set集合中多个对象之间没有明显的顺序,set与Collection的结构基本上完全一样,不同在于set不能包含重复元素。 Set判断两个对象相同不是使用==运算符,而是根据equals方法。 也就是说主要两个对象用equals方法比较返回true,Set就不会接受这两个对象。
@SuppressWarnings("unchecked")
public static void testSet() {
Set set = new HashSet();
set.add(new String("spring"));
//两个字符串对象通过equals比较相等,添加失败
boolean flag = set.add(new String("spring"));
System.out.println(flag); //false
System.out.println(set); //spring
}
1、HashSet类
HashSet是Set接口的典型实现,大多数时候使用Set集合就是使用这个类。HashSet按hash算法来存储集合中的元素,因此具有很好的存储和查找性能。
HaseSet具有以下特点:
● 不能保证元素的排列顺序,顺序有可能发生变化
● HashSet不是同步的,如果多个线程访问同一个HashSet,要注意线程安全问题
● 集合元素值可以为null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值来决定该对象在HashSet中的存储位置。如果有两个元素通过equals方法比较返回true,但它们的hashCode方法返回值不相等,HashSet将会把它们存储在不同的位置。也就是说 HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode方法返回值也相等。
示例:
package cn.nevo.collection;
import java.util.HashSet;
public class TestHashSet {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
HashSet set = new HashSet();
set.add(new A());
set.add(new B());
set.add(new C());
set.add(new A());
set.add(new B());
set.add(new C());
/*[cn.nevo.collection.B@1,
cn.nevo.collection.B@1,
cn.nevo.collection.C@2,
cn.nevo.collection.A@c17164,
cn.nevo.collection.A@14318bb
]
*/
System.out.println(set);
}
}
class A {
//equals总是返回true,但没有重写hashCode方法,Hash会把A对象存储在不同的位置
@Override
public boolean equals(Object obj) {
return true;
}
}
class B {
//类b的hashCode总是返回1,但没有重写equals方法
@Override
public int hashCode() {
return 1;
}
}
class C {
//重写了类C的equal和hashCode方法,HashSet会把两个C对象当作同一个对象看待
@Override
public boolean equals(Object obj) {
return true;
}
@Override
public int hashCode() {
return 2;
}
}
注意:把一个对象放入HashSet中,如果 重写该对象对应类的equals方法,也应该重写其hashCode方法,其规则是:如果2个对象通过equals方法比较返回true时,这两个对象的hashCode也应该相同。如果两个对象通过equals比较为true,但hashCode方法返回不同的hashCode,这会导致HashSet将会把这两个对象保持在HashSet的不同位置,与Set集合的规则有点出入。
重写hashCode方法的基本规则:
● 当两个对象通过equals方法比较返回true时,这两个对象的hashCode应该相等
● 对象中用作equals比较标准的属性,都应该用来计算hashCode的值
2、LinkedHashSet
package java.util;
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
}
LinkedHashSet是HashSet的子类,该集合也是根据元素hashCode值来决定元素存储位置,但它同时 使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。
@SuppressWarnings("unchecked")
public static void testLinkedHashSet() {
LinkedHashSet set = new LinkedHashSet();
set.add("深圳");
set.add("东莞");
set.remove("东莞");
set.add("中山");
System.out.println(set); //[深圳, 中山]
}
3、TreeSet
TreeSet是SortedSet接口的实现, TreeSet可以确保集合元素处于排序状态,TreeSet并不是根据元素的插入顺序进行排序,而是根据元素的实际值来进行排序的。
SortedSet接口定义:
package java.util;
public interface SortedSet<E> extends Set<E> {
Comparator<? super E> comparator();
SortedSet<E> subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement);
E first();
E last();
}
示例:
@SuppressWarnings("unchecked")
public static void testTreeSet() {
TreeSet set = new TreeSet();
set.add(5);
set.add(2);
set.add(2);
set.add(10);
set.add(-9);
//输出处于排序状态的集合元素
System.out.println(set); //[-9, 2, 5, 10]
//输出集合中第一个元素
System.out.println(set.first()); // -9
//输出集合中最后一个元素
System.out.println(set.last()); // 10
//返回小于4的子集,不包含4
System.out.println(set.headSet(4)); // [-9, 2]
//返回大于5的子集,如果集合中包含5,子集中也包含5
System.out.println(set.tailSet(5)); // [5, 10]
//返回大于等于-10,小于5的子集
System.out.println(set.subSet(-10, 5)); // [-9, 2]
}
与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构对元素进行排序。
TreeSet支持两种排序方法:自然排序和定制排序。默认情况下TreeSet采用自然排序。
自然排序
TreeSet会调用集合元素的comparaTo(Object obj)方法来比较元素之间大小关系,然后将集合元素按升序排列,这就是自然排序。
Java提供了一个Comparable接口,该接口中定义了一个comparaTo(Object obj)方法,返回一个整数值,实现了该接口的类的对象就可以比较大小。如:obj1.comparaTo(obj2),如果该方法返回0,则表明这两个对象相等;返回正整数,则表明obj1大于obj2;返回一个负整数,则表明obj1小于obj2。
ComparaTo接口定义:
package java.util;
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
如果试图把一个对象添加进TreeSet时,则该对象的类必须实现CompareTo接口,否则程序将会出现异常
public class TestTreeSet {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
TreeSet set = new TreeSet();
//想TreeSet集合中添加两个TestTreeSet对象
set.add(new TestTreeSet());
//添加第二个元素时TreeSet就会调用该对象的compareTo方法与集合中其它元素作比较
//此处TestTreeSet没有实现Comparable接口,引发ClassCastException异常
set.add(new TestTreeSet());
System.out.println(set);
}
}
@SuppressWarnings("unchecked")
public class TestTreeSet implements Comparable {
public static void main(String[] args) {
TreeSet set = new TreeSet();
//想TreeSet集合中添加两个TestTreeSet对象
set.add(new TestTreeSet());
//添加第二个元素时TreeSet就会调用该对象的compareTo方法与集合中其它元素作比较
//此处TestTreeSet没有实现Comparable接口,引发ClassCastException异常
boolean flag = set.add(new TestTreeSet());
System.out.println(flag);
System.out.println(set);
}
@Override
public int compareTo(Object o) {
return 1;
}
}
向TreeSet集合中添加元素时,只有第一个元素可以无需实现CompareTo接口,后面添加的所有元素都必须实现CompareTo接口。另外向TreeSet中添加的应该是同一个类的对象,不然不好比较,否则也会引发ClassCastException异常。
对于TreeSet集合而言,它判断两个对象不相等的标准是:两个对象通过equals方法比较返回false,或通过compareTo比较没有返回0,即使两个对象是同一个对象,TreeSet也会把他们当成两个对象处理。
public class TestTreeSet2 {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
TreeSet set = new TreeSet();
Z z = new Z(10);
set.add(z);
boolean flag = set.add(z);
System.out.println(flag); // true 添加成功
//输出集合元素[cn.nevo.collection.Z@1fb8ee3,
//cn.nevo.collection.Z@1fb8ee3]
System.out.println(set);
//修改第一个元素的age属性值
((Z)set.first()).age = 20;
//第二个元素的属性值age也发生了改变 20
System.out.println(((Z)set.last()).age);
}
}
@SuppressWarnings("unchecked")
class Z implements Comparable {
int age;
public Z(int age) {
this.age = age;
}
@Override
public boolean equals(Object obj) {
return false;
}
@Override
public int compareTo(Object o) {
return 1;
}
}
定制排序
使用定制排序时,TreeSet对集合元素排序时不管集合元素本身大小,而是由Comparator对象负责集合元素的排序规则。
4、EnumSet
EnumSet是一个专为枚举类设计的集合类。EnumSet中的所有值都必须是指定枚举类型的枚举值,该枚举值在创建EnumSet时显式或隐式的指定。EnumSet的集合元素也是有序的,它以枚举值在Enum类中定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储。 不允许加入null元素,如果试图加入null元素,将抛出NullPointerException异常。EnumSet类没有暴露任何构造器来创建该类的实例,可以通过它提供的static方法来创建EnumSet对象。
public class TestEnumSet {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
//创建一个EnumSet集合,集合元素为Season枚举类的全部值
EnumSet enumset = EnumSet.allOf(Season.class);
//[SPRING, SUMMER, FALL, WINTER]
System.out.println(enumset);
//创建一个具有指定元素类型的空枚举 set
EnumSet enumset2 = EnumSet.noneOf(Season.class);
System.out.println(enumset2); // []
enumset2.add(Season.SPRING);
enumset2.add(Season.WINTER);
System.out.println(enumset2); //[SPRING, WINTER]
//以指定枚举值创建EnumSet集合
EnumSet enumset3 = EnumSet.of(Season.FALL, Season.SPRING);
System.out.println(enumset3); // [SPRING, FALL]
//创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set
EnumSet enumset4 = EnumSet.range(Season.SPRING, Season.WINTER);
System.out.println(enumset4); //[SPRING, SUMMER, FALL, WINTER]
//创建一个其元素类型与指定枚举 set相同的枚举 set,最初包含指定 set中所不包含的此类型的所有元素。
EnumSet enumset5 = EnumSet.complementOf(enumset4);
System.out.println(enumset5); // []
}
}
enum Season {
SPRING, SUMMER, FALL, WINTER
}
当试图复制一个Collection集合里面的元素来创建EnumSet时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值。
@SuppressWarnings("unchecked")
public static void testEnumSet() {
Collection c = new HashSet();
c.add(Season.SPRING);
c.add(Season.WINTER);
//复制Collection集合中的所有元素创建EnumSet
EnumSet set = EnumSet.copyOf(c);
System.out.println(set); //[SPRING, WINTER]
c.add("good");
c.add("nice");
//c集合里的元素不是全部都为枚举值,ClassCastException异常
set = EnumSet.copyOf(c);
}
Set总结:
HashSet和TreeSet是Set的两个典型实现。HashSet的性能总是比TreeSet好(特别是常用的添加、查询等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保存排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet要略微慢一点,这是由于维护链表所带来的额外开销造成的,又因为链表的存在, 遍历LinkedHashSet会更快。
EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
必须指出的是Set的三个实现类 HashSet、TreeSet和EnumSet都是线程不安全的,如果有多条线程同时访问一个Set集合,并有超过一条线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。此操作最好在创建时进行。
List接口
List接口代表一个有序集合,集合中每个元素都有其对应的顺序索引。 允许使用重复元素,可以通过索引来访问指定位置的集合元素。
List接口作为Collection接口的子接口,可以使用Collection接口中的全部方法。 相对于Set集合,List可以根据索引来插入、替换和删除集合元素。
List常规操作示例:
@SuppressWarnings("unchecked")
public static void testList() {
List list = new ArrayList();
list.add("广东");
list.add(new String("深圳"));
System.out.println(list); // [广东, 深圳]
list.add(1, "广东");
//广东 广东 深圳
for(int i = 0; i < list.size(); i ++) {
System.out.println(list.get(i));
}
//删除第二个元素
list.remove(1);
//判断指定元素在list集合中的位置
//深圳这个字符串对象都是通过new String来添加的,不是同一个对象,依旧返回1,
//List判断两个对象相等只要通过equals方法比较返回true即可
System.out.println(list.indexOf(new String("深圳")));
list.set(1, "佛山");
list.add("北京");
System.out.println(list); // [广东, 佛山, 北京]
//将list集合中第一个元素(包括)到第三个元素(不包括)截取子串
System.out.println(list.subList(0, 2)); // [广东, 佛山]
}
因为List集合可以根据位置索引来访问集合中的元素,因此集合List可以使用普通for循环来遍历集合元素。
List集合判断两个对象相等只要通过equals方法比较两个对象返回true即可。示例:
public class TestList {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("lili");
list.add("john");
System.out.println(list); //[tom, lili, john]
//两个对象相等便删除,对象相等的判断取决于equals返回true
System.out.println(new D().equals(list.get(0))); //true
//删除集合中D对象,将导致第一个元素被删除
list.remove(new D());
System.out.println(list); //[lili, john]
list.remove(new D());
System.out.println(list); //[john]
}
}
class D {
@Override
public boolean equals(Object obj) {
return true;
}
}
从运行结果可以看出,试图删除一个D对象,List将会调用该D对象的equals方法依次与集合元素进行比较,如果返回true,List将会删除该元素。D类重写了equals方法,总是返回true,所以每次从集合中删除一个D对象,总是删除List集合中的第一个元素。
与Set只提供了一个iterator()方法不同,List还额外提供了一个listIterator()方法。该方法返回一个ListIterator对象。ListIterator接口继承了Iterator接口,提供了专门操作List的方法。
ListIterator接口定义如下:
package java.util;
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
//返回该迭代器关联的集合是否还有上一元素
boolean hasPrevious();
//返回该迭代器的上一个元素
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
ListIterator与普通Iterator比较可以看到 ListIterator增加了向前迭代的功能(Iterator只能向后迭代) ,而且ListIterator还可以通过add方法向List集合中添加元素(Iterator只能删除元素)。示例:
@SuppressWarnings("unchecked")
public static void testList2() {
String book[] = {"spring", "winner", "summer"};
List list = new ArrayList();
for(int i = 0; i < book.length; i ++) {
list.add(book[i]);
}
System.out.println(list);
ListIterator iter = list.listIterator();
while(iter.hasNext()) {
System.out.println(iter.next());
//使用add方法向上一次迭代元素的后面添加一个新元素
iter.add("fall");
}
//此时的list集合[spring, fall, winner, fall, summer, fall]
System.out.println(list);
//开始反向迭代,依次打印fall、summer、fall、winner、fall、spring
//hasPrevious()如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
while(iter.hasPrevious()) {
//返回列表中的前一个元素。
System.out.println(iter.previous());
}
}
1、ArrayList和Vector实现类
ArrayList和Vector作为List接口的两个典型实现,完全支持List接口的全部功能。
ArrayList和Vector类都是基于数组实现的List类,其内部封装了一个动态再分配的Object[]数组。 每个ArrayList或Vector对象都有一个capacity属性,这个属性表示它们所封装的Object[]数组的长度,当添加元素超过长度时,capacity会自动增长。如果向ArrayList集合或Vector集合中添加大量元素时,可以使用ensureCapacity方法一次性的增加capacity。这可以减少增加重分配的次数,从而提高性能。如果创建ArrayList或Vector集合时没有指定capacity属性,其默认值为10。
ArrayList和Vector在用法上几乎完全相同。Vector是一个古老的集合(JDK 1.0就有了),那时候Java还没有提供系统的集合框架,所以Vector中有一些方法名很长的方法。JDK 1.2之后,java提供了系统的集合框架,将Vector改为List接口的实现,作为List实现之一,Vector中有一些重复的方法,方法名短的属于后面新增的。除此之外ArrayList和Vector的显著区别是:ArrayList是线程不安全的。Vector集合是线程安全的,无须程序保证该集合的同步性。 因为Vector是线程安全的,所以比ArrayList性能低,我们也可以使用Collections工具类,将一个ArrayList变成线程安全的。
Vector还提供了一个Stack子类,用于模拟“栈”这种数据结构,后进先出,与java中其它集合一样,进栈出栈的都是Object。
2、Stack类定义如下:
@SuppressWarnings("unchecked")
public static void teststack() {
Stack s = new Stack();
s.push("1");
s.push("2");
s.push("3");
s.add("4");
s.add("5");
System.out.println(s); //[1, 2, 3, 4, 5]
//访问第一个元素,但并不将其pop出栈
System.out.println(s.peek()); // 5
System.out.println(s); //[1, 2, 3, 4, 5]
//访问第一个元素,将其pop出栈
System.out.println(s.pop()); // 5
System.out.println(s); // [1, 2, 3, 4]
}
除此之外,List还有一个LinkedList的实现,它是一个基于链表实现的List类,对于顺序访问集合中的元素进行了优化,特别是 插入、删除元素时速度非常快。因为LinkedList即实现了List接口,又实现了Deque接口(双向队列)
3、固定长度的List
有一个操作数组的工具类Arrays。它有一个asList方法,public static <T> List<T> asList(T... a),返回一个受指定数组支持的固定大小的列表,此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素: 如 List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
@SuppressWarnings("unchecked")
public static void testArrays() {
List fiexdList = Arrays.asList("spring", "winner");
System.out.println(fiexdList); //[spring, winner]
System.out.println(fiexdList.getClass()); //Arrays$ArrayList
//试图增加删除元素都将引发UnsupportedOperationException异常
fiexdList.add("fall");
fiexdList.remove("spring");
}
通过asList转换为的这个集合既不是ArrayList实现类的实例,也不是Vector实现类的实例, 而是Arrays的内部类ArrayList的实例。 Arrays.ArrayList是一个固定长度的List集合, 程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。
4、LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
LinkedList类是一个比较特殊的类,它即实现了List接口,可以根据索引来随机访问集合中的元素。除此之外,它又实现了Deque接口,Deque接口是Queue接口的子接口,它代表一个双向队列,该接口中定义了一些可以双向操作队列的方法,所以,LinkedList不仅可以当成双向队列使用,也可以当成栈使用,因为该类里还包含了pop(出栈)和push(入栈)两个方法。 LinkedList通常用法示例:
@SuppressWarnings("unchecked")
public static void testLinkedList() {
LinkedList books = new LinkedList();
//将字符串元素加入到队列的尾部
books.offer("深圳");
//将一个字符串元素入栈
books.push("广东");
//将字符串元素加入到队列的头部
books.offerFirst("佛山");
System.out.println(books); //[佛山, 广东, 深圳]
for(int i = 0; i < books.size(); i ++) {
System.out.println(books.get(i));
}
//访问并不删除队列的第一个元素
System.out.println(books.peekFirst()); //佛山
//访问并不删除队列的最后一个元素
System.out.println(books.peekLast()); //深圳
//采用出栈的方式将第一个元素出队列
System.out.println(books.pop()); //佛山
//队列中第一个元素被删除
System.out.println(books); //[广东, 深圳]
//访问并删除队列的最后一个元素
System.out.println(books.pollLast()); //深圳
System.out.println(books); //[广东]
}
LinkedList与ArrayList、Vector的实现机制完全不同,ArrayList与Vector内部以数组的形式来保存元素,因此随机访问集合元素上有较好的性能。而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合时性能较差。但在 插入、删除元素时性能非常出色(只需改变指针所指地址即可)。实际上,Vector因为实现了线程同步,所以各方面性能都有所下降。
因为数组以一块连续内存区来保存所有数组元素,所以数组在随机访问时性能最好。所有内部以数组为底层实现的集合在随机访问时也有较好性能;而内部以链表作为底层实现的集合在插入、删除操作时有较好的性能。进行迭代操作时,以链表作为底层实现的集合也比以数组作为底层实现的集合性能要好。
总结:
使用List集合有以下建议:
● 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好。对于LinkedList集合,使用迭代器(Iterator)来遍历集合元素。
● 如果需要经常执行插入、删除操作来改变List集合的大小,则应该使用LinkedList集合,而不是ArrayList。使用ArrayList、Vector集合将需要经常重新分配内部数组的大小,其时间开销大。
● 如果有多条线程要同时访问List集合中的元素,可以考虑使用Vector这个同步实现。
Queue接口
Queue用于模拟队列这种数据结构,队列通常是“先进先出”的容器。通常,队列不允许随机访问对列中的元素。
Queue接口有两个常用的实现类:LinkedList和PriorityQueue。
PriorityQueue实现类
PriorityQueue是一个比较标准的队列实现类,PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排列。因此当调用peek方法或者poll方法来取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。从这个意义上来看,PriorityQueue已经违反了队列的基本原则:先进先出(FIFO)。
@SuppressWarnings("unchecked")
public static void testPriorityQueue() {
Queue queue = new PriorityQueue();
queue.offer(6);
queue.offer(-5);
queue.offer(9);
queue.offer(0);
//输出队列,并不是按照元素的加入顺序排列,而是按照元素的大小顺序排列
System.out.println(queue); //[-5, 0, 9, 6]
//访问队列中的第一个元素,其实就是队列中最小元素
System.out.println(queue.peek()); // -5
}
PriorityQueue不允许插入null元素,它还需要对队列元素进行排序,队列元素排序有两种方式:
● 自然排序:采用自然排序的PriorityQueue集合中的元素必须实现了Comparable接口,而且应该是同一个类的多个实例,否则可能导致ClassCastException异常。
● 定制排序:采用定制排序时不需要队列元素实现Comparable接口。
Map接口
Map用于保存具有映射关系的数据,因此Map集合里保存着两组值(键值对)。一组用于保存Map里的key,另外一组用于保存Map里的value,key和value都可以是任何引用类型的数据。 Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。可以根据给定的key找到Map中value的值。
如果把Map里所有的key放在一起来看,它们就组成了一个Set集合(key没有顺序,不允许重复),实际上Map确实包含了一个keySet方法,用于返回Map所有key组成的Set集合。Map子类和Set子类在名称上也惊人的相似。Map接口定义了大量的实现类,典型的实现有HashMap和HashTable等,以及HashMap的子类LinkedHashMap,还有SortedMap子接口及其实现类TreeMap,还有WeakHashMap、IdentityHashMap等。
Map中包括一个内部接口:Entry。该接口封装了一个key-value对,我们可以把Map理解成一个特殊的Set,只是该Set里面包含的集合元素是Entry对象,而不是普通对象。
HashMap和Mashtable实现类
HashMap和Mashtable都是Map接口的典型实现类,它们之间的关系完全类似于ArrayList和Vector的关系。Hashtable是一个古老的Map实现类,从JDK 1.0就开始出现,那是java还没有提供Map接口。除此之外,Hashtable和HashMap存在亮点典型区别:
● Hashtable是一个线程安全的Map实现,所以性能会较HashMap低一点。多线程访问同一个Map对象时使用Hashtable会更好
● Hashtable不允许使用null作为key和value,视图放入null,会引发NullPointerException,但HashMap可以使用null作为key或value。
示例:HashMap中存储空值
@SuppressWarnings("unchecked")
public static void testNullInHashMap() {
//HashMap中key不能重复,value可以重复,可以将null值当作key-value的值
HashMap hm = new HashMap();
hm.put(null, null);
hm.put(null, null);
hm.put("ha", null);
//{null=null, ha=null}
System.out.println(hm);
hm.put("ha", "b");
//{null=null, ha=b}
System.out.println(hm);
}
所有Map的实现类都重写了toString()方法。尽量少用Hashtable实现类,即使需要创建线程安全的Map实现类,也可以通过Collections工具类来把HashMap实现类编程线程安全的。
与HashSet类似的是,尽量不要使用可变对象作为HashMap、Hashtable的key,如果确实需要使用可变对象作为key,则尽量不要在程序中修改作为key的可变对象。
HashSet有一个子类LinkedHashSet,HashMap也有一个子类LinkedHashMap;该Map也是用双向链表来维护key-value对的次序,该 链表定义了迭代顺序,迭代顺序与key-value对的插入顺序保持一致。
@SuppressWarnings("unchecked")
public static void testLinkedHashMap() {
LinkedHashMap scores = new LinkedHashMap();
scores.put("语文", 89);
scores.put("数学", 98);
for(Object s : scores.keySet()) {
System.out.print("key:" + s + "---");
System.out.println("value:" + scores.get(s));;
}
}
Properties类
Properties类是Hashtable类的子类,用于处理属性文件。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。 由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的Key、value都是字符串类型 。
public static void testProperties() throws Exception {
Properties props = new Properties();
props.setProperty("username", "scott");
props.setProperty("pwd", "tiger");
//将Properties中的key-value对写入指定属性文件
props.store(new FileOutputStream("F:\\props.ini"), "comment line");
Properties props2 = new Properties();
props2.setProperty("address", "beijing");
//将"F:\\props.ini"文件中的属性名-属性值追加到props2中
props2.load(new FileInputStream("F:\\props.ini"));
System.out.println(props2);
}
Properties还可以把key-value以XML文件的形式保存,也可以从XML文件中加载属性名-属性值。
SortedMap接口和TreeMap实现类
正如Set接口派生出了SortedSet子接口和其实现类TreeSet一样,Map接口也派生了一个SortedMap子接口和其实现类TreeMap。
与TreeSet类似的是TreeMap也是基于红黑树对TreeMap中所有key进行排序,从而保证TreeMap中所有key-value对处于有序状态。TreeMap也有两种排序方式:自然排序、定制排序。
EnumMap实现类
EnumMap是一个与枚举类一起使用的Map实现。 EnumMap中所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式的指定它对应的枚举类。
EnumMap在内部以数组形式保存,这种实现形式非常高效、紧凑。EnumMap根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护key-value对的次序。 EnumMap不允许使用null作为key值,但运行使用null作为value值。 与创建普通Map有所区别的是,创建EnumMap时必须指定一个枚举类,将两者关联起来。
@SuppressWarnings("unchecked")
public static void testEnumMap() {
//创建EnumMap时必须指定一个枚举类,将两者关联起来。
EnumMap enummap = new EnumMap(Season.class);
//该EnumMap的所有key都必须是Season的枚举值
enummap.put(Season.SPRING, "春暖花开");
enummap.put(Season.WINNER, "夏日炎炎");
//{SPRING=春暖花开, WINNER=夏日炎炎}
System.out.println(enummap);
}
Enumeration接口
Enumeration接口是Iterator的古老版本,从JDK 1.0开始它就已经存在(Iterator从JDK 1.2才出现),Enumeration只有两个名字很长的方法:
package java.util;
public interface Enumeration<E> {
//此迭代器如果有剩下的元素则返回true
boolean hasMoreElements();
//返回迭代器的下一个元素,如果有的话
E nextElement();
}
Enumeration接口里的方法冗长,而且难以记忆,没有提供Iterator的remove方法。新的实现应该优先考虑使用 Iterator 接口而不是Enumeration 接口。之所以保留这个接口,主要是为了照顾那些古老的程序(程序中使用了Enumeration接口)。实际上,前面介绍的Vector(包括其子类Stack)、HashTable两个集合类,以及还有一些极少使用的BitSet,都是从JDK 1.0遗留下来的集合类,而Enumeration接口可用于遍历这些古老的集合类。对于ArrayList、HashMap等集合类,不再支持Enumeration迭代器操作。
public static void testEnumeration() {
Vector<String> v = new Vector<String>();
v.add("struts2");
v.add("mongodb");
Hashtable<String, Integer> hash = new Hashtable<String, Integer>();
hash.put("语文", 80);
hash.put("数学", 99);
//Enumeration迭代Vector
Enumeration<String> enumVector = v.elements();
while(enumVector.hasMoreElements()) {
System.out.println(enumVector.nextElement());
}
//Enumeration迭代Hashtable
Enumeration<String> enumHash = hash.keys();
while(enumHash.hasMoreElements()) {
System.out.println(hash.get(enumHash.nextElement()));
}
}
Collections工具类
Java提供了一个操作List、Set和Map等集合的工具类:Collections,该工具类提供了大量方法对集合元素进行排序、查找和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
示例:Collections工具类操作List集合。排序操作
public static void testCollections() {
List<Integer> list = new ArrayList<Integer>();
list.add(2);
list.add(-5);
list.add(4);
list.add(0);
//输出[2, -5, 4, 0]
System.out.println(list);
//将list集合的元素顺序反转
Collections.reverse(list);
//输出[0, 4, -5, 2]
System.out.println(list);
//将list集合元素按自然顺序排序
Collections.sort(list);
//[-5, 0, 2, 4]
System.out.println(list);
//将list集合元素按随机顺序排序
Collections.shuffle(list);
//每次输出不一样
System.out.println(list);
}
示例:Collections工具类操作List集合、查找替换操作
public static void testCollections2() {
List<Integer> list = new ArrayList<Integer>();
list.add(2);
list.add(-5);
list.add(4);
list.add(0);
//输出[2, -5, 4, 0]
System.out.println(list);
//最大元素 4
System.out.println(Collections.max(list));
//最小元素 -5
System.out.println(Collections.min(list));
//将list集合中的0使用1来替代
Collections.replaceAll(list, 0, 1);
System.out.println(list); //[2, -5, 4, 1]
//判断2在list集合中出现的次数
System.out.println(Collections.frequency(list, 2)); //1
//只有排序后的集合才可用二分法查询,如果没有对列表进行排序,则结果是不确定的
Collections.sort(list);
System.out.println(list); //[-5, 1, 2, 4]
System.out.println(Collections.binarySearch(list, 2)); //2
}
同步对象:
Collections类中提供了多个synchronizedXxx方法,该方法返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题。Java中常用集合实现类中的HashSet、ArrayList和HashMap都是线程不安全的。
设置不可变集合:
//设置不可变对象
@SuppressWarnings("unchecked")
public static void testUnmodifiable() {
//创建一个空的,不可改变的List对象
List unmodifiableList = Collections.emptyList();
//创建一个只有一个元素,切不可改变的Set对象
Set unmodifiableSet = Collections.singleton("setOne");
//创建一个普通Map对象
Map map = new HashMap();
map.put("语文", 80);
//返回普通map对象的不可变版本
Map unmodifiableMap = Collections.unmodifiableMap(map);
//下面任意一行代码都将引发java.lang.UnsupportedOperationException异常
unmodifiableList.add("测试1");
unmodifiableSet.add("测试2");
unmodifiableMap.put("数学", 90);
}