疯狂JAVA讲义集合(第八章)学习笔记

一 零碎的知识汇总

1)Collection接口,子接口及其实现类的继承树。

疯狂JAVA讲义集合(第八章)学习笔记_第1张图片

其中粗线圈出的Set和List接口是Collection派生出的子接口,它们分别代表无序集合和有序集合;Queue是java提供的队列实现。

2)Map接口的众多实现类,这些实现类在功能,用法上存在一些差异,但它们都有一个功能特征:Map保存的每项数据都是key-value对。Map中的key是不可重复的,key用于标识集合里的每项元素,如果需要查阅Map中的数据,总是通过key值。

疯狂JAVA讲义集合(第八章)学习笔记_第2张图片

3)Iterator接口是Collection接口的父接口。Iterator接口也是Java集合框架的成员,但它与Collection系列,Map系列的集合不一样:Collection系列集合和Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历Collection集合中的元素,Iterator对象也被称为迭代器。

Iterator定义了如下四个方法:

疯狂JAVA讲义集合(第八章)学习笔记_第3张图片

疯狂JAVA讲义集合(第八章)学习笔记_第4张图片

疯狂JAVA讲义集合(第八章)学习笔记_第5张图片

4)Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove()方法删除上一次next()方法返回的元素才可以;否则会引发java.util.ConcurrentModificationException异常。下面程序示范了这一点:

疯狂JAVA讲义集合(第八章)学习笔记_第6张图片

5) 使用Lambda表达式遍历Iterator。

       java8起为Iterator新增了一个forEachRemaining(Consumer action)方法,该方法所需的Conumer是一个函数式接口。当程序调用Iterator的forEachRemaining(Consumer action)遍历集合元素时,程序会依次将集合元素传给Consumer的accept(T t)方法。

       如下程序示范了使用Lambda表达式来遍历集合元素。

疯狂JAVA讲义集合(第八章)学习笔记_第7张图片

6)使用foreach循环遍历集合元素。

      foreach循环中的迭代变量也不是集合元素本身,系统只是把集合元素的值赋给了迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。同样,当使用foreach循环迭代访问集合元素时。该集合也不能被改变。如下

疯狂JAVA讲义集合(第八章)学习笔记_第8张图片

7)Java8起为Collection集合新增了一个removeIf(Predicate filter)方法,该方法会批量删除符合filter条件的所有元素。该方法需要一个Predicate对象作为参数,Predicate是函数式接口,因此可使用Lambda表达式。

如下程序示例了用Predicate来过滤集合

疯狂JAVA讲义集合(第八章)学习笔记_第9张图片

使用Predicate可以充分简化集合的运算,如下程序所示:

public class PredicateTest{
    public static void main(String[] args){
        //创建一个books集合,为books集合添加元素的代码与前一个程序相同
        //...
        //统计书名包含“疯狂”字串的图书数
        System.out.println(calAll(books,ele->((String)ele).contains("疯狂")));
        //统计书名包含“Java”字串的图书数
        System.out.println(calAll(books,ele->((String)ele).contains("Java")));
        //统计书名字符串长度大于10的图书数
        System.out.println(calAll(books,ele->((String)ele).length()>10));
    }
    private static int calAll(Collection books,Predicate p){
        int total=0;
        for(Object o:books){
            if(p.test(o)){
                total++;
            }
        }
        return total;
    } 
}

8)关于Lambda表达式,如下代码等价:

for (Object n : list) { System.out.println(n); }

list.forEach(n -> System.out.println(n));

list.forEach(System.out::println);

9)HashSet具有如下特点:

     1.不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。

     2.HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。

     3.集合元素值可以是null

10)HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值相同

11)LinkedHashSet可以保证元素的排列顺序与添加顺序一致。

12)HashSet集合采用hash算法来决定元素的存储位置,TreeSet采用红黑树的数据结构来存储集合元素。

13)TreeSet是SortedSet接口的实现类,可以确保各元素处于排序状态。TreeSet有两种排序方式:自然排序和定制排序。

       1.TreeSet会调用集合元素的compareTo(Object obj)方法比较元素之间的大小关系,然后将集合元素按升序排列,这种方式              就是自然排序。因此将一个对象添加到TreeSet中时,该对象的类必须实现了Comparable接口,否则抛出运行时异常:                ClassCastException。

       2.要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator              对象负责元素的排序逻辑。由于Comparator是一个函数式接口,因此可以使用Lambda表达式代替Comparator对象。

14)EnumSet是一个专门为枚举类设计的集合类,EnumSet的所有元素都必须是指定枚举类型的枚举值。EnumSet的元素是有序的,以枚举值在Enum类中定义顺序来决定集合元素的顺序。它有如下类方法来创建对象:

疯狂JAVA讲义集合(第八章)学习笔记_第10张图片

示例如下:

enum Season
{
	SPRING,SUMMER,FALL,WINTER
}
public class EnumSetTest
{
	public static void main(String[] args)
	{
		// 创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值
		EnumSet es1 = EnumSet.allOf(Season.class);
		System.out.println(es1); // 输出[SPRING,SUMMER,FALL,WINTER]
		// 创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。
		EnumSet es2 = EnumSet.noneOf(Season.class);
		System.out.println(es2); // 输出[]
		// 手动添加两个元素
		es2.add(Season.WINTER);
		es2.add(Season.SPRING);
		System.out.println(es2); // 输出[SPRING,WINTER]
		// 以指定枚举值创建EnumSet集合
		EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER);
		System.out.println(es3); // 输出[SUMMER,WINTER]
		EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER);
		System.out.println(es4); // 输出[SUMMER,FALL,WINTER]
		// 新创建的EnumSet集合的元素和es4集合的元素有相同类型,
		// es5的集合元素 + es4集合元素 = Season枚举类的全部枚举值
		EnumSet es5 = EnumSet.complementOf(es4);
		System.out.println(es5); // 输出[SPRING]
	}
}

15)List接口的常用方法:

疯狂JAVA讲义集合(第八章)学习笔记_第11张图片

16)与Set只提供一个iterator方法不同,List还额外提供了一个listItarator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口基础上增加了如下方法:

如下代码示范了ListIterator的使用方法:

public class ListIteratorTest
{
	public static void main(String[] args)
	{
		String[] books = {
			"疯狂Java讲义", "疯狂iOS讲义",
			"轻量级Java EE企业应用实战"
		};
		List bookList = new ArrayList();
		for (int i = 0; i < books.length ; i++ )
		{
			bookList.add(books[i]);
		}
		ListIterator lit = bookList.listIterator();
		while (lit.hasNext())
		{
			System.out.println(lit.next());
			lit.add("-------分隔符-------");
		}
		System.out.println("=======下面开始反向迭代=======");
		while(lit.hasPrevious())
		{
			System.out.println(lit.previous());
		}
	}
}

 

17)Arrays工具类提供了asList(Object o)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加,删除该集合里的元素。如下所示:

疯狂JAVA讲义集合(第八章)学习笔记_第12张图片

18)Queue接口有一个实现类PriorityQueue实现类,还有一个Deque接口。Deque代表一个双端队列,双端队列可以同时从两端来添加,删除元素,因此Deque实现类既可以当成队列使用,也可当成栈使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。

19)PriorityQueue违反队列基本规则,它将加入队列的元素排序,支持有自然排序和定制排序两种方式,自然排序时使用poll()方法将看到元素从小到大的顺序移出队列。

20)使用栈这种数据结构时,推荐使用ArrayDeque,尽量避免使用Stack,因为Stack是古老的集合,性能较差。

21)ArrayDeque实现类可以当作栈使用(使用posh()和pop()方法),也可以当作队列使用(使用offer()与poll()),两种方式使用同一个peek()方法。

22)LinkedList是List接口的实现类,还实现了Deque接口,因此既可以当作队列,也可以当作栈来使用。

23)LinkedHashMap中key-value节点是有序的,与插入顺序一致。HashMap和Hashtable则不能保证插入元素顺序。

24)Properties类是Hashtable类的子类,其key,value都是String类型。该类有如下5个方法来操作Properties里的key,value值

疯狂JAVA讲义集合(第八章)学习笔记_第13张图片

下面程序示范了Properties类的用法:

疯狂JAVA讲义集合(第八章)学习笔记_第14张图片

 

二 易混淆出错的难点汇总

1)对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0————如果返回0,表示相等;返回1,表示不相等。

疯狂JAVA讲义集合(第八章)学习笔记_第15张图片

疯狂JAVA讲义集合(第八章)学习笔记_第16张图片

以上代码在内存中示意图如下:

疯狂JAVA讲义集合(第八章)学习笔记_第17张图片

2)各Set实现类的性能分析:

      HashSet和TreeSet是Set的两个典型实现,HashSet的性能总比TreeSet好,因为TreeSet需要额外的红黑树算法来维护集合元素次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。

      HashSet还有一个子类:LinkedHashSet,对于普通的插入,删除操作,LinkdeHashSet比HashSet要略微慢一点,这是由维护链表所带来的额外开销造成的。但由于有了链表,遍历LinkedHashSet会更快。

      EnumSet是所有Set实现类里性能最好的,但它只能保存同一个枚举类型的枚举值作为集合元素。

      Set的三个实现类HashSet,TreeSet和EnumSet都是线程不安全的。如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。例如:

3)ArrayList集合是线程不安全的,Vector集合则是线程安全的。即使为了线程安全,也不推荐使用Vector实现类,使用                    Collections工具类可以将一个ArrayList变成线程安全的。

4)LinkedList内部以链表形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入,删除元素时性能比较出色。ArrayList,ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时性能较好。虽然Vector也是以数组的形式来存储集合元素,但因为它实现了线程同步功能(实现机制并不好),所以各方面性能很差。

5)关于List集合的使用建议:

     1.如果需要遍历集合元素,对于ArrayList,Vector集合,应采用随机访问方法(get())来遍历集合元素,这样性能更好;对于LinkedList集合,则应该使用迭代器(Iterator或ListIterator)来遍历集合元素。

     2.如果需要经常执行插入,删除操作来改变包含大量数据List集合的大小,优先考虑使用LinkedList集合。使用ArrayList,Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。

     3.如果有多个线程需要同时访问List集合中的元素,开发者可优先考虑使用Collections将集合包装成线程安全的集合。

6)Hashtable(t不大写)和HashMap的两点典型区别:

     1.Hashtable线程安全,HashMap线程不安全。

     2.Hashtable不可以使用null作为key或value的值,HashMap可以使用null作为key或value。

7)各Map实现类性能分析。

      HashMap与Hashtable实现机制几乎一样,但由于Hashtable是一个古老的,线程安全的集合,因此HashMap通常运行更快。

      TreeMap通常比HashMap,Hashtable要慢(尤其在插入,删除时),因为TreeMap底层采用红黑树管理key-value对。

      对于一般应用场景,程序应多考虑使用HashMap,因为HashMap正是为快速查询设计的(HashMap底层也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则优先考虑使用TreeMap。

 

你可能感兴趣的:(学习笔记)