什么是集合:
Java中的集合,就是保存对象的集合。是一种只能用来保存对象的集合。
Java集合包括四种:Set、List、Queue、Map。
其中Set代表无序、不可重复的集合。List代表有序、重复的集合。Map代表具有映射关系的集合。Queue代表一种队列集合实现。
Java中的集合类主要有两个接口派生而来:Collection接口和Map接口。
Collection接口是List接口、Queue接口、List接口的父接口。
该接口里定义的方法可以用于操作Set集合、Queue集合和List集合。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():返回集合是否为空。
Iterator iterator():返回Iterator对象,用于遍历集合里的元素。
boolean remove(Object o):删除集合中的指定元素o。当集合中包含一个或多个元素o时,该方法只删除第一个符合条件的元素,返回true。
boolean removeAll(Collection c):从集合中删除集合c所包含的元素,如果删除一个或一个以上元素,返回true。
boolean retainAll(Collection c):从集合删除集合c中不包含的元素。
int size():返回集合中元素的个数。
Object[] toArray():该方法把集合转化为一个数组,所有集合元素变为相应的数组元素。
package Suanfa;
import java.util.*;
public class Collection1 {
public static void main(String[] args) {
Collection c=new ArrayList();
c.add(12);
c.add(9);
c.add(23);
c.remove(23);
System.out.println(c);//[12,9]
System.out.println(c.contains(12));//true
Collection d=new HashSet();
d.add(109);
d.add(102);
d.add(12);
System.out.println(c.containsAll(d));//false
System.out.println(c.removeAll(d));//true
System.out.println(c);//[9]
c.add(11);
c.add(109);
System.out.println(d.retainAll(c));//true从集合中删除集合c不包含的元素
System.out.println(d);//[109]相当于集合c和d的交集。
//d.clear();
c.toArray();//将集合c变成数组
System.out.println(c);
}
}
创建了两个不同的类型的Collection集合,c是ArrayList集合,d是HashSet集合。虽然实现类不同,但把他们当成Collection来使用可以实现Collection接口的所有方法。
Set集合
Set集合与Collection基本相同,没有提供额外的方法,但是Set集合不允许添加重复的元素,当使用add()方法添加相同的元素会返回false。
Set集合有三个典型的实现类:HashSet、TreeSet、EnumSet。
HashSet类
1不能保证元素的排列顺序,与添加顺序可能相同也可能不同。
2HashSet不是同步的,多个线程修改HashSet,必须通过代码保证同步。
3.HashSet可以为空。
HashSet判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。
package Suanfa;
import java.util.*;
public class Collection2 {
public static void main(String[] args) {
HashSet c=new HashSet();
c.add(new A());
c.add(new A());
c.add(new B());
c.add(new B());
c.add(new C());
c.add(new C());
System.out.println(c);
//结果为:[Suanfa.A@7852e922, Suanfa.B@1, Suanfa.B@1, Suanfa.C@2, Suanfa.A@4e25154f]
}
}
class A{
public boolean equals(Object obj) {
return true;
}
}
class B{
public int hashCode() {
return 1;
}
}
class C{
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return true;
}
}
即使两个A对象的equals返回都为true,两个B对象的hashCode()返回都是1,HashSet仍然把他们当做两个对象。
只有当equals返回相等,hashCode()返回值也相等,HashSet才把他们当成两个对象。
所以,如果需要重写equals方法,则也应该重写hashCode()方法,否则如果equals()返回相等,但是hashCode()返回不等,HashSet会把同一个元素存在Hash表的不同位置。使得两个对象都添加成功,这样不符合HashSet的规则。
如果hashCode()方法返回的hashCode值相同,但是equals返回false。会导致在该位置用链式存储结构来保存多个对象。HashSet访问集合元素是根据HashCode值来快速定位的,一个hashCode位置有两个元素,会导致访问性能的下降。
LInkedHashSet类
LInkedHashSet类是HashSet的子类
LInkedHashSet具有HashSet的特性,也是根据元素的hashCode值来决定元素的存储位置,但是是使用链表来维护元素的次序。元素的顺序与添加的顺序相同。
TreeSet类
TreeSet是SortedSet接口的实现类,TreeSet可以确保元素处于排序状态。
与HashSet集合相比,TreeSet还有如下方法:
Comparator comparator():如果TreeSet采用定制排序,则该方法返回定制排序所使用的Compator,如果采用自然排序,则返回null。
Object first():返回第一个元素。
Object last():返回最后一个元素。
Object lower(Object e):返回集合元素e之前的元素。
Object high(Object e):返回集合元素e之后的元素。
SortedSet subSet(from.to):返回从from到to之间的元素。
SortedSet headSet(Object to):返回小于to的元素子集。
SortedSet tailSet(Object from):返回大于from的元素子集。
EnumSet类
EnumSet是专门为枚举类设计的集合类,EnumSet中的所有元素必须是指定枚举类型的枚举值,在创建EnumSet显式或隐式地指定。EnumSet是有序的,是按照枚举类中的顺序。
EnumSet适用于批量操作,不能添加null元素。
EnumSet没有构造器创建该类的实例,程序应该通过它的类方法来创建EnumSet对象。
EnumSet allOf(Calss elementType):创建一个包含指定枚举类里所有枚举值的EnumSet集合。
EnumSet complementOf(EnumSet s):创建了一个与指定集合s相同的EnumSet集合,包含了枚举类中除s之外的元素。
EnumSet copyOf(Collection c):使用一个普通的集合来创建EnumSet类。
EnumSet copyOf(EnumSet s):创建一个与指定元素EnumSet具有相同元素类型、相同集合元素的EnumSet集合。
EnumSet noneOf(Class elementType):创建一个元素类为指定枚举类型的空EnumSet。
EnumSet of(E first,E...rest):创建一个或多个枚举值的EnumSet集合。
EnumSet range (E from,E to):创建一个从from到to的枚举范围内所有枚举值得EnumSet集合。
package Suanfa;
import java.util.*;
public class Collection3 {
public static void main(String[] args) {
EnumSet es1=EnumSet.allOf(Season.class);
System.out.println(es1);//[Spring, Summer, Fail, Winter]
EnumSet es2=EnumSet.copyOf(es1);
System.out.println(es2);//[Spring, Summer, Fail, Winter]
EnumSet es3=EnumSet.noneOf(Season.class);
es3.add(Season.Spring);
es3.add(Season.Fail);
System.out.println(es3);//[Spring, Fail]
EnumSet es4=EnumSet.of(Season.Summer,Season.Winter);
System.out.println(es4);//[Summer, Winter]
EnumSet es5=EnumSet.range(Season.Spring, Season.Fail);
System.out.println(es5);//[Spring, Summer, Fail]
EnumSet es6=EnumSet.complementOf(es5);
System.out.println(es6);//[Winter]
}
}
enum Season{
Spring,Summer,Fail,Winter
}
各Set实现类的性能分析
HashSet的性能比TreeSet性能好,因为TreeSet需要额外的红黑树算法来维护集合元素的次序。
HashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要慢一点,这是因为维护链表所带来额外开销造成的,但由于有LinkedHashSet,遍历会更快。
EnumSet是Set实现类中性能最好的,但只能保存同一个枚举类中的枚举值。
Set的三个实现类都是线程不安全的,如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了Set集合,则必须手动保证该Set集合的同步性。通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。并且该操作最好在创建时进行,以防止对Set集合的意外非同步访问。
List集合
List集合代表有序的、可重复的集合,集合中的每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来指定位置的集合元素,List集合默认按元素的添加顺序设置元素的索引号。
List接口的方法:
void add(int index,Object element):将元素element插入到索引为index处。
boolean addAll(int index,Collection c):将集合c中的所有元素添加到index处。
Object get(int index):返回集合index处的元素。
int indexOf(Object o):返回对象o在集合第一次出现的索引位置。
int lastIndexOf(Object o):返回对象o在集合最后一次出现的索引位置。
Object remove(int index):删除Index处元素,并返回。
Object set(int index,Object element):将Index处的元素替换成element,并返回被替换的旧元素。
List subList(int fromIndex,int toIndex):返回从索引fromIndex(包含)到toIndex(不包含)的所有元素组成的子集合。
void replaceAll(unaryOperator operator):根据operator指定的计算规则重新设置List集合的所有元素。
void sort(Comparator c):根据Comparator参数对List集合元素进行排序。
package Suanfa;
import java.util.*;
public class Collection4 {
public static void main(String[] args) {
List books=new ArrayList();
books.add(new String("hello"));
books.add(new String("Thank you"));
books.add(new String("Thank you very much!"));
System.out.println(books);//[hello, Thank you, Thank you very much!]
books.add(1,new String("how are you?"));
for(int i=0;i<2;i++) {
System.out.println(books.get(i));//hello
//how are you?
}
books.add(new String("Thank you"));
System.out.println(books);//[hello, how are you?, Thank you, Thank you very much!, Thank you]
System.out.println(books.indexOf(new String("Thank you")));//2
System.out.println(books.lastIndexOf(new String("Thank you")));//4
books.set(0, new String("hi"));
List book2=books.subList(0, 2);
System.out.println(book2);//[hi, how are you?]
books.remove(new String("Thank you very much!"));
System.out.println(books);//[hi, how are you?, Thank you, Thank you]
books.sort((o1,o2)->((String)o1).length()-((String)o2).length());
System.out.println(books);//上程序采用Lambda表达式实现了comparator接口,控制元素从小到大排序。
//[hi, Thank you, Thank you, how are you?]
//books.replaceAll(ele->((String)ele).length());//实现UnaryOperator接口
//System.out.println(books);//[2,9,9,12]
ListIterator lit=books.listIterator();
while(lit.hasNext()) {
System.out.println(lit.next());
lit.add("--------");
}
System.out.println("--分隔符--");
while(lit.hasPrevious()) {
System.out.println(lit.previous());
}
}
}
实际List集合中并未包含该字符串对象,List集合中添加的字符串对象时,添加的是通过new关键字创建的新字符串对象。indexOf(new String("hello"))也是创建的新对象,和原有对象不是一个对象,通过equals比较是相同内容。
Set只提供一个iterator()方法,List还额外提供一个listIterator方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,并提供了专门操作List的方法。
boolean hasPrevious():返回该迭代器关联的集合中是否还有上一个元素。
Object previous():返回该迭代器的上一个元素。
void add(Object e):在指定位置插入一个元素。
ArrayList和Vector实现类
ArrayList和Vector是List接口的两个典型实现类。ArrayList和Vector封装了一个动态允许再分配的Object[]数组。ArrayList和Vector使用initialCapacity参数来设置数组的长度,当元素超过时,会自动增加。当向ArrayList或Vector添加大量元素时,可以使用enusureCapacity(int minCapacity)方法一次性增加initailCapacity。当创建空的ArrayList或Vector时,默认长度为10。还提供一个方法trimToSize(),调整ArrayList或Vector使数组长度为元素个数。
ArrayList和Vector显著区别是:ArrayList是线程不安全的,Vector是线程安全的。
即使需要保证List集合线程安全,仍然不推荐使用Vector。后面会提供一个Collections类可以将ArrayList变成线程安全的。
Vector还提供一个Stack子类,用于模拟栈这种数据结构。提供如下方法:
Object peek():返回栈顶元素,不出栈。
Object pop():返回栈顶元素,并出栈。
void push(Object item):将一个元素压入栈。
Arrays.ArrayList与ArrayList
Arrays类中提供了asList(Object...a)方法,该方法可以把一个数组或指定个数的对象变成一个LIst集合,这个List集合既不是ArrayList实现类的实例也不是Vector实现例的实例,而是Arrays的内部类ArrayList的实例。
Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合中的元素,不可增删该集合中的元素。
package Suanfa;
import java.util.Arrays;
import java.util.List;
public class Collection5 {
public static void main(String[] args) {
List fixedList=Arrays.asList("hello","Thank you","Thank you very much");
System.out.println(fixedList.getClass());
fixedList.forEach(System.out::println);
//fixedList.add("g");//出现异常
}
}
Queue集合
Queue是用于模拟队列数据结构,队列是先进先出。有如下方法:
void add(Object e):将指定元素添加至队尾。
Object element():获取队列的头部元素,但不删除该元素。
boolean offer(Object e):将指定元素添加至队尾。当使用有容量限制的队列时,此方法比add(Object e)好。
Object peek():获取队列头部元素,但是不删除该元素,如果该队列为空则返回null。
Object poll():获取队列的头部元素并删除该元素,如果队列为空则返回null。
Object remove():获取队列的头部元素并删除该元素。
Queue接口有PriorityQueue实现类,有Deque接口,Deque代表一个双端队列,双端队列可以同时从两端来添加和删除元素,因此Deque实现类既可以当做队列使用,也可以当做栈来使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。
package Suanfa;
import java.util.*;
public class Collection6 {
public static void main(String[] args) {
PriorityQueue pq1=new PriorityQueue();
pq1.offer(6);
pq1.offer(12);
pq1.offer(-3);
System.out.println(pq1);//[-3,12,6]
System.out.println(pq1.poll());//[-3]
}
}
PriorityQueue是一个比较标准的队列实现类,不是按照队列元素的添加顺序,而是按照队列元素的大小进行重新排序。因此当调用peek()方法或者poll()方法取出队列的元素并不是取出最先进入的元素,而是最小的元素。
Deque接口与ArrayDeque实现类
双端队列,能够实现栈和队列的功能。
LinkedList实现类
LinkedList实现了List和Deque接口,可以根据索引来随机的访问集合中的元素。可以被当做双端队列来使用,因此可被当做队列也可被当做栈来使用。
LinkedList与ArrayList、ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部是以数组的形式保存集合中元素,而LinkedList内部以链表的形式保存集合元素。
各种线性表的性能分析
如果需要遍历List集合元素:对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,应该使用迭代器(Iterator)来遍历集合元素。
如果需要经常执行插入、删除操作来改变包含大量数据的List集合大小,可考虑使用LinkedList集合。
如果有多个线程需要同时访问List集合中的元素,可以使用Collections类将集合包装成线程安全的集合。