@hainingwyx
2017-06-04T13:39:24.000000Z
字数 28616
阅读 1629
Java
Java集合可以分为Set、List、Map、Queue四种体系。Set是无序集合、List是有序集合、Queue是队列实现,Map保存映射关系。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。
集合类:为了保存数量不确定的数据以及保存具有映射关系的数据。集合里只能保存对象,不能保存基本类型。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所有集合类都位于java.util包下。
派生接口:Collection和Map
Collection是List、Set、Queue接口的父接口。
import java.util.*;public class CollectionTest{public static void main(String[] args){Collection c = new ArrayList();// 添加元素c.add("孙悟空");// 虽然集合里不能放基本类型的值,但Java支持自动装箱c.add(6);System.out.println("c集合的元素个数为:" + c.size()); // 输出2// 删除指定元素c.remove(6);System.out.println("c集合的元素个数为:" + c.size()); // 输出1// 判断是否包含指定字符串System.out.println("c集合的是否包含\"孙悟空\"字符串:"+ c.contains("孙悟空")); // 输出truec.add("轻量级Java EE企业应用实战");System.out.println("c集合的元素:" + c);Collection books = new HashSet();books.add("轻量级Java EE企业应用实战");books.add("疯狂Java讲义");System.out.println("c集合是否完全包含books集合?"+ c.containsAll(books)); // 输出false// 用c集合减去books集合里的元素c.removeAll(books);System.out.println("c集合的元素:" + c);// 删除c集合里所有元素c.clear();System.out.println("c集合的元素:" + c);// 控制books集合里只剩下c集合里也包含的元素books.retainAll(c);System.out.println("books集合的元素:" + books);}}
import java.util.*;public class ForeachTest{public static void main(String[] args){// 创建集合、添加元素的代码与前一个程序相同Collection books = new HashSet();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂Android讲义"));for (Object obj : books){// 此处的book变量也不是集合元素本身String book = (String)obj;System.out.println(book);if (book.equals("疯狂Android讲义")){// 下面代码会引发ConcurrentModificationException异常books.remove(book); //①}}System.out.println(books);}}
Iterator也是Java集合框架的成员,主要用于遍历,Iterable接口是Collection接口的父接口。
import java.util.*;public class CollectionEach{public static void main(String[] args){// 创建一个集合Collection books = new HashSet();books.add("轻量级Java EE企业应用实战");books.add("疯狂Java讲义");books.add("疯狂Android讲义");// 调用forEach()方法遍历集合,Collection集合直接调用父接口的默认方法// foreach 方法所需参数类型是一个函数型接口books.forEach(obj -> System.out.println("迭代集合元素:" + obj));}}
import java.util.*;public class IteratorTest{public static void main(String[] args){// 创建集合、添加元素的代码与前一个程序相同Collection books = new HashSet();books.add("轻量级Java EE企业应用实战");books.add("疯狂Java讲义");books.add("疯狂Android讲义");// 获取books集合对应的迭代器Iterator it = books.iterator();while(it.hasNext()){// it.next()方法返回的数据类型是Object类型,因此需要强制类型转换String book = (String)it.next();System.out.println(book);if (book.equals("疯狂Java讲义")){// 从集合中删除上一次next方法返回的元素it.remove();}// 对book变量赋值,不会改变集合元素本身book = "测试字符串";}System.out.println(books);}}
当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变,只有通过Iterator的remove方法删除上一次返回的集合元素才可以,否则引发java.util.ConcurrentModificationException异常
import java.util.*;public class IteratorErrorTest{public static void main(String[] args){// 创建集合、添加元素的代码与前一个程序相同Collection books = new HashSet();books.add("轻量级Java EE企业应用实战");books.add("疯狂Java讲义");books.add("疯狂Android讲义");// 获取books集合对应的迭代器Iterator it = books.iterator();while(it.hasNext()){String book = (String)it.next();System.out.println(book);if (book.equals("疯狂Android讲义")){// 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常books.remove(book);}}}}
迭代时检测到集合被修改将会引发异常,只有删除集合的某个特定元素才不会引发异常。
lambda表达式遍历Iterator
java8提供的forEachRemaining参数同样也是函数式接口。
import java.util.*;public class IteratorEach{public static void main(String[] args){// 创建集合、添加元素的代码与前一个程序相同Collection books = new HashSet();books.add("轻量级Java EE企业应用实战");books.add("疯狂Java讲义");books.add("疯狂Android讲义");// 获取books集合对应的迭代器Iterator it = books.iterator();// 使用Lambda表达式(目标类型是Comsumer)来遍历集合元素it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));}}
使用foreach循环遍历集合元素
同样当使用foreach迭代访问集合元素时,该集合不能被改变。
import java.util.*;public class ForeachTest{public static void main(String[] args){// 创建集合、添加元素的代码与前一个程序相同Collection books = new HashSet();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂Android讲义"));for (Object obj : books){// 此处的book变量也不是集合元素本身String book = (String)obj;System.out.println(book);if (book.equals("疯狂Android讲义")){// 下面代码会引发ConcurrentModificationException异常books.remove(book);}}System.out.println(books);}}
使用Predicate操作集合
Java8新增了一个removeIf(Predicate filter)方法,该方法将删除符合filter条件的所有元素。Predicate也是函数式接口,因此可以使用Lambda表达式作为参数。
import java.util.*;import java.util.function.*;public class PredicateTest{public static void main(String[] args){// 创建一个集合Collection books = new HashSet();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂iOS讲义"));books.add(new String("疯狂Ajax讲义"));books.add(new String("疯狂Android讲义"));// 使用Lambda表达式(目标类型是Predicate)过滤集合books.removeIf(ele -> ((String)ele).length() < 10);System.out.println(books);}}
使用Predicate可以简化集合的运算。callAll方法只统计满足Predicate条件的图书。
import java.util.*;import java.util.function.*;public class PredicateTest2{public static void main(String[] args){// 创建books集合、为books集合添加元素的代码与前一个程序相同。Collection books = new HashSet();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂iOS讲义"));books.add(new String("疯狂Ajax讲义"));books.add(new String("疯狂Android讲义"));// 统计书名包含“疯狂”子串的图书数量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));}public static int calAll(Collection books , Predicate p){int total = 0;for (Object obj : books){// 使用Predicate的test()方法判断该对象是否满足Predicate指定的条件if (p.test(obj)){total ++;}}return total;}}
Java 8新增的Stream操作
Java 8还新增了Stream、IntStream、LongStream、DoubleStream等流式API。
独立使用Stream的步骤如下:
import java.util.stream.*;public class IntStreamTest{public static void main(String[] args){IntStream is = IntStream.builder().add(20).add(13).add(-2).add(18).build();// 下面调用聚集方法的代码每次只能执行一个System.out.println("is所有元素的最大值:" + is.max().getAsInt());System.out.println("is所有元素的最小值:" + is.min().getAsInt());System.out.println("is所有元素的总和:" + is.sum());System.out.println("is所有元素的总数:" + is.count());System.out.println("is所有元素的平均值:" + is.average());System.out.println("is所有元素的平方是否都大于20:"+ is.allMatch(ele -> ele * ele > 20));System.out.println("is是否包含任何元素的平方大于20:"+ is.anyMatch(ele -> ele * ele > 20));// 将is映射成一个新Stream,新Stream的每个元素是原Stream元素的2倍+1IntStream newIs = is.map(ele -> ele * 2 + 1);// 使用方法引用的方式来遍历集合元素newIs.forEach(System.out::println); // 输出41 27 -3 37}}
Collection接口提供了一个stream()默认方法,该方法可返回该集合对应的流,接下来即可通过流API来操作集合元素。由于Stream可以对集合元素进行整体的聚集操作,因此Stream极大了丰富了集合的功能。
import java.util.*;import java.util.function.*;public class CollectionStream{public static void main(String[] args){// 创建books集合、为books集合添加元素的代码与8.2.5小节的程序相同。Collection books = new HashSet();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂iOS讲义"));books.add(new String("疯狂Ajax讲义"));books.add(new String("疯狂Android讲义"));// 统计书名包含“疯狂”子串的图书数量System.out.println(books.stream().filter(ele->((String)ele).contains("疯狂")).count()); // 输出4// 统计书名包含“Java”子串的图书数量System.out.println(books.stream().filter(ele->((String)ele).contains("Java") ).count()); // 输出2// 统计书名字符串长度大于10的图书数量System.out.println(books.stream().filter(ele->((String)ele).length() > 10).count()); // 输出2// 先调用Collection对象的stream()方法将集合转换为Stream,// 再调用Stream的mapToInt()方法获取原有的Stream对应的IntStreambooks.stream().mapToInt(ele -> ((String)ele).length())// 调用forEach()方法遍历IntStream中每个元素.forEach(System.out::println);// 输出8 11 16 7 8}}
Set集合不允许包含相同的元素,如果试图把两个相同的元素加入同一个Set集合中,添加操作失败,add方法返回false,且新元素不会被加入。
HashSet类是Set接口的典型实现。HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值来决定该对象在HashSet中存储位置。如果有两个元素通过equals方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同位置,也就可以添加成功。
特点:无序、非同步、可以包含null。
HashSet判断元素相等:1.equals方法比较相等;2.hashCode()方法返回值相等。所以如果重写equals方法,需要重写hashCode方法。
import java.util.*;// 类A的equals方法总是返回true,但没有重写其hashCode()方法class A{public boolean equals(Object obj){return true;}}// 类B的hashCode()方法总是返回1,但没有重写其equals()方法class B{public int hashCode(){return 1;}}// 类C的hashCode()方法总是返回2,且重写其equals()方法总是返回trueclass C{public int hashCode(){return 2;}public boolean equals(Object obj){return true;}}public class HashSetTest{public static void main(String[] args){HashSet books = new HashSet();// 分别向books集合中添加两个A对象,两个B对象,两个C对象books.add(new A());books.add(new A());books.add(new B());books.add(new B());books.add(new C());books.add(new C());System.out.println(books);//输出2A,2B,1C}}
重写hashCode的基本规则:
程序把可变对象添加到HashSet中之后,尽量不要修改参与运算的hashCode()、equals()的实例变量,否则会导致HashSet无法正确操作这些集合元素。因为对象的Hashcode改变了,实际存储的位置并没有变化,无法对其进行准确的索引。
import java.util.*;class R{int count;public R(int count){this.count = count;}public String toString(){return "R[count:" + count + "]";}public boolean equals(Object obj){if(this == obj)return true;if (obj != null && obj.getClass() == R.class){R r = (R)obj;return this.count == r.count;}return false;}public int hashCode(){return this.count;}}public class HashSetTest2{public static void main(String[] args){HashSet hs = new HashSet();hs.add(new R(5));hs.add(new R(-3));hs.add(new R(9));hs.add(new R(-2));// 打印HashSet集合,集合元素没有重复System.out.println(hs);// 取出第一个元素Iterator it = hs.iterator();R first = (R)it.next();// 为第一个元素的count实例变量赋值first.count = -3;// 再次输出HashSet集合,集合元素有重复元素System.out.println(hs);// 删除count为-3的R对象hs.remove(new R(-3));// 可以看到被删除了一个R元素System.out.println(hs);System.out.println("hs是否包含count为-3的R对象?"+ hs.contains(new R(-3))); // 输出falseSystem.out.println("hs是否包含count为-2的R对象?"+ hs.contains(new R(-2))); // 输出false}}
LinkedHashSet
LinkedHashSet根据元素的hashCode来决定元素的存储位置,但同时使用链表维护元素的次序。当遍历LinkedHashSet集合里的元素时,将会按照元素的添加顺序来访问集合里的元素。因为需要维护元素的插入顺序,性能略低于HashSet的性能,但在迭代访问Set里的全部元素时有很好的性能。
import java.util.*;public class LinkedHashSetTest{public static void main(String[] args){LinkedHashSet books = new LinkedHashSet();books.add("疯狂Java讲义");books.add("轻量级Java EE企业应用实战");System.out.println(books);//与添加顺序一致// 删除 疯狂Java讲义books.remove("疯狂Java讲义");// 重新添加 疯狂Java讲义books.add("疯狂Java讲义");System.out.println(books);}}
TreeSet
TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态。
import java.util.*;public class TreeSetTest{public static void main(String[] args){TreeSet nums = new TreeSet();// 向TreeSet中添加四个Integer对象nums.add(5);nums.add(2);nums.add(10);nums.add(-9);// 输出集合元素,看到集合元素已经处于排序状态System.out.println(nums);// 输出集合里的第一个元素System.out.println(nums.first()); // 输出-9// 输出集合里的最后一个元素System.out.println(nums.last()); // 输出10// 返回小于4的子集,不包含4System.out.println(nums.headSet(4)); // 输出[-9, 2]// 返回大于5的子集,如果Set中包含5,子集中还包含5System.out.println(nums.tailSet(5)); // 输出 [5, 10]// 返回大于等于-3,小于4的子集。System.out.println(nums.subSet(-3 , 4)); // 输出[2]}}
TreeSet采用红黑树的数据结构对元素进行排序。TreeSet支持两种排序方法:自然排序和定制排序。
1. 自然排序
TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按照升序排列。如果试图把一个对象添加到TreeSet时,则该对象必须实现Compareable接口的compareTo(Object obj)方法,否则抛出异常。
import java.util.*;class Err{}public class TreeSetErrorTest{public static void main(String[] args){TreeSet ts = new TreeSet();// 向TreeSet集合中添加两个Err对象ts.add(new Err());ts.add(new Err()); //Err没有实现compareTo方法,引发ClassCastException}}
大部分类在实现CompareTo方法时,需要将被比较对象强制转换成相同类型。因此向TreeSet中添加的应该是同一个类的对象,否则引发ClassCastException。
import java.util.*;public class TreeSetErrorTest2{public static void main(String[] args){TreeSet ts = new TreeSet();// 向TreeSet集合中添加两个对象ts.add(new String("疯狂Java讲义"));ts.add(new Date()); // 会引发异常}}
如果希望TreeSet能正常运作,TreeSet只能添加同一种类型的对象。
TreeSet判断对象相等:通过compareTo方法比较是否返回0
import java.util.*;class Z implements Comparable{int age;public Z(int age){this.age = age;}// 重写equals()方法,总是返回truepublic boolean equals(Object obj){return true;}// 重写了compareTo(Object obj)方法,总是返回1,总是认为不相等public int compareTo(Object obj){return 1;}}public class TreeSetTest2{public static void main(String[] args){TreeSet set = new TreeSet();Z z1 = new Z(6);set.add(z1);// 第二次添加同一个对象,输出true,表明添加成功System.out.println(set.add(z1)); //true// 下面输出set集合,将看到有两个元素System.out.println(set);// 修改set集合的第一个元素的age变量((Z)(set.first())).age = 9;// 输出set集合的最后一个元素的age变量,将看到也变成了9System.out.println(((Z)(set.last())).age);}}
注意equals方法应该和compareTo方法有一致的结果。
一旦改变了TreeSet集合里可变元素的实例变量,导致它的与其他对象的大小顺序发生改变,但TreeSet不会调整他们的 顺序,可能导致compareTo返回0。当试图删除该对象时,删除会失败,可以删除没有被修改的实例变量、切不与被修改的实例变量重复的变量。执行一次成功删除之后TreeSet会对集合中的元素重新索引,接下来就可以删除TreeSet中的所有元素了。
import java.util.*;class R implements Comparable{int count;public R(int count){this.count = count;}public String toString(){return "R[count:" + count + "]";}// 重写equals方法,根据count来判断是否相等public boolean equals(Object obj){if (this == obj){return true;}if(obj != null && obj.getClass() == R.class){R r = (R)obj;return r.count == this.count;}return false;}// 重写compareTo方法,根据count来比较大小public int compareTo(Object obj){R r = (R)obj;return count > r.count ? 1 :count < r.count ? -1 : 0;}}public class TreeSetTest3{public static void main(String[] args){TreeSet ts = new TreeSet();ts.add(new R(5));ts.add(new R(-3));ts.add(new R(9));ts.add(new R(-2));// 打印TreeSet集合,集合元素是有序排列的System.out.println(ts); // -3 -2 5 9// 取出第一个元素R first = (R)ts.first();// 对第一个元素的count赋值first.count = 20;// 取出最后一个元素R last = (R)ts.last();// 对最后一个元素的count赋值,与第二个元素的count相同last.count = -2;// 再次输出将看到TreeSet里的元素处于无序状态,且有重复元素System.out.println(ts); // 20 -2 5 -2// 删除实例变量被改变的元素,删除失败System.out.println(ts.remove(new R(-2))); // falseSystem.out.println(ts);//20 -2 5 -2// 删除实例变量没有被改变的元素,删除成功System.out.println(ts.remove(new R(5))); // true,之后会重新索引,可以删除所有元素了System.out.println(ts);// 20 -2 -2}}
2. 定制排序
如果需要实现定制排序,例如降序,需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联,由该Comparator对象负责集合元素的排序逻辑。可以用Lambda表达式代替Comparator对象,这时候无需实现Comparable接口。
import java.util.*;class M{int age;public M(int age){this.age = age;}public String toString(){return "M[age:" + age + "]";}}public class TreeSetTest4{public static void main(String[] args){// 此处Lambda表达式的目标类型是ComparatorTreeSet ts = new TreeSet((o1 , o2) ->{M m1 = (M)o1;M m2 = (M)o2;// 根据M对象的age属性来决定大小,age越大,M对象反而越小return m1.age > m2.age ? -1: m1.age < m2.age ? 1 : 0;});ts.add(new M(5));ts.add(new M(-3));ts.add(new M(9));System.out.println(ts);}}
List集合代表一个元素有序可重复的集合,集合中每个元素都有其对应的顺序索引。List作为Collection接口的子接口,与Set集合相比多了根据索引插入、替换、删除集合元素的方法。
import java.util.*;public class ListTest{public static void main(String[] args){List books = new ArrayList();// 向books集合中添加三个元素books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂Android讲义"));System.out.println(books);// 将新字符串对象插入在第二个位置books.add(1 , new String("疯狂Ajax讲义"));for (int i = 0 ; i < books.size() ; i++ ){System.out.println(books.get(i));}// 删除第三个元素books.remove(2);System.out.println(books);// 判断指定元素在List集合中位置:输出1,表明位于第二位System.out.println(books.indexOf(new String("疯狂Ajax讲义"))); //①//将第二个元素替换成新的字符串对象books.set(1, new String("疯狂Java讲义"));System.out.println(books);//将books集合的第二个元素(包括)//到第三个元素(不包括)截取成子集合System.out.println(books.subList(1 , 2));}}
List判断对象相等:equals方法返回true即可。
import java.util.*;class A{public boolean equals(Object obj){return true;}}public class ListTest2{public static void main(String[] args){List books = new ArrayList();books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂Android讲义"));System.out.println(books);// 删除集合中A对象,将导致第一个元素被删除books.remove(new A()); // ①System.out.println(books);// 删除集合中A对象,再次删除集合中第一个元素books.remove(new A()); // ②System.out.println(books);}}
List常用的默认方法sort()需要一个Comparator对象控制秩序,可使用Lambda表达式作为参数;默认方法replaceAll()需要一个函数式接口的UnaryOperator来替换所有集合元素,也可用Lambda表达式作为参数。
import java.util.*;public class ListTest3{public static void main(String[] args){List books = new ArrayList();// 向books集合中添加4个元素books.add(new String("轻量级Java EE企业应用实战"));books.add(new String("疯狂Java讲义"));books.add(new String("疯狂Android讲义"));books.add(new String("疯狂iOS讲义"));// 使用目标类型为Comparator的Lambda表达式对List集合排序books.sort((o1, o2)->((String)o1).length() - ((String)o2).length());System.out.println(books);// 使用目标类型为UnaryOperator的Lambda表达式来替换集合中所有元素// 该Lambda表达式控制使用每个字符串的长度作为新的集合元素books.replaceAll(ele->((String)ele).length());System.out.println(books); // 输出[7, 8, 11, 16]}}
Set只提供一个iterator()方法,List提供了listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口。
import java.util.*;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());}}}
ArrayList和Vector
两个类都是基于数组实现的List类,所以都封装了动态的可以再分配的Object[]数组。ArrayList对象和Vector对象使用initialCapacity参数来设置数组的长度。添加元素超出数组长度时,initialCapacity会自动增加。
Vector提供了一个子类Stack,这也是一个古老的集合,线程安全,性能较差,尽量少用。
区别在于:ArrayList时线程不安全的,Vector是线程安全的。所以Vector性能比ArrayList性能要低。
Vector提供了Stack子类,用于模拟后进先出(LIFO)的“栈”这种数据结构。同样线程安全、性能较差。如果需要实现“栈”,可以使用ArrayDeque。
固定长度的List
介绍数组时的Arrays类提供了asList(Object a)方法,该方法可以把一个数组或者指定个数对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。
import java.util.*;public class FixedSizeList{public static void main(String[] args){List fixedList = Arrays.asList("疯狂Java讲义", "轻量级Java EE企业应用实战");// 获取fixedList的实现类,将输出Arrays$ArrayListSystem.out.println(fixedList.getClass());// 使用方法引用遍历集合元素fixedList.forEach(System.out::println);// 试图增加、删除元素都会引发UnsupportedOperationException异常fixedList.add("疯狂Android讲义");fixedList.remove("疯狂Java讲义");}}
Queue用于模拟“先进先出”(FIFO)队列这种数据结构,通常队列不允许随机访问队列中的元素。Queue接口有一个PriorityQueue实现类和一个Deque接口,后者既可以当成队列使用,也可以当栈使用。
PriorityQueue实现类
比较标准的队列实现类--保存队列元素的顺序按照队列元素的大小重新排列。这是违反队列的先进先出的原则的
import java.util.*;public class PriorityQueueTest{public static void main(String[] args){PriorityQueue pq = new PriorityQueue();// 下面代码依次向pq中加入四个元素pq.offer(6);pq.offer(-3);pq.offer(20);pq.offer(18);// 输出pq队列,并不是按元素的加入顺序排列System.out.println(pq); // 输出[-3, 6, 20, 18]// 访问队列第一个元素,其实就是队列中最小的元素:-3System.out.println(pq.poll());}}
没有从小到大排序是因为受了toString方法返回值的影响。如果是使用poll()方法,则严格从小到大。
PriorityQueue两种排序方式:自然排序、定制排序,详细参考TreeSet。
Deque接口和ArrayDeque实现类
Deque接口代表一个双端队列,还可以当成栈使用。Deque接口提供了典型的实现类ArrayDeque,这是基于数组实现的双端队列。
ArrayDeque可作为“栈”,也可作为“队列”
注意:ArrayList和ArrayDeque两个集合类的实现机制基本相似,底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出了数组的容量时,系统会在底层重新分配一个Object[]数组来存储元素。
ArrayDeque作为栈:
import java.util.*;public class ArrayDequeStack{public static void main(String[] args){ArrayDeque stack = new ArrayDeque();// 依次将三个元素push入"栈"stack.push("疯狂Java讲义");stack.push("轻量级Java EE企业应用实战");stack.push("疯狂Android讲义");// 输出:[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]System.out.println(stack);// 访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义System.out.println(stack.peek());// 依然输出:[疯狂Android讲义, 疯狂Java讲义, 轻量级Java EE企业应用实战]System.out.println(stack);// pop出第一个元素,输出:疯狂Android讲义System.out.println(stack.pop());// 输出:[轻量级Java EE企业应用实战, 疯狂Java讲义]System.out.println(stack);}}
ArrayDeque作为队列
import java.util.*;public class ArrayDequeQueue{public static void main(String[] args){ArrayDeque queue = new ArrayDeque();// 依次将三个元素加入队列queue.offer("疯狂Java讲义");queue.offer("轻量级Java EE企业应用实战");queue.offer("疯狂Android讲义");// 输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]System.out.println(queue);// 访问队列头部的元素,但并不将其poll出队列"栈",输出:疯狂Java讲义System.out.println(queue.peek());// 依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]System.out.println(queue);// poll出第一个元素,输出:疯狂Java讲义System.out.println(queue.poll());// 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]System.out.println(queue);}}
LinkedList实现类
这是一个List接口的实现类,所以可以根据索引来随机访问集合中的元素。除此之外还实现了Deque接口,可作为双端队列。双端队列栈的头部和尾部分别对应队列的头部和尾部。
import java.util.*;public class LinkedListTest{public static void main(String[] args){LinkedList books = new LinkedList();// 将字符串元素加入队列的尾部books.offer("book1");books.offer("boook2");// 将一个字符串元素加入栈的顶部books.push("book3");books.push("book4");// 将字符串元素添加到队列的头部(相当于栈的顶部)books.offerFirst("book5");books.offerFirst("book6");// 以List的方式(按索引访问的方式)来遍历集合元素for (int i = 0; i < books.size() ; i++ ){System.out.println("遍历中:" + books.get(i));}// 访问、并不删除栈顶的元素 book6System.out.println(books.peekFirst());// 访问、并不删除队列的最后一个元素 book2System.out.println(books.peekLast());// 将栈顶的元素弹出“栈” book6System.out.println(books.pop());// 下面输出将看到队列中第一个元素被删除System.out.println(books);// 访问、并删除队列的最后一个元素book2System.out.println(books.pollLast());System.out.println(books);}}
LinkedList与ArrayList、ArrayDeque的实现机制完全不同。后两者内部以数组形式来保存集合中的元素,因此随机访问集合元素时性能较好;LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合中的元素时性能较差,但在插入、删除元素时性能较好。Vector和后两者都是以数组形式存储元素,,而Vector实现了线程安全,所以性能较差。
各种线性表的性能分析
数组一块连续内存区来保存所有的数组元素,所以在随机访问时性能最好,所有的内部以数组作为底层实现的集合随机访问时性能都比较好。而内部以链表作为底层实现的集合在执行插入、删除操作时性能较好。
List集合使用的几点建议
+ 需要遍历List集合元素时,对于ArrayList、Vector集合,应该使用随机访问方法来遍历集合元素;对于LinkedList应该采用迭代器来遍历。
+ 需要经常执行插入、删除来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList需要重新分配内部数组的大小,效果比较差。
+ 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collection将集合包装成线性安全的集合。
Map用于保存具有映射关系的数据,包含两组值,一组是key,一组是value。key不允许重复。Java源码中先实现了Map,然后通过包装一个所有value都为null的Map实现了Set集合。Map中包括一个内部类Entry,该类封装了一个key-value对。
import java.util.*;public class MapTest{public static void main(String[] args){Map map = new HashMap();// 成对放入多个key-value对map.put("疯狂Java讲义" , 109);map.put("疯狂iOS讲义" , 10);map.put("疯狂Ajax讲义" , 79);// 多次放入的key-value对中value可以重复map.put("轻量级Java EE企业应用实战" , 99);// 放入重复的key时,新的value会覆盖原有的value// 如果新的value覆盖了原有的value,该方法返回被覆盖的valueSystem.out.println(map.put("疯狂iOS讲义" , 99)); // 输出10System.out.println(map); // 输出的Map集合包含4个key-value对// 判断是否包含指定keySystem.out.println("是否包含值为 疯狂iOS讲义 key:"+ map.containsKey("疯狂iOS讲义")); // 输出true// 判断是否包含指定valueSystem.out.println("是否包含值为 99 value:"+ map.containsValue(99)); // 输出true// 获取Map集合的所有key组成的集合,通过遍历key来实现遍历所有key-value对for (Object key : map.keySet() ){// map.get(key)方法获取指定key对应的valueSystem.out.println(key + "-->" + map.get(key));}map.remove("疯狂Ajax讲义"); // 根据key来删除key-value对。System.out.println(map); // 输出结果不再包含 疯狂Ajax讲义=79 的key-value对}}
import java.util.*;public class MapTest2{public static void main(String[] args){Map map = new HashMap();// 成对放入多个key-value对map.put("疯狂Java讲义" , 109);map.put("疯狂iOS讲义" , 99);map.put("疯狂Ajax讲义" , 79);// 尝试替换key为"疯狂XML讲义"的value,由于原Map中没有对应的key,// 因此对Map没有改变,不会添加新的key-value对map.replace("疯狂XML讲义" , 66);System.out.println(map);// 使用原value与参数计算出来的结果覆盖原有的valuemap.merge("疯狂iOS讲义" , 10 ,(oldVal , param) -> (Integer)oldVal + (Integer)param);System.out.println(map); // "疯狂iOS讲义"的value增大了10// 当key为"Java"对应的value为null(或不存在时),使用计算的结果作为新valuemap.computeIfAbsent("Java" , (key)->((String)key).length());System.out.println(map); // map中添加了 Java=4 这组key-value对// 当key为"Java"对应的value存在时,使用计算的结果作为新valuemap.computeIfPresent("Java",(key , value) -> (Integer)value * (Integer)value);System.out.println(map); // map中 Java=4 变成 Java=16}}
HashMap和Hashtable实现类
HashMap和Hashtable类似于ArrayList和Vector的关系,Hashtable是一个古老的Map类。类似Vector尽量少用Hashtable,可以通过Collections工具类把HashMap变成线程安全的。
区别:
import java.util.*;public class NullInHashMap{public static void main(String[] args){HashMap hm = new HashMap();// 试图将两个key为null的key-value对放入HashMap中hm.put(null , null);hm.put(null , null); // 无法放入// 将一个value为null的key-value对放入HashMap中hm.put("a" , null); // ②// 输出Map对象System.out.println(hm);}}
HashMap、Hashtable判断key相等:两个key通过equal方法返回true;两个key的hashCode值相等。
HashMap、Hashtable判断value相等:两个对象通过equal方法返回true。
import java.util.*;class A{int count;public A(int count){this.count = count;}// 根据count的值来判断两个对象是否相等。public boolean equals(Object obj){if (obj == this)return true;if (obj != null && obj.getClass() == A.class){A a = (A)obj;return this.count == a.count;}return false;}// 根据count来计算hashCode值。public int hashCode(){return this.count;}}class B{// 重写equals()方法,B对象与任何对象通过equals()方法比较都返回truepublic boolean equals(Object obj){return true;}}public class HashtableTest{public static void main(String[] args){Hashtable ht = new Hashtable();ht.put(new A(60000) , "疯狂Java讲义");ht.put(new A(87563) , "轻量级Java EE企业应用实战");ht.put(new A(1232) , new B());System.out.println(ht);// 只要两个对象通过equals比较返回true,// Hashtable就认为它们是相等的value。// 由于Hashtable中有一个B对象,// 它与任何对象通过equals比较都相等,所以下面输出true。System.out.println(ht.containsValue("测试字符串")); // 输出true// 只要两个A对象的count相等,它们通过equals比较返回true,且hashCode相等// Hashtable即认为它们是相同的key,所以下面输出true。System.out.println(ht.containsKey(new A(87563))); // 输出true// 下面语句可以删除最后一个key-value对ht.remove(new A(1232));System.out.println(ht);}}
尽量不要使用可变对象作为HashMap、Hashtable的key,如果确实需要,尽量不要在程序中修改作为key的可变对象。
LinkedHashMap实现类
LinkedHashMap也是用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,其余key-value对的插入顺序保持一致。因为需要维护元素的插入顺序,性能不如HashMap,但在迭代访问Map里的全部元素时将有较好的性能。
import java.util.*;public class HashMapErrorTest{public static void main(String[] args){HashMap ht = new HashMap();// 此处的A类与前一个程序的A类是同一个类ht.put(new A(60000) , "疯狂Java讲义");ht.put(new A(87563) , "轻量级Java EE企业应用实战");// 获得Hashtable的key Set集合对应的Iterator迭代器Iterator it = ht.keySet().iterator();// 取出Map中第一个key,并修改它的count值A first = (A)it.next();first.count = 87563;// 输出{A@1560b=疯狂Java讲义, A@1560b=轻量级Java EE企业应用实战}System.out.println(ht);// 只能删除没有被修改过的key所对应的key-value对ht.remove(new A(87563));System.out.println(ht);// 无法获取剩下的value,下面两行代码都将输出null。System.out.println(ht.get(new A(87563))); //输出nullSystem.out.println(ht.get(new A(60000))); //输出null}}
LinkedHashMap实现类
使用双向链表维护key-value对。因为需要维护元素的插入顺序,性能低于HaspMap。
import java.util.*;public class LinkedHashMapTest{public static void main(String[] args){LinkedHashMap scores = new LinkedHashMap();scores.put("语文" , 80);scores.put("英文" , 82);scores.put("数学" , 76);// 调用forEach方法遍历scores里的所有key-value对scores.forEach((key, value) -> System.out.println(key + "-->" + value));}}
使用Properties读写属性文件
Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便。Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件,也可以把属性文件中的属性名=属性值加载到Map对象中。由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型
import java.util.*;import java.io.*;public class PropertiesTest{public static void main(String[] args)throws Exception{Properties props = new Properties();// 向Properties中增加属性props.setProperty("username" , "yeeku");props.setProperty("password" , "123456");// 将Properties中的key-value对保存到a.ini文件中props.store(new FileOutputStream("a.ini"), "comment line"); //写入文件// 新建一个Properties对象Properties props2 = new Properties();// 向Properties中增加属性props2.setProperty("gender" , "male");// 将a.ini文件中的key-value对追加到props2中props2.load(new FileInputStream("a.ini") ); //读取System.out.println(props2);}}
SortedMap和TreeMap实现类
Set接口派生出SortedSet子接口,TreeSet接口也有一个TreeSet实现类。
Map接口派生出SortedMap子接口,SortedMap接口也有一个TreeMap实现类。
两种排序方式:自然排序、定制排序,同TreeSet。
TreeMap判断key相等:两个key通过compareTo方法返回0。但是自定义类时需要equals方法和compareTo方法返回的结果一致。
import java.util.*;class R implements Comparable{int count;public R(int count){this.count = count;}public String toString(){return "R[count:" + count + "]";}// 根据count来判断两个对象是否相等。public boolean equals(Object obj){if (this == obj)return true;if (obj != null && obj.getClass() == R.class){R r = (R)obj;return r.count == this.count;}return false;}// 根据count属性值来判断两个对象的大小。public int compareTo(Object obj){R r = (R)obj;return count > r.count ? 1 :count < r.count ? -1 : 0;}}public class TreeMapTest{public static void main(String[] args){TreeMap tm = new TreeMap();tm.put(new R(3) , "轻量级Java EE企业应用实战");tm.put(new R(-5) , "疯狂Java讲义");tm.put(new R(9) , "疯狂Android讲义");System.out.println(tm);// 返回该TreeMap的第一个Entry对象System.out.println(tm.firstEntry());// 返回该TreeMap的最后一个key值System.out.println(tm.lastKey());// 返回该TreeMap的比new R(2)大的最小key值。System.out.println(tm.higherKey(new R(2)));// 返回该TreeMap的比new R(2)小的最大的key-value对。System.out.println(tm.lowerEntry(new R(2)));// 返回该TreeMap的子TreeMap 介于-1--4之间的keySystem.out.println(tm.subMap(new R(-1) , new R(4)));}}
Map实现类的性能分析
HashMap和Hashtable实现机制几乎一样,但是后者是线程安全的,HashMap通常比Hashtable快。
TreeMap比HashMap和Hashtable慢,因为其底层采用红黑树管理key-value对。优势在于:key-value对总是处于有序状态,无须专门进行排序操作。
以上应该多考虑使用HashMap。
LinkedHashMap比HashMap慢一点,因为需要维护链表维持添加顺序 。
Collections提供了大量方法对Set、List、Map进行排序、查询、修改操作。
排序
public class SortTest{public static void main(String[] args){ArrayList nums = new ArrayList();nums.add(2);nums.add(-5);nums.add(3);nums.add(0);System.out.println(nums); // 输出:[2, -5, 3, 0]Collections.reverse(nums); // 将List集合元素的次序反转System.out.println(nums); // 输出:[0, 3, -5, 2]Collections.sort(nums); // 将List集合元素的按自然顺序排序System.out.println(nums); // 输出:[-5, 0, 2, 3]Collections.shuffle(nums); // 将List集合元素的按随机顺序排序System.out.println(nums); // 每次输出的次序不固定}}
查找替换
import java.util.*;public class SearchTest{public static void main(String[] args){ArrayList nums = new ArrayList();nums.add(2);nums.add(-5);nums.add(3);nums.add(0);System.out.println(nums); // 输出:[2, -5, 3, 0]System.out.println(Collections.max(nums)); // 输出最大元素,将输出3System.out.println(Collections.min(nums)); // 输出最小元素,将输出-5Collections.replaceAll(nums , 0 , 1); // 将nums中的0使用1来代替System.out.println(nums); // 输出:[2, -5, 3, 1]// 判断-5在List集合中出现的次数,返回1System.out.println(Collections.frequency(nums , -5));Collections.sort(nums); // 对nums集合排序System.out.println(nums); // 输出:[-5, 1, 2, 3]//只有排序后的List集合才可用二分法查询,输出3System.out.println(Collections.binarySearch(nums , 3));}}
同步控制
HashSet、TreeSet、ArrayList、ArrayDeque、linkedlist、HashMap、TreeMap都是线程不安全的。Collections提供了许多类方法把他们包装成线程同步集合。
import java.util.*;public class SynchronizedTest{public static void main(String[] args){// 下面程序创建了四个线程安全的集合对象Collection c = Collections.synchronizedCollection(new ArrayList());List list = Collections.synchronizedList(new ArrayList());Set s = Collections.synchronizedSet(new HashSet());Map m = Collections.synchronizedMap(new HashMap());}}
设置不可变集合
import java.util.*;public class UnmodifiableTest{public static void main(String[] args){// 创建一个空的、不可改变的List对象List unmodifiableList = Collections.emptyList();// 创建一个只有一个元素,且不可改变的Set对象Set unmodifiableSet = Collections.singleton("疯狂Java讲义");// 创建一个普通Map对象Map scores = new HashMap();scores.put("语文" , 80);scores.put("Java" , 82);// 返回普通Map对象对应的不可变版本Map unmodifiableMap = Collections.unmodifiableMap(scores);// 下面任意一行代码都将引发UnsupportedOperationException异常unmodifiableList.add("测试元素"); //异常unmodifiableSet.add("测试元素"); //异常unmodifiableMap.put("语文" , 90); //异常}}