Java集合框架

集合框架的出现

集合框架,在平时的编程中经常会用到,体系庞大又十分的重要,所以做个总结很有必要,我们知道数组可以用来存放基础数据类型与引用类型,在定义的时候,存放在数组中的类型是已经确定的了,如下面给定的数组只能用来存放String类型的值,并且这时数组的长度已经固定

String[] array = new String[5];

如果你试图在array中赋于String以外的类型,则会给出编译提示,类型转换错误

又如果你想给数组添加一个新的值,比如说像这样

array[5] = "s";

那么在运行时,虚拟机会抛出如下异常,数组越界

为了弥补数组这种不可变的行为,Java设计者们设计了一系列操作元素的集合类,这一系列的集合类就组成了集合框架,由于这里面涉及到的接口及类较多,所以需要先对整个体系有一个感观的认识,这样才能心中有数。

集合的组成

 

上面这张图只是简单的列出了集合中最常使用的接口以及实现类,当然远远不止这些,不过掌握了这些就能解决大部分的集合问题。从图中,可以知道集合框架中主要有两大派系,这两个派系被抽象成了两个接口,分别为Collection与Map,这两个接口下面又衍生出子接口以及一些实现类,对于List、Set和Map三种集合,最常用的实现类分别是ArrayList、HashSet和HashMap三个实现类。面对众多繁杂的各种接口以及类,怎么才能比较好的掌握呢,其实可以把集合框架看成是一个小型的数据库,我们都知道,数据库主要的操作就是增删改查,那么集合也是如此,从这四个方面入手,就可以大体掌握集合的用法。

List

ArrayList

ArrayList类为List接口的主要实现类,是一个数组队列,提供了添加、删除、修改、遍历等功能,相当于是一个动态数组,与数组的固定容量相比较,ArrayList集合的容量可以根据一定的规则动态的增长,实现了RandmoAccess接口,即提供了随机访问功能。在ArrayList中,我们可以通过元素的序号快速获取元素对象,这就是快速随机访问

ArrayList中的方法很多都跟位置有关,下面的代码列举了ArrayList一些常用的方法

package collection.list;
 
import java.util.ArrayList;
import java.util.List;
 
public class ListDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
 
        // 添加元素
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");
 
        // 获取集合大小
        System.out.println("初始状态:" + list);
        System.out.println("数组大小:" + list.size());
 
        // 判断集合是否包含某元素
        boolean contain = list.contains("水浒传");
        System.out.println("集合是否包含水浒传: " + contain);
 
        // 判断集合是否为空
        boolean isEmpty = list.isEmpty();
        System.out.println("集合是否为空:" + isEmpty);
 
        // 获取第一个元素
        String string = list.get(0);
        System.out.println("第一个元素是: " + string);
 
        // 获取第一次出现给定元素的位置
        int index = list.indexOf("水浒传");
        System.out.println("第一次出现水浒传的位置是:" + index);
 
        // 在1位置插入元素,插入的位置不能超过集合的最大长度,否则报java.lang.IndexOutOfBoundsException异常
        list.add(2, "平凡的世界");
        System.out.println("在位置2处插入元素后的集合: " + list);
 
        // 删除刚才插入的元素
        string = list.remove(2);
        System.out.println("被删除的元素: " + string);
        System.out.println("删除元素后的集合: " + list);
 
        // 替换指定位置的元素
        string = list.set(0, "儒林外史");
        System.out.println("被替换的元素是:" + string);
        System.out.println("现在集合中的元素是:" + list);
 
        // 清空集合
        list.clear();
        System.out.println("清空集合后:" + list);
        System.out.println("清空集合是否为空:" + list.isEmpty());
    }
}

运行结果

初始状态:[红楼梦, 三国演义, 西游记, 水浒传]
数组大小:4
集合是否包含水浒传: true
集合是否为空:false
第一个元素是: 红楼梦
第一次出现水浒传的位置是:3
在位置2处插入元素后的集合: [红楼梦, 三国演义, 平凡的世界, 西游记, 水浒传]
被删除的元素: 平凡的世界
删除元素后的集合: [红楼梦, 三国演义, 西游记, 水浒传]
被替换的元素是:红楼梦
现在集合中的元素是:[儒林外史, 三国演义, 西游记, 水浒传]
清空集合后:[]
清空集合是否为空:true

与数据库一样,集合中最常用的也是查询的操作,所以这个部分单独介绍,因为ArrayList实现了RandmoAccess接口,所以与数组类似,每个元素在ArrayList中都有自己特定的位置,我们可以通过这个角标,也就是索引来获取对应的元素

package collection.list;
 
import java.util.ArrayList;
import java.util.List;
 
public class TraverseDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");
 
        String string = null;
 
        for (int i = 0; i < list.size(); i++) {
            string = list.get(i);
            System.out.println(string);
        }
    }
}

查看API可知,ArrayList实现了Iterable接口,调用Iterable接口中的iterator() 方法可以返回一个叫做迭代器的接口Iterator,可以把Iterator当做一个游标使用,API很简单,总共就三个方法

boolean hasNext() 
          如果仍有元素可以迭代,则返回 true。 
E next() 
          返回迭代的下一个元素。 
void remove() 
          从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 

下面的代码演示了Iterator的常用用法

package collection.list;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class TraverseDemo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("红楼梦");
        list.add("三国演义");
        list.add("西游记");
        list.add("水浒传");
 
        String string = null;
        Iterator<String> it = list.iterator();
 
        while (it.hasNext()) {
            string = it.next();
            System.out.println(string);
        }
    }
}

上面的while循环,我们可以改成for循环,这种方式效果可能高些,因为for循环结束后,iterator变量便释放了

package collection.list;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TraverseDemo4 {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("红楼梦");
		list.add("三国演义");
		list.add("西游记");
		list.add("水浒传");

		String string = null;

		for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
			string = iterator.next();
			System.out.println(string);
		}
	}
}

通过增强for循环,也可以遍历ArrayList,增强for循环的优点是代码简洁,但是无法获取特定的元素,因为没有脚标了

package collection.list;

import java.util.ArrayList;
import java.util.List;

public class TraverseDemo3 {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("红楼梦");
		list.add("三国演义");
		list.add("西游记");
		list.add("水浒传");

		for (String string : list) {
			System.out.println(string);
		}
	}
}

LinkedList

LinkedList也是List接口下一个非常重要的实现类,底层数据结构是基于链表,链表相对于数组(ArrayList的底层实现)对数据的增删的速度较快,不过查询速度就稍微逊色了些,来看一下LinkedList中特有的方法

package collection.list;

import java.util.LinkedList;

import org.junit.Before;
import org.junit.Test;

public class LinkedListDemo {
	LinkedList<String> list = null;

	@Before
	public void init() {
		list = new LinkedList<String>();
	}

	@Test
	public void testOfferFirst() { // 在列表开头插入元素
		list.offerFirst("s1");
		list.offerFirst("s2");
		System.out.println(list); // [s2, s1]
	}

	@Test
	public void testOfferLast() { // 在列表末尾插入元素
		list.offerLast("s1");
		list.offerLast("s2");
		System.out.println(list); // [s1, s2]
	}

	@Test
	public void testPeekFirst() { // 获取但不移除列表的第一个元素;如果列表为空,则返回 null
		list.offerLast("s1");
		list.offerLast("s2");
		list.offerLast("s3");
		System.out.println(list.peekFirst()); // s1
		System.out.println(list); // [s1, s2, s3]
	}

	@Test
	public void testPeekLast() { // 获取但不移除列表的最后一个元素;如果列表为空,则返回 null
		list.offerLast("s1");
		list.offerLast("s2");
		list.offerLast("s3");
		System.out.println(list.peekLast()); // s3
		System.out.println(list); // [s1, s2, s3]
	}

	@Test
	public void testPollFirst() { // 获取并移除列表的第一个元素;如果此列表为空,则返回 null
		list.offerLast("s1");
		list.offerLast("s2");
		list.offerLast("s3");
		System.out.println(list.pollFirst()); // s1
		System.out.println(list); // [s2, s3]
	}

	@Test
	public void testPollLast() { // 获取并移除列表的最后一个元素;如果此列表为空,则返回 null
		list.offerLast("s1");
		list.offerLast("s2");
		list.offerLast("s3");
		System.out.println(list.pollLast()); // s3
		System.out.println(list); // [s1, s2]
	}

}

小结

ArrayList:底层数据结构是数组,增删操作慢,查询速度快

LinkedList:底层数据结构是链表,增删操作快,查询速度慢

Set

HashSet

HashSet是Set接口的一个重要的实现类,主要的特点是:里面不能存放重复元素,而且采用散列的存储方法,所以没有顺序,这里的没有顺序指的是存入和取出的顺序不一定一致,HashSet的底层数据结构是哈希表,内部实现是基于HashMap

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

看个例子(API请自行查阅)

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo1 {
	public static void main(String[] args) {
		HashSet<String> hs = new HashSet<String>();
		hs.add("s1");
		hs.add("s2");
		hs.add("s2");
		hs.add("s3");

		Iterator<String> it = hs.iterator();

		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

运行结果

s2
s1
s3

从运行结果来看,我们往hashset中存入了四个元素,其中存了两次的s2,最终只存入了一个,这说明hashset中元素确实不能重复,元素的取出顺序与存放顺序也不一致。更多时候,我们要存放的不仅仅是像字符串这样简单的对象,而是一些自定义对象

package collection.set;

public class Student {
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

现在往hashset中添加自定义对象Student

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo2 {
	public static void main(String[] args) {
		HashSet<Student> set = new HashSet<Student>();

		set.add(new Student("s1", 21));
		set.add(new Student("s2", 22));
		set.add(new Student("s2", 22));
		set.add(new Student("s3", 23));

		Iterator<Student> it = set.iterator();

		while (it.hasNext()) {
			Student student = it.next();
			System.out.println(student.getName() + ":" + student.getAge());
		}
	}
}

运行结果

s1:21
s2:22
s3:23
s2:22

出现了一个与预期不同的答案,那就是hashset中添加了两个s2对象,不是说不允许重复元素存在吗,这又是怎么回事,其实hashset在添加元素的时候,首先会调用hashCode方法判断集合中是否有对象的哈希码值与新的对象相等,如果不相等,则添加成功,如果相等,那么再继续调用equals方法将这些对象与插入的对象进行比较,如果为true,则添加失败,如果为false,则添加成功,用下面的流程图表示更直观些

那上面的SetDemo1又是怎么回事,别忘了SetDemo1中添加的是String对象,String类中重写了hashCode与equals方法,下面验证一下所说的这个结论,重写Student类的hashCode与equals方法

package collection.set;

public class Student {
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int hashCode() {
		System.out.println(this.getName() + "...hashCode...");
		return this.getName().hashCode() + age * 31;
	}

	public boolean equals(Object object) {
		Student student = (Student) object;
		System.out.println(this.name + "...equals..." + student.getName());
		return this.name.equals(student.getName()) && this.age == student.getAge();
	}
}

主程序也修改下

package collection.set;

import java.util.HashSet;
import java.util.Iterator;

public class HashSetDemo2 {
	public static void main(String[] args) {
		HashSet<Student> hs = new HashSet<Student>();

		boolean isAdd = hs.add(new Student("s1", 21));
		System.out.println(isAdd);
		isAdd = hs.add(new Student("s2", 22));
		System.out.println(isAdd);
		isAdd = hs.add(new Student("s2", 22));
		System.out.println(isAdd);
		isAdd = hs.add(new Student("s3", 23));
		System.out.println(isAdd);

		Iterator<Student> it = hs.iterator();

		while (it.hasNext()) {
			Student student = it.next();
			System.out.println(student.getName() + ":" + student.getAge());
		}
	}
}

运行结果

s1...hashCode...
true
s2...hashCode...
true
s2...hashCode...
s2...equals...s2
false
s3...hashCode...
true
s1:21
s2:22
s3:23

一起来分析一下这个过程,首先添加s1对象,它调用了hashCode方法,因为刚开始hashset中是没有任何元素的,所以这时候添加成功,为true,接着添加s2对象,同样s2对象也调用hashCode方法,由于s2对象与s1对象的哈希值不一样,所以hashset认为集合中没有s2对象,添加成功,接着又添加一个s2对象,根据运行结果,这个s2对象调用hashCode方法之后,发现hashset中已经存在一个与它哈希值一样的对象(哈希值一样只能说明两个对象在内存中的位置一样,对象是否是同一个还得用equals方法进行判断),所以接着调用了equals方法,由Student类的equals方法知道,这两个s2对象相同,返回true,所以这时候s2对象添加失败,最后添加s3对象,根据前面的分析,容易知道,s3对象成功的添加到set集合中了,最终hashset添加了s1,s2,s3三个对象。

通常hashset操作的对象,都需要重写hashCode与equals方法。

TreeSet

上面说到的HashSet,当我们获取元素的时候,由于底层数据结构是哈希表,所以输出的顺序是无序的,如果需要对Set集合中的元素进行排序,那就要用到set中另一个重要的实现类TreeSet,TreeSet的底层数据结构是二叉树,可以对Set集合中的元素进行排序,先来看个例子

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo1 {
	public static void main(String[] args) {
		TreeSet<String> ts = new TreeSet<String>();
		ts.add("abc");
		ts.add("aaa");
		ts.add("acd");
		ts.add("abcd");

		Iterator<String> it = ts.iterator();

		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

输出结果

aaa
abc
abcd
acd

输出的结果与存入的顺序不一致,那么TreeSet集合内部是根据什么来排序的呢,查阅API知道,有两种方式可以实现对元素的排序

  1. 使用元素的自然顺序对元素进行排序
  2. 根据创建 set 时提供的 Comparator 进行排序

这边使用的就是第一种,根据元素的自然顺序进行排序,接下来我们往TreeSet集合中添加自定义对象Student看看

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo2 {
	public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>();
		ts.add(new Student("s01", 20));
		ts.add(new Student("s02", 22));
		ts.add(new Student("s04", 21));
		ts.add(new Student("s03", 22));

		Iterator<Student> it = ts.iterator();

		while (it.hasNext()) {
			Student student = it.next();
			System.out.println(student.getName() + " : " + student.getAge());
		}
	}
}

这时候,控制台抛出了如下异常信息

Exception in thread "main" java.lang.ClassCastException: collection.set.Student cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(Unknown Source)
	at java.util.TreeMap.put(Unknown Source)
	at java.util.TreeSet.add(Unknown Source)
	at collection.set.TreeSetDemo2.main(TreeSetDemo2.java:9)

根据提示信息,是因为类型转换错误引起的,这里出现了一个Comparable,通过查询API得知Comparable是一个接口,这个接口很简单,只有一个方法,该方法在添加对象时隐式地被调用

int compareTo(T o)
    比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

由于TreeSet会对存入的对象进行排序,所以需要对象之间相互进行比较,也就是具有比较性,那怎样才能让对象具有比较性呢,Java中提供了一个接口Comparable,只要实现了这个接口,对象之间就具有了比较性,说到这里,应该可以猜测出来为什么存入String对象的时候可以成功了,现在我们让Student对象实现Comparable接口,让它具有比较性

package collection.set;

public class Student implements Comparable<Student> {
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public int compareTo(Student s) {
		return this.age - s.age; // 按照年龄升序排列
	}
}

代码的运行结果

s01 : 20
s04 : 21
s02 : 22

问题来了,存了四个对象,怎么就输出三个对象了,重新回顾一下compareTo方法,因为compareTo方法中是根据学生的年龄大小进行比较,而s02与s03这两个学生的年龄相同,所以认为他们是同一个对象,这里假定姓名与年龄都相同的时候,认为两个学生才是同一个人,修改一下compareTo方法

@Override
public int compareTo(Student s) {
	int num = this.age - s.age;

	if (num == 0) { // 当年龄相同时,按照姓名排序
		return this.getName().compareTo(s.getName());
	}

	return num; // 按照年龄升序排列
}

这时候的运行结果就符合我们的预期了

s01 : 20
s04 : 21
s02 : 22
s03 : 22

来思考一个问题,如果现在优先根据学生的年龄进行排序,那怎么办,首先想到修改我们的代码,这是可以的,那如果突然有一天又需要根据年龄的长度进行排序,那难道又要修改我们的代码吗,别忘了,还有另一种方法可以实现对对象的排序,那就是

根据创建 set 时提供的 Comparator 进行排序

什么意思呢,看下TreeSet的构造函数就明白了

TreeSet(Comparator<? super E> comparator)
            构造一个新的空 TreeSet,它根据指定比较器进行排序。

这里在初始化TreeSet的时候,传入了一个参数Comparator,Comparator也是一个接口,其中compare方法可以用来比较对象

int compare(T o1, T o2)
    比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。

现在新建一个类实现Comparator接口

package collection.set;

import java.util.Comparator;

public class StudentComparator implements Comparator<Student> {

	@Override
	public int compare(Student s1, Student s2) {
		int num = s1.getName().compareTo(s2.getName());

		if (num == 0) {
			return s1.getAge() - s2.getAge();
		}

		return num;
	}

}

在构造TreeSet的时候,传入StudentComparator

package collection.set;

import java.util.Iterator;
import java.util.TreeSet;

public class TreeSetDemo3 {
	public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>(new StudentComparator());
		ts.add(new Student("s01", 20));
		ts.add(new Student("s02", 22));
		ts.add(new Student("s04", 21));
		ts.add(new Student("s03", 22));

		Iterator<Student> it = ts.iterator();

		while (it.hasNext()) {
			Student student = it.next();
			System.out.println(student.getName() + " : " + student.getAge());
		}
	}
}

运行结果符合我们的要求

s01 : 20
s02 : 22
s03 : 22
s04 : 21

别忘了,此时Student类依然是实现了Comparable接口的,内部有自己的比较规则,从上面的运行结果可以得出一个结论构造器中传入的比较器规则优于对象本身的比较规则,后期如果需要根据其他方式进行排序,则只需新建一个比较器即可,就不必修改Student类了。

小结

HashSet:底层数据结构是哈希表,元素无序,往HashSet里面存的自定义元素要复写hashCode和equals方法,以保证元素的唯一性,保证元素唯一性的顺序

  1. 调用元素的hashCode方法,如果不同,则存入元素,如果相同,转2
  2. 调用元素的equals方法,如果不同,则存入元素,如果相同,则不存

注意:hashCode和equals方法在集合操作元素时默认被调用

TreeSet:底层数据结构式二叉树,元素有序,可以对集合中的元素进行排序,有两种方式

  1. 元素实现Compareble接口,覆盖compareTo方法
  2. 自定义一个类,实现Comparator接口,覆盖compare方法

Map

Map这个体系下经常使用的主要有HashMap和TreeMap这两个实现类,可以与set体系中的HashSet和TreeSet进行对比学习,前面提到Set的底层就是用Map,所以他们之间有些许类似,比如他们中的元素都是无序的。map在这里是映射的意思,跟数学中集合的映射概念是一个意思,map中的元素是以键值对的形式存储的,一个键对应一个值,并且需要保证键的唯一性,否则值将会被覆盖。

HashMap

HashMap与HashSet一样,底层数据结构基于哈希表,允许存在null的键,null的值。

package map;

import java.util.Collection;
import java.util.HashMap;

public class HashMapDemo1 {
	public static void main(String[] args) {
		HashMap<String, Integer> hm = new HashMap<String, Integer>();
		hm.put("s1", 21); // 添加元素
		hm.put("s2", 22);
		hm.put("s3", 23);
		hm.put("s4", 24);
		hm.put(null, null); // 存入null键,null值

		Collection<Integer> values = hm.values(); // 获取集合中所有的值
		System.out.println(values);
		System.out.println(hm.get("s1")); // 根据key获取对应的值
		System.out.println(hm.containsKey("s1")); // 判断是否包含给定的键
		System.out.println(hm.isEmpty()); // 集合是否为空
		System.out.println(hm.size()); // 集合中包含的键值对个数
		System.out.println(hm.remove("s2")); // 移除集合中给定键所对应的值,并返回该值
		System.out.println(hm.size()); // 集合中包含的键值对个数
		values = hm.values(); // 获取集合中所有的值
		System.out.println(values);
	}
}

运行结果

[null, 22, 21, 23, 24]
21
true
false
5
22
4
[null, 21, 23, 24]

通常,我们需要成对的取出集合中的键值对,那么根据前面的经验,查找一下,发现并没有类似ArrayList,HashSet中所使用的迭代器,那么用什么方法可以获取键值对呢,Map集合中两种方法可以实现

  1. 使用keySet()方法先将Map转换成Set,然后用Set中的迭代器进行迭代
  2. 使用entrySet()方法,将Map转成具有映射关系的Set视图


先看第一种

package map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class HashMapDemo2 {
	public static void main(String[] args) {
		HashMap<String, Integer> hm = new HashMap<String, Integer>();
		hm.put("s1", 21);
		hm.put("s2", 22);
		hm.put("s3", 23);
		hm.put("s4", 24);

		Set<String> keySet = hm.keySet();

		Iterator<String> it = keySet.iterator();

		while (it.hasNext()) {
			String key = (String) it.next();
			Integer value = hm.get(key);

			System.out.println("key=" + key + "," + "value=" + value);
		}

	}
}

再来看第二种,也就是使用entrySet方法

package map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapDemo3 {
	public static void main(String[] args) {
		HashMap<String, Integer> hm = new HashMap<String, Integer>();
		hm.put("s1", 21);
		hm.put("s2", 22);
		hm.put("s3", 23);
		hm.put("s4", 24);

		Set<Map.Entry<String, Integer>> entry = hm.entrySet();

		Iterator<Map.Entry<String, Integer>> it = entry.iterator();

		while (it.hasNext()) {
			Map.Entry<String, Integer> me = it.next();
			String key = me.getKey();
			Integer value = me.getValue();
			System.out.println("key=" + key + "," + "value=" + value);
		}

	}
}

TreeMap

有了前面学习TreeSet的基础,学习TreeMap就容易多了,与TreeSet一样,TreeMap底层数据结构也是基于二叉树,可以对Map集合中的元素进行排序,同样有两种方式

  1. 根据键的自然顺序进行排序
  2. 根据创建映射时提供的 Comparator 进行排序

具体的使用方法可以参考上述所讲的TreeSet,本小节的总结见最后。

集合工具类

Collections

Collections是集合框架中的一个工具类,该类中提供了一系列静态的方法来操作集合,包括对list集合的排序,二分查找,查询集合的最大最小值等,像上面介绍的比较常用的ArrayList,HashSet,TreeMap等集合都是线程不同步的,当有多线程操作这些集合的时候,可以使用该工具类synchronizedList,synchronizedSet,synchronizedMap等方法将线程不同步的集合转成同步的。

package utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CollectionsDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("abcd");
		list.add("aaa");
		list.add("zz");
		list.add("y");
		list.add("kkkkk");

		// 使用二分查找指定元素,找到则返回索引
		int index = Collections.binarySearch(list, "zz");
		System.out.println("111 : " + index);

		// 没有找到,返回(-(插入点) - 1)
		index = Collections.binarySearch(list, "zzz");
		System.out.println("222 : " + index);

		// 查找列表最大值
		String max = Collections.max(list);
		System.out.println("333 : " + max);

		// 根据元素的自然顺序对列表按升序进行排序
		Collections.sort(list);
		System.out.println("444 : " + list);

		// 根据给定比较器对指定列表进行排序
		Collections.sort(list, new StrLenComparator());
		System.out.println("555 : " + list);

		// reverseOrder()强行逆转实现了 Comparable 接口的对象 collection 的自然顺序
		Collections.sort(list, Collections.reverseOrder());
		System.out.println("666 : " + list);

		// 反转列表中元素的顺序
		Collections.reverse(list);
		System.out.println("777 : " + list);

		// 随机排列列表的元素
		Collections.shuffle(list);
		System.out.println("888 : " + list);

		// 返回一个空的集合,该集合不可变,如果对该集合进行增删操作,会抛出java.lang.UnsupportedOperationException异常
		List<String> emptyList = Collections.<String> emptyList();
		System.out.println("999 : " + emptyList);

		emptyList = Collections.EMPTY_LIST;
		System.out.println("000 : " + emptyList);
	}
}

class StrLenComparator implements Comparator<String> {

	@Override
	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

运行结果

111 : 2
222 : -6
333 : zz
444 : [aaa, abcd, kkkkk, y, zz]
555 : [y, zz, aaa, abcd, kkkkk]
666 : [zz, y, kkkkk, abcd, aaa]
777 : [aaa, abcd, kkkkk, y, zz]
888 : [kkkkk, abcd, y, aaa, zz]
999 : []
000 : []

Arrays

Arrays包含用来操作数组(比如排序和搜索)的各种方法,同样里面也都是静态方法。如果指定数组引用为 null,则此类中的方法都会抛出 NullPointerException。

package utils;

import java.util.Arrays;
import java.util.List;

public class ArraysDemo {
	public static void main(String[] args) {
		String[] array = { "abc", "ggg", "dh", "zzzz", "o" };

		// 数组转集合,如果试图对集合进行增删操作,则会抛出UnsupportedOprationException异常
		List<String> list = Arrays.asList(array);
		System.out.println("111 : " + list);

		// 数组转字符串
		String string = Arrays.toString(array);
		System.out.println("222 : " + string);

		// 使用二分查找指定元素,找到则返回索引
		int index = Arrays.binarySearch(array, "ccc");
		System.out.println("333 : " + index);

		// 复制数组
		String[] newArray = Arrays.copyOf(array, array.length);
		System.out.println("444 : " + Arrays.asList(newArray));

		// 复制指定范围的数组
		String[] newArrayRange = Arrays.copyOfRange(array, 0, 2);
		System.out.println("555 : " + Arrays.asList(newArrayRange));

		// 判断两个数组是否相等
		boolean equals = Arrays.equals(array, newArray);
		System.out.println("666 : " + equals);

		// 对数组元素进行排序
		Arrays.sort(array);
		System.out.println("777 : " + Arrays.asList(array));

		// 转成字符串
		String arrayString = Arrays.toString(array);
		System.out.println("888 : " + arrayString);

		// 用指定的值替换数组中的每个值
		Arrays.fill(array, "new");
		System.out.println("999 : " + Arrays.asList(array));
	}
}

运行结果

111 : [abc, ggg, dh, zzzz, o]
222 : [abc, ggg, dh, zzzz, o]
333 : -2
444 : [abc, ggg, dh, zzzz, o]
555 : [abc, ggg]
666 : true
777 : [abc, dh, ggg, o, zzzz]
888 : [abc, dh, ggg, o, zzzz]
999 : [new, new, new, new, new]

总结

集合可以存储不同类型的对象,并且长度可变

List集合:元素有序,可重复,可存储null元素,如果想要元素不重复,则存入对象需要重写equals方法

  ArrayList:底层数据结构是数组,查询快

  LinkedList:底层数据结构是链表,增删操作快

Set集合:元素无序,不可重复,可存储null元素

  HashSet:底层数据结构是哈希表,存取速度快,元素唯一、线程不同步

  TreeSet:底层数据结构式二叉树,可以对元素进行排序,元素有序、线程不同步

Mpa集合:集合存储的是键值对,键需要保证唯一,否则值将会被覆盖

  HashMap:底层数据结构是哈希表,允许使用null键和null值,线程不同步,但是效率高

  TreeMap:底层数据结构是二叉树,允许使用null键和null值,线程不同步,可以对元素进行排序

工具类:Collections是集合框架中的一个工具类,该类提供了一系列静态的方法来操作集合,而Collection是集合框架的一个顶层接口,二者直接或间接的关系。

posted @ 2017-01-28 19:51  不能差不多  阅读(1047)  评论(1编辑  收藏  举报