Java基础温习3

Java集合

Java5增加了Queue体系集合,代表一种队列集合实现.
所有的集合类都位于java.util包下,JDK1.5之后在java.util.concurrent包下提供了一些多线程支持的集合类.
Java的集合类主要由两个接口派生而出的:Collection和Map,它们时集合框架的根接口.

图片

使用Lambda表达式遍历集合

Java8为Iterable接口新增了一个forEach(Consumer action)的默认方法,Iterable时Collection的父接口.

迭代器Iterator接口

方法如下:

  1. boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回true;
  2. Object next():返回集合里的下一个元素;
  3. void remove();删除集合里上一次next方法返回的元素.
  4. void forEachRemaining(Cusumer action),这时Java8为Iterator新增的默认方法,该方法使用Lambda表达式来遍历集合元素.
    Iterator必须依附于Collection对象.
    Iterator在迭代Collection时,Collection不能发生变化,只有通过Iterator的remove方法删除上一次next()方法返回的集合元素这一种方式.否则会报错.
    Iterator迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改,程序立即引发异常.而不是显式修改后的结果.这样可以避免共享资源而引发的潜在问题.

使用foreach循环遍历集合元素

for(Object obj : books){}
同样的,在遍历时集合不能发生改变,否则ConcurrentModificationException异常.

Java8新增的predicate操作集合

Java8为Collectin集合新增了以removeIf(Predicate filter);方法

Java8新增的Stream操作集合

Java8为每个流式API提供了对应的Builder.例如IntStream.Builder();
独立使用Stream的步骤:

  1. 使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder.
  2. 重复调用Builder的add()方法向该流中添加多个元素.
  3. 调用Builder的build()方法获取对应的Stream.
  4. 调用Stream的聚集方法.

Set集合

特点:无序,不可重复

HashSet (源码的实现是HashMap)

特点:

  1. 不能保证元素的排列顺序
  2. HashSet不是同步的,多线程访问时必须通过代码保证同步.
  3. 元素值可以是null

实现:
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到对象的hashCode值,然后根据该hashCode值决定该对象在HashSet的存储位置.如果由两个元素通过equels()方法比较返回的是true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置.
也就是说HaseSet集合判断两个元素相等的标准是两个对象的equals()和hashCode()方法同时相等才相等contains().
所以当把对象放入HashSet中时,如果需要重写该对象对应类的equals()方法,则也应该重写hashCode()方法.规则是,当equals()方法比较返回true时,hashCode值也应该相等.
当hashCode()方法返回值相同时,如果equals()不相等,则会在同一个位置保存两个对象,采用链式结构方式,这是HaseSet效率会降低;
如果equals()相等,但hashCode()不相等,就相当于把相同的对象都保存在Set中,不符合Set的特性。

public class HashSetTest {
	public static void main(String[] args) {
		Set<G> set = new HashSet<G>();
		set.add(new G());
		System.out.println(set.size());//1
		set.add(new G());
		System.out.println(set.size());//2
	}
}
class G{
	
}

因为对象的hashCode()不同,equals()返回的是false;

重写hashCode()方法的一般步骤:

  • 把对象内每个有意义的实例变量计算出一个int类型的hashCode。计算的方法如下:
  1. boolean hashCode = (f ? 0 : 1);
  2. (byte,short,char,int) hashCode = (int) f;
  3. long hashCode = (int)(f^(f>>>32));
  4. float hashCode = Float.floatToIntBits(f);
  5. double long l = Double.doubleToLongBits(f); hashCode = (int)(l^(l>>>32));
  6. 引用类型 hashCode = f.hashCode();
  • 用第一步计算出来的多个hashCode值组合计算出来一个hashCode值返回;例如 retrun f1.hashCode() + (int)f2;为了避免求和时,偶尔产生相等,可以通过为各实例变量的hashCode值乘以任意一个指数后再相加。例如如下代码:return f.hashCode() * 19 + (int) f2 * 31;

如果向HashSet中添加一个对象后,后面程序修改了该可变对象的实例变量,则可能导致它与集合中的其他元素相同。这就有可能导致HasSet中包含两个相同的对象。但改变的对象其位置还在以前对象的位置(根据hashCode()计算的位置);

public class HashSetTest2 {
	public static void main(String[] args) {
		Set<R> set = new HashSet<R>();
		set.add(new R(5));
		set.add(new R(-3));
		set.add(new R(9));
		set.add(new R(-2));
		System.out.println(set);
		Iterator<R> it = set.iterator();
		R first = it.next();
		System.out.println(first); //-2
		first.count = -3;
		System.out.println(set); //-3,-3,9,5
		set.remove(new R(-3));
		System.out.println(set);//-3,9,5
		System.out.println("是否包含count为-2的元素" + set.contains(new R(-2))); //false
		System.out.println("是否包含count为-3的元素" + set.contains(new R(-3)));	//false
	}
}
class R {
	int count;
	public R(int count) {
		this.count = count;
	}
	@Override
	public String toString() {
		return "R [count=" + count + "]";
	}
	@Override
	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;
	}
	@Override
	public int hashCode() {	
		return this.count;
	}	
}

因此尽量避免去修改HashSet中的元素参与计算HashCode()、equals()的实例变量,否则将会导致HashSet无法正确操作这些集合元素。

LinkedHashSet类 (源码的实现是LinkedHashMap【使用的是双向链表】)

与HashSet类相似,但使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。遍历时的顺序和插入的相同。
由于LinkedHashSet需要维护元素的插入顺序。其(保存)性能略低于HashSet的性能。但在访问Set里的全部元素时将会有更好的性能,因为它以链表来维护内部顺序。

TreeSet类 (底层是TreeMap)

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。与HashSet集合相比,TreeSet还提供了如下几个额外的方法。

  1. Comparator comparator():如果TreeSet采用了定制排序(不是插入的顺序),则方法返回定制排序所使用的Camparator;如果TreeSet采用了自然排序,则返回null;
  2. Object first();返回集合中的第一个元素;
  3. Object last();
  4. Object lower(Object e);返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要是TreeSet集合里的元素)。
  5. Object higher(Object e);
  6. SortedSet subSet(Object fromElement,Object toElement);返回此Set的子集合,范围从fromElement(包含)到toElement(不包含);
  7. SortedSet headSet(Object toElement);返回Set的子集,由小于toElement的元素组成。
  8. SortedSet tailSet (Object fromElement);

与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用了红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序。默认情况下采用自然排序。

自然排序

Comparable接口是Java定义的一个排序接口,需要被排序的对象只需要实现该接口并实现方法即可。
Java的一些常用类已经实现Comparable接口:

  • BigDecimal,BigInteger 以及所有的数值型对应的包装类:按它们对应的数值大小进行比较。
  • Character 按字符的 UNICODE值进行比较。
  • Boolean true对应的包装类实例大于false对应的包装类实例。
  • String 按照字符串中字符的UNICODE值进行比较。
  • Date、Time:后面的时间、日期比前面的时间、日期大。
    如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常(添加第一个时可以直接添加,当添加第二个是就要调用接口里边的方法,如果没有就会抛出异常)。
    TreeSet把一个对象加入TreeSet集合中时,会调用对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到他的存储位置。如果两个队形通过方法的比较返回的是0,则TreeSet会认为他们相等,新对象无法添加到TreeSet集合中。

所有集合中存储的是对象的索引
因此当要把一个对象放入TreeSet中,重写该对象的对应的equals()方法时,应保证与compareTo(Object obj)方法有一致的结果,其规则是如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj);方法比较应返回0;

当TreeSet中的可变元素中的实例变量一旦改变时,当试图删除该对象时,TreeSet会删除失败(甚至集合中原有的、实例变量没被修改,但与修改后元素相等的元素也无法删除)。

定制排序

定制排序可以通过Comparator接口的帮助。该接口中包含一个int compare(T o1,T o2)方法;如果需要实现定制排序,则需要在创建TreeSet集合对象时,提供一个Comparator对象与该TreeSet集合关联。由该Comparator对象负责集合元素的排序逻辑。
构造器是:TreeSet(Comparator c);因为Comparator是一个函数式接口,因此可以使用Lambda表达式.以后比较大小就是通过该排序进行的.

EnumSet类

EnumSet是一个专门为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值.EnumSet的集合元素是有序的,EnumSet以枚举值在Enum类内的定义顺序来决定集合元素的顺序.
EnumSet在内部以位向量的形式存储,这种存储形式非常紧凑,高效,因此EnumSet对象占用内存很小,而且运行效率很好.尤其是进行批量操作(如调用containsAll()和retainAll()方法),如果其参数也是EnumSet集合,则该批量操作的执行速度也非常快。
EnumSet集合不允许加入null元素。
EnumSet没有暴露任何构造器,因此只能使用类方法创建EnumSet对象。

  • EnumSet allOf(Class elementType);创建一个包含指定枚举类里所有枚举值的EnumSet集合。
  • EnumSet complementOf(EnumSet s);创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此枚举类剩下的枚举值。(即新的EnumSet集合和原EnumSet集合的集合元素加起来就是该枚举类的所有枚举值)
  • EnumSet copyOf(Collection c);使用一个普通集合来常见EnumSet集合,集合中的值是一个枚举类的值。
  • EnumSet copyOf(EnumSet e);
  • EnumSet noneOf(Class elementType);创建一个元素类型为指定枚举类型的空EnumSet;
  • EnumSet of(E first , E… rest);创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类.
  • EnumSet range(E from,E to);创建一个包含从from枚举值到to枚举值枚举内所有枚举值的EnumSet集合.

各Set实现类的性能分析

HashSet和TreeSet是Set的两个典型的实现,HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
但必须指出的是,Set的三个实现类HashSet、TreeSet和EnumSet都是线程不安全的。如果有多个线程同时访问一个Set集合,并且有超过一个线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。此操作最好在创建时进行,以防对Set集合的意外非同步访问。

List集合

代表一个元素有序,可重复的集合。

Java8改进的List接口和ListIterator接口

List增加了一些根据索引来操作元素的方法:

  • void add(int index,Object element);
  • boolean addAll(int index,Collection c);
  • Object get(int index);
  • int indexOf(Object o);返回第一次出现的索引
  • int lastIndexOf(Object o);
  • Object remove(int Index);
  • Object set(int index,Object element);返回被替换的旧元素。index必须是有效索引
  • List subList(int fromIndex,int toIndex);
  • void replaceAll(UnaryOperator operator);根据operator指定的计算规则重新设置List集合的所有元素。
  • void sort(Comparator c); 根据Comparator参数对List集合的元素排序。
    在indexOf();方法中例如indexOf(new String(“abc”));在List中比较元素是通过equals()方法来确定的。依次对List中的各元素进行比较,返回true即确定对象。remove(Object obj)也是通过这个机制。
    与Set不同的是List还额外提供了一个listIterator()方法,此方法返回一个ListIterator,ListIterator接口继承了Iterator接口,在Iterator接口基础上增加了如下方法。
  • boolean hasPrevious();
  • Object previous();
  • void add(Object o);在指定位置插入一个元素。

ArrayList和Vector实现类

ArrayList和Vector类都是基于数组实现的List类,封装了一个动态地、允许在分配的Object[]数组。使用initialCapacity参数来设置数组的初始长度。
通常场景下不必关心ArrayList或Vector时,无须考虑initialCapacity。但如果向集合中添加大量的数据元素时,可以使用ensureCapacity(int minCapacity)方法一次性地增加initialCapacity。这个可以减少集合多次增加容量,从而提高性能。
如果开始就知道ArrayList或vector集合需要保存多少个元素,则可以在常见它们时及制定initialCapacity大小。
除此之外,ArrayList和Vector还提供了如下两个方法或重新分配Object[]数组。

  • void ensureCapacity(int minCapacity);将ArrayList或Vector集合的Object[]数组长度增加大于或等于minCapacity值。
  • void trimToSize();调整ArrayList或Vector集合的数组长度为当前元素的个数。减少对象占用内存。
    Vector从jdk1就有了,但集合框架是从1.2开始的,因此Vector可能有重复的方法。(1.2之后作为List的实现类),实际上Vector具有很多缺点,通常尽量少用Vector;
    除此之外,显著的区别是:ArrayList是线程安全的,Vector是线程安全的。Vector的性能要低。实际上即使需要保证List集合线程安全,也同样不推荐使用Vector实现类。后面会介绍一个Collections工具类,它可以将一个ArrayList变成线程安全的。
    Vector还提供了一个Stack子类,用于模拟“栈”这种数据结构,“栈”通常是指“后进先出”的容器。最后push进栈的元素将最后pop出栈。与其他集合一样他的元素都是Object;
  1. Object peek();返回栈中的第一个元素,但不出栈。
  2. Object pop();返回栈中的第一个元素,并将该元素pop出栈。
  3. void push(Object item):将一个元素push进栈,总是位于栈顶。
    由于Stack继承了Vector,因此他也是一个古老的Java集合类,它同样是线程安全,性能较差的,仅此尽量少用Stack类。如果程序需要使用栈这种数据结构,则可以考虑使用后面将要介绍的ArrayDeque;

固定长度的List

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

Queue集合

Queue用于模拟队列这种数据结构,队列通常是指“先进先出”的容器。
Queue接口中定义了如下几种方法:

  1. void add(Object e);将指定元素加入此队列尾部。
  2. Object element();获取队列头部的元素,但不是删除该元素。
  3. boolean offer(Object e);将指定的元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
  4. Object peek();获取队列头部元素,但不是删除该元素。如果此队列为空,则返回null;
  5. Object poll();获取队列头部的元素,并删除该元素。如果此队列为空,则返回null;
  6. Object remove();获取队列头部的元素,并删除该元素。
    Queue接口有一个PriorityQueue实现类。除此之外,Queue还有一个Deque接口,Deque代表一个“双端队列”,双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可当成队列使用,也可当成“栈”使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。

PriorityQueue实现类

PriorityQueue是一个标准的队列实现类。它不是一个标准的队列,不是按加入队列的顺序,而是按队列元素的大小进行重新排序。因此当调用Peek()方法或poll()方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。

	public static void main(String[] args) {
		Queue<Integer> q = new PriorityQueue<Integer>();
		q.add(2);
		q.offer(4);
		q.add(-4);
		q.offer(-9);
		System.out.println(q.element());//-9
		System.out.println(q.poll());//-9
	}

PriorityQueue不允许插入null元素,它会需要对队列元素进行排序,PriorityQueue的元素由两种排序方式.

  • 自然排序:采用自然顺序的PriorityQueue集合中的元素必须实现了Comparable接口,而且应该是同一个类的多个实例,否则可能导致ClassCassException异常.
  • 定制排序:常见ProiorityQueue队列时,传入一个Comparator对象,该对象负责对队列中的所有元素进行排序.采用定制排序时不要求队列元素实现Camparable接口.
    使用方式与前面的TreeSet相似.

Deque接口与ArrayDeque实现类

Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义一些双端队列的方法。

  • void addFirst(Objcet e);
  • void addLast(Object e);
  • Iterator descecdingIterator();返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。
  • Object getFirst();获取但不删除队列的第一个元素。
  • Object getLast();获取但不删除栓段队列的最后一个元素。
  • boolean offerFirst(Object e);将指定元素插入该双端队列的开头。
  • boolean offerLast(Object e);将指定元素插入到该双端队列的末尾。
  • Object peekFirst();获取但并不删除该双端队列的第一个元素;如果此双端队列为空,则返回null;
  • Object peekLast();获取但并不删除该双端队列的最后一个元素,如果此双端队列为空,则返回null;
  • Object pollFirst();获取并删除该双端队列的第一个元素;如果此双端队列为空,则返回null;
  • Object pollLast();获取并删除双端队列的最后一个元素;如果此上端队列为空,则返回null;
  • Object pop():(栈方法)pop出该双端队列所表示的栈顶元素。相当于removeFirst()。
  • void push(Object e);(栈方法)将一个元素push进该双端队列所表示的栈的栈顶。相当于addFirst(e);
  • Object removeFirst():获取并删除该双端队列的第一个元素。
  • Object removeLast();获取并删除该双端队列的最后一个元素。
  • Object removeFirstOccurrence(Object o);删除该双端队列中第一次出现的元素o;
  • Object removeLastOccurrence(Object o);
    从上边的方法可以看出,Deque不仅可以当成双端队列使用,而且可以被当成栈来使用,因为该类里边还包含了pop(出栈)、push(入栈)两个方法。
    Deque的方法与Queue方法的对比
    Deque add(e)/offer(e) remove()/po() element()/peek()
    Queue addLast(e)/offerLast() removeFirst()/pollFirst() getFirst()/peekFirst()

Deque的方法与Stack的方法对比
Stack push(e) pop() peek()
Deque addFirst(e)/offerFirst(e) removeFirst()/pollFirst() getFirst()/peekFirst()

Deque接口提供了一个典型的实现类:ArrayDeque,从该名称就可以看出,它是几个基于数组实现的双端队列,创建Deque时同时可指定一个numElements参数,该参数用于指定Object[]数组的长度:如果不指定numElement参数,Deque底层数组的长度为16;

	public static void main(String[] args) {
		Deque<Integer> d = new ArrayDeque<Integer>(8);
		d.push(4);
		d.push(8);
		d.push(3);
		d.push(1);
		System.out.println(d); //[1, 3, 8, 4]
		System.out.println(d.pop());//1
		System.out.println(d); //[3, 8, 4]
	}

因此当需要使用"栈"这种数据结构时,推荐使用ArrayDeque,应为Stack时古老的集合,性能较差.
当然ArrayDeque也可以当成队列使用,此处ArrayDeque将按"先进先出"的方式操作集合方式.

	public static void main(String[] args) {
		Deque<Integer> d = new ArrayDeque<Integer>(8);
		d.offer(1);
		d.offer(2);
		d.offer(3);
		System.out.println(d);//[1, 2, 3]
		System.out.println(d.peek());//1
		System.out.println(d.poll());//1
		System.out.println(d);//[2, 3]
	}

通过上面的两个例子,可以看出,ArrayDeque不仅可以使用栈使用,也可以作为队列使用.

LinkedList实现类

LinkedList是List接口的实现类,这意味着它是一个List集合,可以通过索引来访问集合中的元素。除此之外,LinkedList还实现了Deque接口,可以被当成双端队列来使用,因此既可以被当成栈来使用,也可以被当成队列来使用。

	public static void main(String[] args) {
		LinkedList<Integer> l = new LinkedList<Integer>();
		l.offer(2);
		l.push(1);
		l.offerFirst(3);
		System.out.println(l);
		System.out.println(l.get(2));
		System.out.println(l.peekFirst());
		System.out.println(l.peekLast());
		System.out.println(l.pop());
		System.out.println(l.poll());
	}

上面实例分别示范了LinkedList作为List集合和双端队列的用法,由此可见,LinkedList是一个功能强大的集合类.
ArrayList,ArrayDeque内部是以数组的形式实现的,因此随机访问集合元素时有较好的性能;而LinkedList内部是以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入,删除元素时性能比较出色.Vector也是以数组的形式来存储集合元素的,但因为实现了线程同步功能,所以各方面的性能比较差.
对于内部基于数组的集合实现,随用随机访问性能比使用Iterator迭代的性能要好.

各种线性表的性能分析

ArrayList的性能比LinkedList性能要更好(整体而言),因此大部分时候都应该考虑使用ArrayList.
关于List集合有如下建议:

  1. 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该使用迭代器(Iterator)来遍历集合元素。
  2. 如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可以考虑使用LinkedList集合。使用ArrayList、Vector集合可能重新分配内部数组的大小,效果可能较差。
  3. 如果有多个线程需要同时访问List集合中的元素,开发着可以考虑使用Collections将集合包装成线程安全的集合。

Java8增强的Map集合

保存key-value形式的数据,Map提供了一个Entry的内部类。key和value可以是任何引用类型的数据。
Map接口中定义的方法有:

  • void clear():删除Map对象中的所有key-value对。
  • boolean containsKey(Object key);查询Map中是否包含指定的key,如果包含则返回true;
  • boolean containsValue(Object value);查询Map中是否包含一个或多个vaule,如果包含则返回true;
  • Set entrySet();返回Map中包含的key-value对所组合成的Set集合,每个集合元素都是Map.Entry(Entry是Map的内部类)对象。
  • Object get(Object key);返回指定key所对应的value;如果此Map中不包含该key,则返回null;
  • boolean isEmpty();查询Map是否为空(即不包含任何key-value对)
  • Set keySet():返回该Map中所有key组成的Set集合;
  • Object put(Object key,Object value);添加一个key-value对,如果当前Map中已有一个与该key相等的key-value,则新的key-value将会覆盖原来的key-value对。
  • void putAll(Map m);将指定的Map中的key-value对复制到本Map中。
  • Object remove(Object key);删除指定key所对应的key-value对,返回被删除key所关联的value,如果key不存在,则返回null;
  • boolean(Object key,Object value);这是java8中新增的方法,删除指定key、value所对应的key-vale对,如果从Map中成功地删除该key-value对,该方法返回true,否则返回false。
  • int size();返回该Map里的key-value对的个数。
  • Collection values();返回Map里所有value组成的Collection。
    Map接口提供了大量的实体类,典型的有HashMap和Hashtable等、HashMap的子类LinkedHashMap,还有SortedMap子接口及该接口的实现类TreeMap,以及weakHashMap、IdentityHashMap等。
    Map中包含一个内部类Entry,该类封装了一个key-value对,Entry包含了如下三个方法:
  1. Object getKey();返回该Entry里包含的key值。
  2. Object getValue();返回该Entyr里包含的value值。
  3. Object setValue(V value);设置Entry里包含的value值,并返回value值。

Map的遍历使用的是Map提供的keySet()方法。

	public static void main(String[] args) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("疯狂Java讲义", 109);
		map.put("疯狂IOS讲义", 10);
		map.put("轻量级Java EE企业应用实战", 99);
		Object put = map.put("疯狂IOS讲义", 99);
		System.out.println(put);
		System.out.println("是否包含疯狂ioskey:" +
				map.containsKey("疯狂IOS讲义")
				);
		System.out.println("是否值为99的Value:" +
				map.containsValue(99)
				);
		for(String key : map.keySet()) {
			System.out.println(key + "------>" + map.get(key));
		}
		map.remove("轻量级Java EE企业应用实战");
	}

Java8为Map新增的方法

Java8除了为Map增加了remove(Object value,Object value)默认方法外,还增加了如下方法.

  • Object compute(Object key,BiFunction remappingFunction);使用remappingFunction根据原value-value对计算出一个新值.(1)来代替原来的值;(2)如果计算出来的结果为null,则删除该元素;(3)如果没有这个key,就添加该元素
  • Object computeIfAbsent(Object key,Function mappingFunction);如果key对应的值是null或者是不存在key所对应的元素,通过mappingFunction进行计算,是null的进行替换,不存在的进行添加(absent缺席的)
  • Object computeIfPresent(Object key,BinFunction remappingFunction);
  • void forEach(BiConsumer action);新提供的遍历方法
  • Object getOrDefault(Object key,V defaultValue);
  • Object merge(Object key,Object value,BiFunction remappingFunction);先根据key去集合中查找value,如果时null则用value的值去覆盖.如果不为null,则使用remappingFunction函数根据原value,新value计算出一个新的结果,并用得到的结果去覆盖原有的value.
  • Object putIfAbsent(Object key,Object value);该方法会自动检测指定的key对应key对应的值是否是null,如果是null,该方法就会用新的value代替原来的null值.
  • Object replace(Object key,Object value):将Map中指定key对应的value替换成新value.与传统put()方法相比,该方法不可能添加新的key-value,如果尝试替换key在原Map中不存在,该方法不会添加key-value对,而是返回null;
  • Object replace(Object key,V oldValue,V newValue);将Map中指定key-value对的原value替换成新value.如果在Map中找到指定的key-value对,则执行替换并返回true,否则返回false;
  • void replaceAll(BiFunction function);该方法使用BiFunction对原key-value对执行计算,并将计算结果作为key-value对的value值.
	public static void main(String[] args) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("西游记", "吴承恩");
		map.put("红楼名", "曹雪芹");
		map.put("三国演义", "罗贯中");
		map.put("水浒传", "施耐庵");
		map.put("葫芦娃", null);
		System.out.println(map);
		// 如果return为null,会删除该元素.否则是替换该元素的value
		map.compute("水浒传", (key, value) -> {
			return "施耐庵的哥哥";
		});
		// 如果没有该key,添加
		map.compute("哈哈哈", (key, value) -> {
			return "呵呵呵";
		});
		
		System.out.println(map);

		// absent(没有该键值对) //覆盖(1) 
		map.computeIfAbsent("葫芦娃", key -> key + "的弟弟");
		// 无效
		map.computeIfAbsent("水浒传", key -> "任妤程"); 
		//添加(2)
		map.computeIfAbsent("程之家", key -> "庄");
		System.out.println(map);
		
		//啦啦啦不存在,没有任何效果
		map.computeIfPresent("啦啦啦", (key,value) -> "傻不拉几");
		//没有任何作用(即使存在,但值是null)
		map.put("金刚", null);
		map.computeIfPresent("金刚", (key,value) -> "空变不为空");
		//上边存在且值不为null,进行修改
		map.computeIfPresent("水浒传", (key,value) -> "水浒传的原作者");
		System.out.println(map);
		map.forEach((key,value) -> System.out.println(key + "+" + value));
	
	}

感觉新增方法所谓的不存在时指不存在该key,或者key对应的value是null;

Java8改进的HashMap和Hashtable实现类

这两个类都是Map的子类,Hashtable是一个古老的Map实现类,现在很少使用.
Java8改进了HashMap的实现,使用HashMap存在key冲突时,依然具有较好的性能.
Hashtable和HashMap存在两点典型区别:

  • Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable性能更高一点;但如果有多个线程方位同一个Map对象时,使用Hashtable实现类会更好.
  • Hashtable不允许使用null作为key和value,如果试图把null放进Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为key或value;
    为了成功地在HashMap,Hashtable中存储,获取对象,用作key的对象必须实现hashCode()方法和equals()方法,HashMap和Hashtable都不能保证key-value对的顺序,类似于HashSet,HashMap和Hashtable判断两个key相等的标准也是:两个key通过equals()方法比较返回true,两个key的hashCode值也相等.containsKey();
    除此之外,HashMap和Hashtable中还包含一个containsValue()方法,用于判断是否包含指定的value.通过的方法是equals();
    与HashSet类似的是,如果使用可变对象作为HashMap,Hashtable的key,则不要修改key内参与equals()或hashCode()方法,否则就无法准确访问到Map中被修改郭的key.

LinkedHashSet实现类

该类是HashSet的子类,使用双向链表维护key-value对的次序.使迭代的顺序与添加的时候一样.性能略低于HashMap,但因为链表维护内部顺序,所以在迭代访问元素有较好的性能.

使用Properties读写属性文件

Properties类是Hashtable类的子类,可以把Map和属性文件关联起来,把Map对象中的key-value对写入属性文件,也可以把属性文件中的"属性名=属性值"加载到Map对象中.
Properties里的key和value都是字符串类型的.

  • String getProperty(String key);获取Properties中指定属性名对应的属性值,类似于Map的get(Object)方法.
  • String getProperty(String key,String defaultValue);获取属性值,如果没有就返回默认值.
  • Object setProperty(String key,String value);设置属性值,类似于Hashtable的put()方法.
  • void load(InputStream inStream);从属性文件(以输入流表示)中加载key-value对,把加载到key-value对追加到Properties里;(但不能保证顺序)
  • void store(OutputStream out,String comments);将Properties中的key-value对输出到指定的属性文件(以输出流表示)中.
	public static void main(String[] args) throws FileNotFoundException, IOException {
		Properties p = new Properties();
		p.setProperty("userName", "admin");
		p.setProperty("passWord", "123456");
		p.store(new FileOutputStream("a.ini"), "my Commonts");
		System.out.println(p);
		Properties p1 = new Properties();
		p1.setProperty("sex", "mail");
		p1.load(new FileInputStream("a.ini"));
		System.out.println(p1);
	}

同时在根目录下生成a.ini文件(不是资源目录)
文件内容是:

#my Commonts
#Mon Sep 02 16:03:24 CST 2019
passWord=123456
userName=admin

SortedMap接口和TreeMap实现类

TreeMap就是一个红黑树数据结构,每个key-value对作为红黑树的一个节点,TreeMap存储key-value对是,需要根据key对节点进行排序.TreeMap可以保证所有的key-value对处于有序状态.有两种排序方式:

  • 自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key应该是同一个类的对象,否则会抛出ClassCastException异常.
  • 定制排序:创建TreeMap时,传入一个Comparator对象,该队形负责对TreeMap中的所有key进行排序.采用定制排序时不要求Map的key实现Comparable接口.
  • 如果使用自定义类作为TreeMap的key,且想让TreeMap良好地工作,则重写该类地equals()方法和comparaTO()方法时应该一致.

与TreeSet类似地是,TreeMap中也提供了一系列根据key顺序访问key-value对的方法.

  • Map.Entry firstEntry()返回Map中最小key对应的key-value对,如果Map为空,则返回null.
  • Object firstKey():返回该Map中最小key值,如果Map为空,则返回null.
  • Map.Entry lastEntry();
  • Object lastKey();
  • Map.Entry higherEntry(Object key);返回该Map中位于key后一位的key值(即大于指定key的最小key所对应的key-value对),如果Map为空或不存在,则返回null;
  • Map.Entry lowerEntry(Object key)
  • Object Lower Key(Object key);
  • NavigableMap subMap(Object fromKey,boolean frominclusive,Object toKey,Boolean toInclusive):返回Map的子Map,第二个参数和第四个参数代表是否包含.
  • SortedMap subMap(Object fromKey,Object toKey):返回该Map的子Map,其ke从fromKey(包括)到toKey(不包含).
  • SortedMap tailMap(Object fromKey):返回给Map的子Map,其key的范围是大于fromKey(包括)的所有;
  • NavigableMap tailMap(Object fromKey,boolean inclusive);
  • SortedMap headMap(Object toKey);返回Map的子集,其key的范围小于toKey(不包括);
  • NavigableMap headMap(Object toKey,boolean inclusive);

WeakHashMap实现类

WeakHashMap与HashMap的用法基本相似.区别在于,WeakHashMap的key值保留了对实际对象的弱引用,这意味着被引用的对象可能会被垃圾回收.也会自动删除该key对应的key-value.

	public static void main(String[] args) {
		WeakHashMap<String, Object> map = new WeakHashMap<String, Object>();
		map.put(new String("数学"), "中等");
		map.put(new String("语文"), "及格");
		map.put(new String("英语"), "良好");
		map.put("java", "优秀");
		System.out.println(map); //{java=优秀, 数学=中等, 英语=良好, 语文=及格}
		System.gc();
		System.runFinalization();
		System.out.println(map);//{java=优秀}
	}

前三个是匿名字符串,WeakHashMap只保留了对它们的弱引用,这样垃圾回收时就会自动删除这三个key-value对.而第四个时一个字符串直接量(系统会保留对该字符串对象的强引用);
使用WeakHashMap实现类,尽量不要让该key所引用的对象具有任何强引用,否则将会失去使用WeakHashMap的意义.

IdentityHashMap

对于普通的HashMap判别key时只需要两个key的equals()方法返回true,且它们的hashCode值相等即可,但IdentityHashMap必须严格相等,即key1==key2.

	IdentityHashMap<String, Object> m = new IdentityHashMap<String, Object>();
	m.put(new String("语文"), 89);
	m.put(new String("语文"), 67);
	m.put("java", 23);
	m.put("java", 33);
	System.out.println(m);//{语文=89, java=33, 语文=67}

EnumMap实现类

EnumMap的所有key都必须时单个枚举类的枚举值.创建EnumMap时必须显式或隐式指定它的枚举类.有如下特征:

  • 在内部是以数组形式保存,所有这种实现形式非常紧凑,高效
  • EnumMap根据key的自然顺序(即枚举类在枚举类中的定义顺序)来维护key-value对的顺序.
  • EnumMap不允许使用null作为key,但允许null作为value.

创建方法: new EnumMap(Sensom.class);需要指定枚举类

性能分析

HashMap的性能好于Hashtable;
TreeMap的性能略于HashMap和Hashtable.但是它是有序的
一般情况下使用HashMap,但需要一个排好序的Map时,则使用TreeMap.
LinkedHashMap比HashMap慢一点,只需要维护链表来保存添加的顺序.
IndentityHashMap性能没有特别出色之处.
EnumMap性能最好,但它只能使用同一个枚举类的枚举值作为key.

HashMap的key和HashSet的存储都是按照数组加上上单向链表

操作集合的工具类:Collections

提供了对于List集合进行排序的方法:

  1. void reverse(List list);
  2. void shuffle(List list);对List集合元素进行随机排序(模拟了洗牌的动作)
  3. void sort(List list):对集合进行自然排序
  4. void sort(List list,Comparator c)
  5. void swap(List list,int i,int j);把i出元素和j处元素进行交换.
  6. void rotate(List list,int distance);当distance为正数时,将list集合的后distance个元素"整体"移到前边,当为负数时,将集合的前distance个元素"整体"移到后边.不会改变集合的长度.

查找,替换操作

  • int binarySearch(List list,Object key);使用二分法搜索指定的list,以获取对象在List集合中的索引.前提是元素已经处于有序状态.
  • Object max(Collection coll);根据元素的自然顺序,返回给定集合中的最大元素.
  • Object max(Collection coll,Comparator comp);
  • Object min(Collection coll);
  • Object min(Collection coll,Comparator comp);
  • void fill(List list,Object obj):使用obj替换指定List集合所有元素.
  • int frequency(Collection c,Object o);在集合中出现的次数.
  • int indexOfSubList(List source,List target);返回子list在父list中第一次出现的位置索引,如果target不是source集合的子集合,则返回-1;
  • int lastIndexOfSubList(List source,List target);
  • boolean replaceAll(List list,Object oldVal,Object newVal);

同步控制:

Collections类中提供了多个**synchronizedXxx()**方法,该方法课可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时线程安全问题.
HashSet,TreeSet,ArrayList,ArrayDeque,LinkedList,HashMap和TreeMap都是线程不安全的.

设置不可变集合:

  1. emptyXxxx();返回一个空的,不可变的集合对象,此处的集合既可以是List,也可以实SortedSet,Set还可以是Map,SortedMap等.
  2. singletonXxx(Object obj);返回一个只包含一个指定对象(只有一个或一项元素)的,不可变的集合对象,此处的集合既可以是List,还可以是Map.
  3. unmodifiableXxx(Collenction coll);返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set,SortSet,还可以是Map,SortedMap;

返回的集合对象都是只读的.

繁琐的接口:Enumeration

是Iterator迭代器的"古老版本";用于迭代Vector,Stack,Hashtable.
获取迭代器
coll.elements();

两个方法:
boolean hasMoreElements();
Object nextElement();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值