Java集合框架提供如下两个类型的容器: 1、集合(Collection): 存取单个元素的集合。 2、图/映射(Map): 存储键/值对(key-value)。
Collection 接口 和 AbstractCollection 类
Collection 接口是处理对象集合的根接口;
集合(Collection)
Java集合框架主要支持三种主要类型的集合: 1、规则集Set: 用以存储一组不重复的元素 2、线性表List: 用于存储一个有元素构成的有序集合,其中元素可以重复。 3、队列Queue: 用于存储使用先进先出方式处理的对象。
注意: 1、Java中的所有的集合框架的接口和类都定义在Java.util包内。 2、Java的集合框架中的所有的具体类都是实现了java.lang.Cloneable和java.io.Serializable接口的。所以,它们的实例都是可复制且可以序列化的
Collection接口和AbstractCollection类
定义:public interface Collection<E>extends Iterator<E>;
全部方法:
Collection 中的addAll();removeAll();retainALL()类似与集合中的并、差、交
Collection接口提供了在集合之中添加或删除元素的基本操作: 1、add方法给集合添加一个元素。 2、addAll方法会把指定集合中的所有元素添加到这个集合中。 3、remove方法从集合中删除一个元素。 4、removeAll方法会从集合之中删除指定集合中的所有元素。 5、retainAll方法保留即出现在当前集合中,也出现在指定集合中的元素。 以上所有的方法返回的都是boolean类型的值,如果执行方法会改变这个集合,那么返回的就是true。clear()方法返回值类型为void,单纯的删除集合中所有元素。 注意:方法addAll(), removeAll(), retainAll()类似于集合上的并、差、交运算。
Collection接口提供了多种查询操作: 1、size方法返回集合中元素的个数。 2、contains方法检测集合中是否包含指定的元素。 3、containsAll方法检测这个集合是否包含指定集合中的元素。 4、isEmpty方法检测这个集合是否为空。 Collection接口提供的toArray方法返回一个表示集合的数组。 集合可以是一个规则集,也可以是一个线性表。Iterator接口提供了对不同类型集合中的元素进行遍历的统一方法。
集合可以是一个规则集,也可以是一个线性表。Iterator接口提供了对不同类型集合中的元素进行遍历的统一方法。可以使用Collection接口中的Iterator()方法返回一个Iterator接口的实例: 1、它的next()方法顺序访问集合中的元素。 2、它的hasNext()方法用来检测迭代器中是否还有更多的元素。 3、它的remove()方法删除从迭代器返回的最后一个元素。
AbstractCollection类提供Collection接口的部分抽象方法的实现,它是一个抽象类。除了size方法和iterator方法之外,它实现了Collection接口的其他所有方法。 size方法和iterator方法在其相应的子类中给出具体实现。
规则集Set
Set接口扩展了Collection接口。它没有引入新的方法或常量,只是规定Set的实例不包含重复的元素。实现Set的具体类必须确保没有向这个规则集添加重复的元素。也就是说,在一个规则集中,一定不存在元素e1、e2使得e1.equals(e2)返回为true。 Set接口定义如下: public interface Set<E> extends Collection<E> AbstractSet是一个便利类,它扩展AbstractCollection类并且实现Set接口。它提供equals方法和hashCode方法的具体实现。一个规则集的hashCode(散列码)是这个集合中所有元素的hashCode的和。AbstractSet类没有实现size()方法和iterator()方法,因此它是一个抽象类。
Set接口的三个具体类是:散列类HashSet、链式散列集LinkedHashSet、树形集合TreeSet。
散列集合HashSet 可以用它的无参的构造方法来创建空的散列集,也可以由一个现有的集合创建散列集。默认情况下,初始容量为16而客座率(load factor)为0.75。如果知道集合大小,还可以手动指定初始容量和客座率。 客座率(load factor): 客座率的值必须在0.0到1.0之间。它是用来测量在增加Set的容量之前,该Set的饱满程度。当元素的个数超过了容量与客座率的乘积,容量就会自动翻倍。例如,如果容量是16而客座率是0.75。那么当尺寸达到12(16×0.75)的时候,容量将会翻倍到32。
HashSet可以存储互不相同的任何元素。 考虑到效率的因素,添加到Set中的对象必须以一种正确分散hashCode的的方式来实现hashCode()方法。如果两个相同类型对象完全相等,那么这两个对象对应的hashCode必须相同。由于HashSet可以存储类型不相同的元素,这将会带来一个问题:两个不同的对象可能会碰巧有相同的hashCode。怎么办?应当在实现hashCode()方法的时候尽量规避这种情况的出现。比如,Integer类的hashCode()方法返回它的int值;Character类的hashCode()方法返回这个字符的统一码,等等。HashSet中如果同时放入Integer和Character可能会导致hashCode冲突。
链式散列类LinkedHashSet
LinkedHashSet使用一个链表来扩展HashSet类,它支持对规则集合内的元素进行排序。 LinkedHashSet中的元素按照它们插入的顺序进行排序。 LinkedHashSet中的元素不能重复,插入重复的元素等效于只插入1次。 注意:如果不需要维护元素被插入的顺序,就应该使用HashSet,它会比LinkedHashSet更加高效。
树形集合TreeSet
集合扩展顺序: TreeSet 实现 NavigableSet接口,NavigableSet接口是SortedSet的子接口,SortedSet接口是Set接口的子接口。 SortedSet确保规则集合中的所有元素都是有序的。 TreeSet是实现了NavigableSet接口的一个具体类。为了创建TreeSet的一个对象,必须保证TreeSet中加入的对象类型是可以比较的。用两种方法可以实现: 1、使用Comparable接口 2、给Set中的元素指定一个比较器。这种方法定义的顺序称为是比较器顺序。比较器Comparator将在后面介绍。
创建一个TreeSet: //当使用语句new TreeSet的时候就从一个HashSet对象创建一个TreeSet对象并且将规则集中的元素排序一次 //每次给树形集合TreeSet添加元素的时候,其中的元素都会重新排序。 TreeSet<String> treeSet = new TreeSet<String>(set);
TreeSet中的方法: System.out.println("first(): " + treeSet.first());//返回TreeSet中第一个元素 System.out.println("last(): " + treeSet.last());//返回TreeSet中最后一个元素 System.out.println("headSet(\"New York\"): " + treeSet.headSet("New York"));//返回TreeSet中比"New York"小的那一部分 System.out.println("headSet(\"New York\"): " + treeSet.tailSet("New York"));//返回TreeSet中比"New York"大的那一部分
TreeSet中的方法: treeSet.first();//返回TreeSet中第一个元素 treeSet.last();//返回TreeSet中最后一个元素 treeSet.headSet("New York");//返回TreeSet中比"New York"小的那一部分 treeSet.tailSet("New York");//返回TreeSet中比"New York"大的那一部分 treeSet.lower("Paris");//小于Paris的第一个元素 treeSet.higher("Paris");//大于Paris的第一个元素 treeSet.floor("Paris");//小于等于Paris的第一个元素 treeSet.ceiling("Paris");//大于等于Paris的第一个元素 treeSet.pollFirst();//删除和返回树形集合TreeSet中的第一个元素 treeSet.pollLast();//删除和返回树形集合TreeSet中的最后一个元素
注意: 1、Java集合框架中的所有具体类都至少有两个构造方法:一个是创建空集合的无参构造方法,另一个是用某个集合对象来创建其实例的构造方法。 2、在需要对Set中的元素进行频繁的插入删除操作的的时候,最好使用HashSet,因为这样会降低程序的运行时间。当我们需要一个排好序的集合的时候,可以将这个HashSet作为TreeSet的构造方法的入参创建一个排好序的Set。
比较器接口Comparator
如果我们需要将元素插入到一个树集合中但是同时这些元素又不是Comparable接口的实例。这时候就可以通过java.util.Comparator来定义一个比较器。Comparator接口有两个方法:compare和equals。 public int compare(Object element1, Object element2) 如果element1小于element2,就返回一个负值;如果element1大于element2,就返回一个正值。相等返回0。 public boolean equals(Object element) 如果指定的对象也是一个比较器并且与当前比较器有相同的排序,则返回true。注意,在Object类中也定义了equals方法。所以,即使在你自定义的比较器类中没有实现equals方法,也不会出现编译错误。然而,有时候实现该equals方法会提高程序的执行效率,因为这样可以让程序快速判断两个不同的比较器是否有相同的顺序。
有时我们希望将元素插入到一个TreeSet中,但是这些元素可能不是也无法定义为java.lang.Comparable的实例。这时候可以自定义一个比较器Comparator来计算这些元素。 创建一个实现java.util.Comparator接口的类需要实现Comparator接口中的两个方法: 1、public int compare(Object element1, Object element2) 如果element1小于element2,就返回一个负值;如果element1大于element2就返回正值,否则返回0 2、public boolean equals(Object element) 如果指定的对象也是一个比较器,并且与这个比较器具有相同的排序,则返回true。由于Object类中也实现了equals方法。所以在自定义的Comparator中没有实现equals方法也不会出现编译错误。但是有时候实现equals方法能够让程序快速判断两个不同的比较器是否有相同的排序,从而提高运行效率。
public class GeometricObjectComparator implements Comparator<GeometricObject>, java.io.Serializable { @Override public int compare(GeometricObject o1, GeometricObject o2) { double area1 = o1.getArea(); double area2 = o2.getArea(); if (area1 < area2) { return -1; } else if (area1 > area2) { return 1; } else { return 0; } } }
线性表List
之前介绍的Set只能存储不重复的元素。为了允许在集合中存储重复的元素,需要使用List(线性表)。 List不仅可以存储重复的元素,还允许用户指定元素的存储位置。 用户通过下标可以访问List中特定位置的元素。 List接口扩展自Collection接口,以定义一个允许重复的有序集合。List接口增加了面向位置的操作,并且增加了一个能够双向遍历List的迭代器。
List中的方法: 1、方法add(index, element)用于指定下标index处插入一个元素element。 2、方法addAll(index, collection)用于在指定下标处插入一个集合collection。 3、方法remove(index)用于从线性表中删除指定下标index处的元素。 4、方法set(index, element)可以在指定下标index处设置一个新元素。 5、方法indexOf(element)用于获取指定元素在List中第一次出现的下标。 6、方法lastIndexOf(element)用于获取指定元素在List中最后一次出现时的下标 7、方法subList (fromIndex, toIndex)返回一个从fromIndex到toIndex-1的子list 8、方法listIterator()或者listIterator(startIndex)都会返回一个ListIterator的实例。ListIterator扩展了Iterator接口,以增加对线性表的双向遍历能力。
List中的方法: 1、方法add(index, element)用于指定下标index处插入一个元素element。 2、方法addAll(index, collection)用于在指定下标处插入一个集合collection。 3、方法remove(index)用于从线性表中删除指定下标index处的元素。 4、方法set(index, element)可以在指定下标index处设置一个新元素。 5、方法indexOf(element)用于获取指定元素在List中第一次出现的下标。 6、方法lastIndexOf(element)用于获取指定元素在List中最后一次出现时的下标 7、方法subList (fromIndex, toIndex)返回一个从fromIndex到toIndex-1的子list 8、方法listIterator()或者listIterator(startIndex)都会返回一个ListIterator的实例。ListIterator扩展了Iterator接口,以增加对线性表的双向遍历能力。
ListIterator中的方法: 1、方法add(element)用于将指定元素element插入到当前List中 如果Iterator接口中定义的next()方法返回非空,该元素将立即被插入到next()方法返回的元素之前;而且,如果previous()方法的返回值非空。该元素被立即插入到previous()方法返回的元素之后。 2、方法set(element)用于将next方法或者previous方法返回的最后一个元素替换为指定的元素。 3、next()、previous()、nextIndex()、previousIndex()方法分别返回Iterator中的下一个元素,前一个元素,下一个元素的下标,前一个元素的下标。 4、方法hasNext()用于检测迭代器向前遍历时是否有元素,而方法hasPrevious()用于检测迭代器向后遍历是否有元素。
ListIterator中的方法: 1、方法add(element)用于将指定元素element插入到当前List中 如果Iterator接口中定义的next()方法返回非空,该元素将立即被插入到next()方法返回的元素之前;而且,如果previous()方法的返回值非空。该元素被立即插入到previous()方法返回的元素之后。 2、方法set(element)用于将next方法或者previous方法返回的最后一个元素替换为指定的元素。 3、next()、previous()、nextIndex()、previousIndex()方法分别返回Iterator中的下一个元素,前一个元素,下一个元素的下标,前一个元素的下标。 4、方法hasNext()用于检测迭代器向前遍历时是否有元素,而方法hasPrevious()用于检测迭代器向后遍历是否有元素。
ArrayList和LinkedList是实现List接口的两个具体类。 ArrayList用数组来存储元素,这个数组是动态创建的。如果元素超过了数组的容量,就创建一个更大的数组,并将当前数组中的元素都复制到新的数组中。 而LinkedList在一个链表中存储元素。 如何选用ArrayList还是LinkedList? 一般来说,如果需要通过下标随机的访问元素,但是除了在末尾处之外,不能在其他位置插入或删除元素(或者不需要插入或删除元素),那么选用ArrayList将更加高效。 而如果需要在List中的任意位置插入元素,LinkedList将会是一个更好的选择。
ArrayList的创建方法: 1、调用无参构造方法ArrayList()创建一个带默认初始值容量的空列表。 2、调用ArrayList(collection)从现有集合collection创建一个数组列表。 3、调用ArrayList(initialCapacity)来创建一个指定初始容量为initialCapacity的列表。
LinkedList的创建方法: 1、调用无参构造方法LinkedList()创建一个默认的空列表。 2、调用LinkedList(collection)从现有集合collection创建一个数组列表。
注意: 1、为了从泛型类型的可变长参数表创建线性表,Java提供了静态的asList方法。这样,就可以使用下面的语句创建一个String类型的List和一个Integer类型的List。 List<String> list1 = Arrays.asList("red", "green", "blue"); List<Integer> list1 = Arrays.asList(1, 2, 3);
Collections接口
Java集合框架在Collections类中提供了用于对线性表进行排序的静态方法。 Collections还包含用于线性表的binarySearch,reverse,shuffle,copy和fill方法。以及用于集合的max,min,disjoint以及frequency方法。
可以使用方法sort(list)来对线性表中的元素排序,这里的比较方法使用的是Comparable接口中的compareTo方法。也可以自定义一个Comparator来比较。 List<String> list = Arrays.asList("tree", "apple", "java"); Collections.sort(list, Collections.reverseOrder()); System.out.println(list);// 逆序排列 Collections.sort(list); System.out.println(list);// 顺序排列
可以使用方法binarySearch(list, element)来对线性表中的元素排序,使用binarySearch方法之前,注意必须以顺序排列数组中的元素。如果list中的元素不是以顺序排序的,则会出现错误。 System.out.println("apple's index:" + Collections.binarySearch(list, "apple")); System.out.println("alpha's index:" + Collections.binarySearch(list, "alpha"));
可以使用方法reverse()将list1中元素的顺序逆反过来 List<Integer> list1 = Arrays.asList(1, 2, 10, 4, 3, 7); System.out.println(list1); Collections.reverse(list1);// 逆反list1 System.out.println(list1);
可以使用方法shuffle(list)来对线性表中的元素进行随机的重新排序 List<Integer> list1 = Arrays.asList(1, 2, 10, 4, 3, 7); for (int i = 1; i <= 3; i++) { List<Integer> tempList = new ArrayList<Integer>(list1); Collections.shuffle(tempList);// 打乱tempList System.out.println("shuffle time: " + i + ". list1: " + tempList); } for (int i = 1; i <= 3; i++) { List<Integer> tempList = new ArrayList<Integer>(list1); // 使用随机数Random(20),打乱tempList,结果拥有相同的元素序列 Collections.shuffle(tempList, new Random(20)); System.out.println("shuffle time: " + i + ". list1: " + tempList); }
可以使用方法copy(det, src)将源线性表中的所有元素以同样的下标复制到目标线性表中。 目标线性表必须和源线性表等长。 如果源线性表的长度大于目标线性表,那么,源线性表中的剩余元素不会受到影响。 List<Integer> list2 = Arrays.asList(1, 2, 3, 4); List<Integer> list3 = Arrays.asList(31, 32); Collections.copy(list2, list3); System.out.println("list2: " + list2);// [31, 32, 3, 4] 注意:copy方法是浅复制。复制的只是线性表中元素的引用。
可以使用方法nCopies(int n, Object o)创建一个包含指定对象的n个副本的不可变线性表。 List<Date> list4 = Collections.nCopies(5, new Date()); System.out.println("list4: " + list4); 如果执行list4.add(new Date());会产生一个java.lang.UnsupportedOperationException。这里注意使用nCopies()方法产生的list是不可变的,不能在该线性表中添加,删除,或者更新元素。nCopies()方法产生的list中所有的元素都有相同的引用。
max方法和min方法: 可以使用max方法和min方法找出集合中的最大元素和最小元素。 集合中的元素必须使用Comparable接口或者Comparator接口。 List<String> list6 = Arrays.asList("red", "green", "blue"); System.out.println("max in list6 " + Collections.max(list6)); System.out.println("min in list6 " + Collections.min(list6));
如果两个集合中没有相同的元素,那么方法disjoint返回true Collection<String> collection1 = Arrays.asList("red", "blue"); Collection<String> collection2 = Arrays.asList("red", "green"); Collection<String> collection3 = Arrays.asList("cyan", "tan"); System.out.println("collection1 disjoint collection2: " + Collections.disjoint(collection1, collection2)); System.out.println("collection2 disjoint collection3: " + Collections.disjoint(collection2, collection3));
使用方法frequency(collection, element)可以找出集合中某元素的出现次数 List<String> list7 = Arrays.asList("blue", "green", "green"); System.out.println("green frequency: " + Collections.frequency(list7, "green")); //控制台输出2
这里我们给出一个测试Set和List性能的方法,如下所示: private static long getTestTime(Collection<Integer> c, int size) { long startTime = System.currentTimeMillis(); List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < size; i++) { list.add(i); } Collections.shuffle(list); for (int e : list) { c.add(e); } long endTime = System.currentTimeMillis(); return endTime - startTime;
向量类Vector和栈类Stack
Java集合框架是在Java2引入的。Java2之前的版本也支持一些数据结构,其中就有向量类Vector与栈类Stack。为了适应Java的集合框架,Java2对这些类进行了重新设计。 除了包含用于访问和修改向量的同步方法之外,Vector类与ArrayList是一样的。同步方法用于防止两个或者多个线程同时访问某个向量的时候引起数据损坏。对于许多不需要使用同步的应用程序来说,使用ArrayList比使用Vector更高效。 Stack栈类也是在Java 2之前引入的。方法empty()与方法isEmpty()的功能是一样的。方法peek()可以返回栈顶的元素而不删除它。方法pop()返回栈顶的元素并删除。方法push(element)将指定元素添加到Stack中。方法search(element)检测指定元素是否在栈内。
队列和优先队列
队列是一种先进先出的数据结构。元素被追加到队列的末尾,然后从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,拥有最高优先级的元素首先被删除。
双端队列Deque和链表LinkedList
LinkedList实现了Deque接口,Deque接口又扩展了Queue接口。Deque是“double-ended queue”的简称,通常发音是“deck”。Deque接口用附加的从队列两端插入和删除元素的办法扩展Queue接口。方法addFirst()、removeFirst()、addLast()、removeLast()、getFirst()、getLast()都被定义在Deque接口中。 PriorityQueue类实现一个优先队列。默认情况下,优先队列使用Comparable接口以元素的自然顺序排列。拥有最小数值的元素被赋予最高优先级,因此最先从队列中删除。如果几个元素具有相同的最高优先级,其中的任意一个元素都可以从列表中被删除。也可以使用构造方法PriorityQueue(initialCapacity, comparator)中的Comparator来指定一个顺序。
Map
假设程序中需要存储100万个市民的姓名和他们的身份证号,而且需要使用身份证号来搜索对应人的姓名。那么,针对这个任务的最有效的数据结构就是映射(Map)。 Map是一种依照Key-Value值存储元素的容器。Key就像是下标。在List中,下标是整数;而在Map中键值Key可以市任意类型的对象(Integer,String)。 Map中的键值Key必须保持唯一性,即不能有重复的键值,每个Key对应一个Value。一个键值Key和它对应的值Value构成一个item。真正在Map存储的是这个item。
Map接口提供了查询、更新、和获取集合的值和集合的键值的方法。下面介绍更新方法: 1、clear() 从图中删除所有的条目。 2、put(key, value) 将一个值value和Map中的一个键值key关联。如果这个Map中原来就包含到该键值的一个映射,那么该方法会返回原来和这个键值相关联的旧值。(而将key关联为新的Value值) 3、putAll(m) 将指定的Map m添加到当前Map中,但是重复的items会被覆盖。 4、remove() 将指定的key对应的元素从Map中删除。
查询方法: 1、containsKey(key) 检测Map中是否包含指定key的key-value 的映射 2、containsValue(value) 检测Map中是否包含指定value的key-value 的映射 3、isEmpty() 检测Map是否为空 4、size() 返回Map中item的个数
将Map转换为Set的方法: 1、keySet() 调用map.keySet()将map中的key全部拿出来组成一个包含全部key值的Set 2、values() 调用map.values()将map中的value全部拿出来组成一个包含全部value值的Set 3、entrySet() 调用map.entrySet()将map中的<key, value>全部拿出来组成一个包含全部<key, value>值的Set,该Set中每个对象都是底层Map中的一个key-value对。 该方法实际返回一个实现Map.Entry<K, V>接口的对象集合,这里的Entry是Map接口的一个内部接口。
AbstractMap抽象类:实现了Map接口的便利抽象类。 AbstractMap实现了Map接口中除了entrySet()方法之外的所有方法。 SortedMap接口: 扩展了Map接口,并保持Map中的<K, V>对以键值K升序排列。 它还有附加的方法firstKey()和lastKey()返回最低键值和最高键值,headMap(toKey)返回键值K小于toKey的那部分Map,tailMap(fromKey)返回键值大于或等于fromKey的那部分Map。
1、具体类HashMap: 对于定位一个值、插入一个<K, V>、以及删除一个<K, V>而言,HashMap 是最高效的。HashMap 中的item是没有顺序的。 2、具体类LinkedHashMap: LinkedHashMap使用链表实现来扩展HashMap类,它支持Map中的item排序。元素既可以按照它们插入图的顺序排序,也可以按照它们被最后一次访问的顺序排序。 3、具体类TreeMap: TreeMap在遍历排好序的键值的时候是很高效的。键值可以使用Comparator接口来排序,也可使用Comparable接口排序。
单元素和不可变的集合和图
Collections类包含了线性表和集合的静态方法,一些可以创建单元素的Set、List和Map的方法,以及用于创建不可变Set、List和Map的方法。 Collections类中还定义了3个常量:EMPTY_SET、EMPTY_LIST和EMPTY_MAP,分别表示空的规则集、线性表和图。 方法singleton()用于创建仅包含一个item的不可变Set 方法singletonList()用于创建仅包含一个item的不可变List 方法singletonMap()用于创建仅包含一个单一键值对的不可变Map Collections类中还提供了6个用于创建只读集合的静态方法: unmodifiableCollection(Collection)、unmodifiableSet(Set)、unmodifiableList(List)、unmodifiableMap(Map)、unmodifiableSortedMap(SortedMap)、unmodifiableSortedSet(SortedSet)