第8章:集合

8.1 集合概述

  1. 为保存数量不确定的数据,以及保存具有映射关系的数据,java提供了集合类
  2. 继承关系:红色部分为线程安全容器
    在这里插入图片描述
  3. Set、List、Queue、Map
    1. Set:不能重复,无法从指定位置插入取出元素,有序或无序
    2. List:可以重复,可以从在指定位置插入取出元素,有序
    3. Queue:可以重复,只能从头尾插入取出元素,模拟队列,有序
    4. Map:key放在一起为Set集合,value放在一起为Collection

8.2 Collection与Iterator接口

8.2.1 Collection方法:增删改查转
//集合中添加元素。一定注意,将元素插入集合失败不会报错,而是返回false
boolean add(Object o)
//集合中添加集合
boolean addAll(Collection c)
//删除元素o
boolean remove(Object o)
//删除c集合中所包含的元素
boolean removeAll(collection c)
//将符合filter条件的元素从集合中删除
//Predicate翻译过来叫做谓语(是、不是),或叫断言,实际上就是一个函数式接口,该函数式接口包含一个abstract的test方法,返回值为boolean类型。集合会将元素传给Predicate对象的test方法,返回值为true时,该元素被删除
boolean removeIf(Predicate filter)
//删除原集合中,与c没有交集的所有元素
boolean retainAll(collection c)
//清空集合
void clear()
//返回集合中元素个数
int size()
//集合长度是否为0
boolean isEmpty()
//集合中是否包含元素o
boolean contains(Object o)
//结合中是否包含c集合中所有元素
boolean containsAll(Collection c)
//返回Iterator对象来遍历集合中的元素
Iterator iterator()
//集合转数组
Object[] toArray()
//返回以该集合中元素作为元素的流
Stream<E> stream()
8.2.2 遍历集合中元素
  1. foreach循环遍历集合中元素
    1. foreach循环的本质是调用this对象的iterator方法获取迭代器,使用迭代器循环Collection或数组中每一个元素
    2. 与Iterator循环相同,不可以用Collection对象的remove方法对集合进行改变
    3. 由于foreach中没有Iterator的remove方法,所以foreach循环只能遍历集合中元素,无法修改集合中元素
  2. Iterator遍历集合
//Iterator对象必须依附于Collection对象
Iterator<String> it = books.iterator();
//方法一:
//hasNext:只要还没遍历完集合,就会返回true
while(it.hasNext()){
    //next:返回集合中的下一个元素
	String book = (String)it.next();
	if(book.equals("java疯狂讲义")){
	    //删除集合中最近一次next方法返回的元素
	    //Iterator迭代访问Collection中对象时,集合中元素只能通过Iterator的remove方法改变,不可用Collection对象的remove方法改变,否则会引发java.util.ConcurrentModificationException
	    //books.remove();
		it.remove();
	}
//方法二:
//该方法内部使用hashNext、next,对集合中所有元素进行循环,并做action操作
//default void forEachRemaining(Consumer<? super E> action)
it.forEachRemaining(c -> System.out.println("迭代集合中元素:" + c));
}

  1. Iterable遍历集合:Collection继承了Iterable类,拥有其forEach(Consumer action)方法,其内部相当于调用Iterable内部的iterator方法获取迭代器,从而循环Collection的所有元素,做action操作
Collection books = new HashSet();
...
books.forEach(c -> System.out.println("迭代集合中元素:" + c));
8.2.3 Stream操作集合

Java8新增

8.2.3.1 Stream简介
  1. Stream:
    1. 同一个高级版本的 迭代器(Iterator)
    2. 代表数据元素有限/无限的顺序,它不是数据结构,不会保存数据
    3. 是流式的:即所有对元素的操作可以链接成一个表达式
    4. 不会修改原来的数据源,它会将操作后的数据保存到新对象中
    5. Stream中的泛型表示该流中存放的元素类型
  2. Stream pipeline:
    1. 表示数据元素的一个多级的运算
    2. 包含一个源Stream,0个/多个中间操作,和一个终止操作
    3. 终止操作是单向的,且不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返
    4. 是lazy的,即调用终止操作时,才开始计算,对于终止操作不需要的数据元素,永远不会计算。
    5. 可以并行,但通常不建议
8.2.3.2 创建Stream对象
  1. Collection.stream/parallelStream():集合转为流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
  1. Arrays.stream():数组转为流
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
  1. Stream.of/iterate/generate():流的静态方法
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
//iterate第一个参数为Stream中的第一个元素,第二个参数为如何通过第一个元素产生后续元素的迭代方案
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
//generate方法中传入生产元素的方法 
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);
  1. BufferedReader.lines():将IO流,每一行对应的字符串作为一个元素,转为流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
  1. Pattern.splitAsStream(final CharSequence input):将字符序列input以Pattern规定的字符串进行分割,分割后的每个元素为流中元素
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
8.2.3.3 Stream中方法分类
  1. 中间操作(intermediate):中间方法的返回值为另一个流,只是对操作进行了记录,并不会立即执行
    1. 无状态
    //过滤Stream中所有不符合predicate的元素,组成新流
    filter(Predicate predicate)
    //按mapper的规则,将流中所有元素转换为其他对象,用这些新对象组成流
    map(Function mapper)
    //按mapper的规则,将流中所有元素转换为基本类型,用这些基本类型变量组成流
    mapToInt/Long/Double(ToInt/Long/DoubleFunction mapper)
    //按action的规则,修改流中元素的属性,并组成新流
    //该方法主要用于调试,与mapToXxx方法不同为,方法接收的对象一个为ToXxxFunction,另一个为Consumer,这两个对象中需被重写的方法,返回值一个为void,一个为boolean
    peek(Consumer action)
    //跳过前n个元素,组成新流
    skip(Long n)
    
    1. 有状态:会给流增加新的属性,例如流中"最多元素个数"、“不允许有重复元素”、“必须按顺序排序”。有状态方法需要更大的性能开销
    //用于保证对流的后续访问中,只对重复元素访问一次,判断重复的标准是equals返回true、hashCode方法相等
    distinct()
    //用于保证流中的元素在后续访问中处于有序状态
    sorted()
    //用于保证对该流的后续访问中最大允许访问的元素个数
    limit(long maxSize)
    
  2. 终止操作(terminal):末端方法后,流被"消耗",不可再用
    1. 非短路
    //遍历流中所有元素,对每个元素执行action
    forEach(Consumer action)
    //将流中所有元素转换为一个数组
    toArray()
    //该方法有三个重载版本,都用于通过某种操作来合并流中元素
    reduce(BinaryOperator accumulator)
    //返回如果流中元素按comparator排序时,最小值/最大值
    min/max(Comparator comparator)
    //数量
    count()
    //接收一个Collector实例,将流中元素收集成另外一个数据结构
    collect(Collector collector)
    
    1. 短路:遇到符合条件的元素就结束整个操作,不再对剩余元素处理
    //判断流中是否包含至少有一个/全/没有一个符合predicate的条件
    any/all/noneMatch(Predicate predicate)
    //返回流中第/任意一个元素
    findFirst/Any()
    
8.2.3.4 方法示例
  1. 中间操作示例
import java.io.FileNotFoundException;
import java.util.stream.Stream;

public class Student {
	int age;

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

	@Override
	public String toString() {
		return "Student [age=" + age + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		return true;
	}

	public static void main(String[] args) throws FileNotFoundException {
		// 6 4 6 7 3 9 8 10 12 14 14
		Stream<Student> stream = Stream.of(new Student(6), new Student(4), new Student(6), new Student(7),
				new Student(3), new Student(9), new Student(8), new Student(10), new Student(12), new Student(14),
				new Student(14));
		// 6 6 7 9 8 10 12 14 14 注意此处如果补重写hashCode与equals方法,将得不到想要的结果,因为不同Student对象是不同的
		Stream<Student> newStream = stream.filter(s -> s.age > 5)
				// 6 7 9 8 10 12 14
				.distinct()
				// 9 8 10 12 14
				.skip(2)
				// 9 8
				.limit(2)
				// 10 9
				.peek(c -> c.age = c.age + 1);
		// 9 10 利用map转为了新的只包含Integer元素的流
		Stream<Integer> intStream = newStream.map(c -> c.age).sorted();
		intStream.forEach(System.out::println);
	}
}
  1. 终止操作代码
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class TerminalTest {
	public static void main(String[] args) {
		List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

		boolean allMatch = list.stream().allMatch(e -> e > 10); // false
		boolean noneMatch = list.stream().noneMatch(e -> e > 10); // true
		boolean anyMatch = list.stream().anyMatch(e -> e > 4); // true

		Integer findFirst = list.stream().findFirst().get(); // 1
		Integer findAny = list.stream().findAny().get(); // 1

		long count = list.stream().count(); // 5
		Integer max = list.stream().max(Integer::compareTo).get(); // 5
		Integer min = list.stream().min(Integer::compareTo).get(); // 1

		// 第一次执行,以第一个元素作为x1,第二个元素作为x2,第二次执行时候,以上一次结果(x1+x2)作为x1,第三个元素作为x2,依此类推
		Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
		// 1+2+3+4+5
		System.out.println(v);

		List<Student> listStudent = Arrays.asList(new Student(2), new Student(2), new Student(3), new Student(4),
				new Student(5));

		List<Student> ageList = listStudent.stream().collect(Collectors.toList());
		// [Student [age=1], Student [age=2], Student [age=3], Student [age=4], Student
		// [age=5]]
		System.out.println(ageList);
		Set<Student> ageSet = listStudent.stream().collect(Collectors.toSet());
		// [Student [age=2], Student [age=3], Student [age=4], Student [age=5]]
		// 少了一个元素,因为Set有去重功能
		System.out.println(ageSet);

		// toMap方法中需要传入两个函数式接口,这两个函数式接口的方法需要有返回值,不能是void
		// 下方方法执行时报错,因为Map的key不能相同,但上面listStudent,有age相同的对象,所以转换时失败
		Map<Integer, Student> ageMap = listStudent.stream().collect(Collectors.toMap(c -> c.age, c -> c));
		System.out.println(ageMap);
	}
}

8.3 Set

不可重复插入

8.3.1 HashSet
  1. 重复判断标准:equals返回true且hashCode方法返回值相同
  2. 当调用了HashSet的add方法存放obj时,HashSet先调用obj的hashCode方法,得到一个hashCode值,将这个值转为一个数组下标,该下标标记了obj的位置,如果这个位置上的链表中没有元素,就把obj对象添加到链表上,如果链表中已有元素,则遍历该链表,调用obj的equals方法,判断obj是否和链表上的某个元素重复,不重复则添加,重复则不添加
  3. equals相等,hashCode值不等:两个对象都可以插入,且存放在不同的位置,但HashSet原则上不想插入相等的元素
  4. equals不等,hashCode相等:两个对象会被放到同一位置的链表上,影响性能
  5. 重写hashCode方法原则:
    1. hashCode应尽量与equals方法返回的结果一致
    2. 对象中用于equals方法中的实例变量,都应用于计算hashCode值
    3. 同一对象多次调用hashCode方法返回值应该相同
  6. 重写hashCode具体步骤:
    1. 把对象中每个参与equals方法的实例变量都计算出一个int类型的hashCode值,计算方式在p294-图8.1
    2. 用上一步算出的多个hashCode值组合计算出一个hashCode值并返回,为避免直接相加的偶然相等,可将各个实例变量的hashCode值再乘一个质数后再相加
  7. 向HashSet中添加可变对象时,尽量不要修改对象的参与equals,hashCode计算的实例变量的值,这可能导致HashSet无法准确访问该对象
例:HashSet中存放R类型的元素,该类中有成员变量count参与了equals和hashCode的计算。元素r1(count:-2)r2(count:-3),r3(count:5),r4(count:9)
//1.将第r1的count值由-2改为-3,此时调用方法remove(new R(-3));系统首先计算R(-3)对象的hashCode值,找其存放位置,然后将此位置对象用equals方法与R(-3)进行比较,相等即删除,此时r2被删除
//2.当调用contains(new R(-3))时,返回false,原理与上相同
8.3.2 LinkedHashSet
  1. 重复判断标准:equals返回true且hashCode方法返回值相同
  2. 按插入顺序排序
  3. HashSet子类,也根据hashCode值决定存储的位置,但同时使用链表维护元素次序,当遍历LinkedHashSet中元素时,会按元素插入顺序访问集合中元素
8.3.3 TreeSet
  1. 重复判断标准:Comparable的compareTo方法或Comparator的compare方法返回0
  2. 有序,分为定制排序与自然排序,自然排序下不可以插入null,因为会调用插入元素的compareTo方法
  3. SortedSet的实现类,TreeSet可确保集合元素处于排序状态
  4. TreeSet方法
Comparator comparator():如果TreeSet采用定制排序,返回定制排序所使用的的Comparator,如果为自然排序,返回null
Object first():返回第一个元素
Object last():返回最后一个元素
Object lower(Object e):返回小于e的最大元素,e无需在集合中
Object higher(Object e):返回大于e的最小元素,e无需在集合中
SortedSet subSet(Object fromEle,object toEle):将集合中fromEle到toEle(不包含)范围的元素作为子集合返回
SortedSet headSet(object toEle):返回toEle之前的所有元素所组成的子集
SortedSet tailSet(object fromEle):返回元素fromEle之后的所有元素的子集
  1. 自然排序与定制排序
    1. 自然排序(升序):
      1. 只有实现了Comparable接口的类的对象才可以以自然排序的方式放入TreeSet中,否则引发ClassCastException。因为如果不继承该类,则没有compareTo方法,自己在类中定义compareTo方法无效
      2. TreeSet会调用集合元素实现的Comparable类的compareTo(T o)方法来比较元素大小,然后按元素升序排列
      3. obj1.compareTo(obj2)方法返回一个整数,0表示两者相等,正整数表示obj1>obj2,负数表示obj1<obj2
      4. 如果希望TreeSet正常工作,TreeSet中只能添加同一类型的对象,因为大部分类在实现compareTo(Object obj)方法时,都需将被比较对象obj强制转换成相同类型,因为只有相同类型的两个实例才会比较大小,当将对象放入TreeSet时,TreeSet会调用这个被放入对象的compareTo方法,与集合中其他元素进行比较,并根据红黑树结构找到其存储位置
      5. 重写对象的compareTo方法应保证该方法与equals方法返回相同的结果,否则会造成明明"不同"的对象却无法被插入到TreeSet中
      6. TreeSet只能删除没有被修改实例变量,且不与其他被修改实例变量的对象重复的对象
    2. 定制排序
      1. 可在创建TreeSet时传入Comparator接口的对象,并重写其int compare(T obj1,T obj2)方法,如该方法返回正整数,表明obj1大于obj2,返回0表示obj1等于obj2,返回负整数表示obj1小于obj2,该方法负责对集合中元素进行排序,按compare方法的结果将集合中元素从小到大排序
      TreeSet ts = new TreeSet(new Comparator() {
      	@Override
      	public int compare(Object o1, Object o2) {
      	    //如果返回0表示任意两个元素都相等,即永远无法插入第二个元素
      		return 0;
      	}
      });
      
      1. 此时向集合ts中添加的元素不用再实现Comparable接口,因为此时不再使用对象的compareTo方法进行排序,而是调用Comparator中重写的compare方法排序
8.3.4 EnumSet
  1. 重复判断标准:同一枚举对象
  2. 按枚举值定义顺序进行排序,不可插入空(因为枚举值不可以为空)
  3. EnumSet中的元素必须是指定枚举类型的枚举值
  4. EnumSet为抽象类,不能通过构造器创建对象,但可以通过其提供的类方法创建其子类的实例,以下介绍EnumSet的所有类方法
EnumSet allOf(class elementType):将枚举类型为elementType的所有枚举值组成集合并返回
EnumSet complementOf(EnumSet s):返回S中不包含的所有枚举值的EnumSet
EnumSet copyOf(Collection c):使用普通集合创建EnumSet,该普通集合中所有元素必须为同一枚举类型的枚举值
EnumSet copyOf(EnumSet s):创建与s相同的新EnumSet
EnumSet noneOf(Class elementType):创建元素类型为elementType的枚举值的空EnumSet
EnumSet of(E first ,E ... rest):创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类
EnumSet range(E from ,E to):创建包含从from枚举值到to枚举值范围内所有枚举值的EnumSet
8.3.5 Set实现类性能分析
  1. EnumSet最快,但只能保存同一枚举类下的枚举值作为集合元素
  2. HashSet其次,但无序,LinkedHashSet相当于比HashSet多建了一个链表,用于维护集合中元素顺序,因此插入删除慢,但遍历快
  3. TreeSet更次,但可以按任意方式排序,做范围查询时候较快
  4. 以上三个实现类均线程不安全,如果多个线程访问同一个Set,必须手动保证Set集合的同步,通常可用Collections工具类的synchronizedSet来包装集合

8.4 List

可以通过索引来访问指定位置的集合元素,List默认按元素的添加顺序设置元素的索引,第一次添加的元素的索引为0,第二次为1,以此类推

8.4.1List接口与ListIterator接口
  1. List作为Collection接口的子接口,拥有其全部方法,由于List有序,所以增加了根据索引操作集合元素的方法
//将元素element插入到List集合的index处,index最多比List中现有的最大index大1,否则报错,如果在中间index处插入元素,其后所有元素后移1位
void add(int index,Object element)
//将集合c所包含的所有元素插入到index处
boolean addAll(int index,Collection c)
//Collection接口中有remove(Object o)方法,虽然int为Object子类,但不构成重写,形参列表只要不完全相同,就不构成重写,即如果传入int型,会调用remove(int index),传入其他类型时,先自动转型为Object,然后调用remove(Object o)方法
//删除并返回index索引处元素
Object remove(int index)
//将index处索引元素替换为element,并返回旧元素,此index必须为集合中有效索引,不会增加集合的长度
Object set(int index,Object element)
//返回指定index处元素
Object get(int index)
//返回o在集合中第一次出现的位置的索引
int indexOf(Object o)
//返回o在集合中最后一次出现的位置的索引
int lastIndexOf(Object o)
//返回从fromIndex到toIndex的子集合,包含fromIndex不包含toIndex
List subList(int fromIndex,int toIndex)
  1. List可以使用普通for循环来遍历集合
  2. List判断两个对象相等的标准为通过equals方法返回true
books.remove(new A()):会调用new A()这个对象的equals方法,从索引0处开始与集合中所有元素进行比较,找到第一个返回true的地方,将其删除
  1. List额外提供了一个listIterator方法,返回一个ListIterator对象,ListIterator接口继承Iterator接口,增加了一些专门操作List集合的方法
//返回迭代器关联的集合是否还有上一个元素
boolean hasPrevious()
//返回迭代器的上一个元素
Object previous()
//上一次迭代元素后添加元素o,迭代器初始指向索引为-1的元素,先使用正向迭代(next())方法到几个元素后,hasPrevious方法才会返回true
void add(Object o)
8.4.1 ArrayList与Vector实现类
  1. ArrayList与Vector都是基于数组实现的List类,即封装了一个动态的允许再分配的Object[]数组
  2. ArrayList和Vector对象使用initialCapacity参数来设置该数组长度,当添加元素个数超出该长度,他们的initialCapacity自动增加,默认为10,ArrayList线程不安全
  3. 固定长度的List:Arrays.asList(Object a,Object b,…):该方法将传入对象转换成一个List集合,但这个集合不是ArrayList或Vector的实例,而是Arrays的内部类ArrayList的实例,是一个固定长度的List集合,如果试图删除或增加该集合中元素,会引发UnsupportedOperationException

8.5 Queue接口

  1. Queue模拟队列这种数据结构,队列通常指尾插头出的容器,即新元素插入(offer)到队列的尾部,访问(poll)元素会返回队列的头部,通常不允许随机访问队列中的元素
  2. Queue定义了如下方法
//插入元素到队尾,越界返回false
boolean offer(Object e)
//插入元素到队尾,越界报错
boolean add(Object e)
//获取队列头元素,并删除,队列中无元素返回null
Object poll()
//获取队列头元素,并删除,队列中无元素报错
Object remove()
//获取队列头元素,不删除,队列中无元素返回null。即队列中元素不变,多次调用peek返回值相同
Object peek()
//获取队列头元素,不删除,队列中无元素报错
Object element()
8.5.1 PriorityQueue实现类
  1. 从PriorityQueue中取出元素时,不按插入顺序,而是按从小到大顺序
  2. PriorityQueue对象的toString方法不一定按从小到大顺序打印
    1. toString:[元素1,元素2,…]
  3. 有自然排序和定制排序,与TreeSet细节相同
8.5.2 Deque接口与ArrayDeque实现类
  1. Deque为Queue子接口,代表一个双端队列,即允许元素从头或尾进行插入和读取
  2. Deque定义了一些双端队列的方法,允许两端来操作队列中的元素
boolean offerFirst(Object e)
boolean offerLast(Object e)
//插入元素到队列头,越界报错
void addFirst(Object e)
//插入元素到队尾,越界报错
void addLast(Object e)
//为入栈(头进头出)方法,同addFirst
void push(Object e)

//获取队列头元素,并删除,队列中无元素返回null
Object pollFirst()
//获取队尾元素,并删除,队列中无元素返回null
Object pollLast()
//获取队列头元素,并删除,队列中无元素报错
Object removeFirst()
//获取队尾元素,并删除,队列中无元素报错
Object removeLast()
//为出栈(头进头出)方法,同removeFirst
Object pop()

//获取队列头元素,不删除,队列中无元素返回null
Object peekFirst()
//获取队列尾元素,不删除,队列中无元素返回null
Object peekLast()
//获取队列头元素,不删除,队列中无元素报错
Object getFirst()
//获取队尾元素,不删除,队列中无元素报错
Object getLast()
//从队列头开始遍历,删除第一次出现的元素o  Occurrence:[əˈkʌrəns]发生
boolean removeFirstOccurrence(Object o)
//从队尾开始遍历,删除第一次出现的元素o
boolean removeLastOccurrence(Object o)
//返回该双端队列对应的迭代器,该迭代器以逆向顺序迭代队列中的元素 descending:下降的
Iterator descendingIterator()
  1. ArrayDeque为Deque实现类,是基于数组实现的,可以指定numElements参数来指定队列容量,默认16
  2. ArrayDeque由于支持头进头出,所以可以模拟栈的行为,因此一般用ArrayDeque来代替Vector的Stack子类
8.5.3 LinkedList实现类

LinkedList实现List接口,所以可以根据索引随机访问集合中的元素,同时也实现了Deque接口,所以也可以被当做双端队列使用

8.5.4 各种线性表的性能分析
  1. 线性表分类
    1. 双向链表:开辟、销毁内存空间的次数相对较多,但不会造成内存空间的浪费
    2. 动态数组:开辟、销毁内存空间的次数相对较少,但可能造成内存空间浪费(可以通过缩容解决)
  2. ArrayList是通过动态数组实现的,LinkedList是通过双向链表实现的
  3. 对于插入和删除
    1. 动态数组:
      1. 当容量不够时,需要进行数组复制操作进行扩容
      2. 在指定位置插入、删除元素时,通过复制该元素后的数组来完成
    2. 双向链表:
      1. 指定位置插入、删除时,只需找到该元素,并维护相应的Entry中标记前一个元素和后一个元素所对应的指针即可
  4. 几种情况下线性表选择
    1. 如果频繁在尾部进行添加、删除操作,动态数组、双向链表均可选择
    2. 如果频繁在头部进行添加、删除操作,建议选择使用双向链表
    3. 如果有频繁的(在任意位置)添加、删除操作,建议选择使用双向链表
    4. 如果有频繁的查询操作(随机访问操作),建议选择使用动态数组
  5. 多线程访问集合中元素,应用Collections包装集合

8.6 Map

8.6.1 Map理解与基本方法
  1. Map中保存两组值,一组值可以组成Set集合,用于保存Map中的key,另一组值可以组成Collection,用于保存Map中的value,key和value都可以是任何数据类型
  2. Java先实现了Map,然后通过包装一个所有value都为null的Map就实现了Set集合,因此所有key放在一起就组成了一个Set集合,Map的key不允许重复,因此Set集合不允许插入重复数据
  3. Map接口方法
//添加一个key-value对,如果原来已有该key,则新value覆盖原value
Object put(Object key,Object value)
//将m中所有key-value对放入集合中
void putAll(Map m)
//删除key对应的key-value对,返回value值,如果key不存在,返回null
Object remove(Object key)
//删除所有key-value对
void clear()
//返回key所对应的value,都不包含此key,返回null
Object get(Object key)
//是否包含指定的key
boolean containsKey(Object key)
//是否包含指定的value
boolean containsValue(Object value)
//返回key-value对个数
int size()
//Map中是否没有元素(不包含任何key-value对)
boolean isEmpty()
//返回Map中key-value对所组成的Set集合,因为Map中key不允许重复,所以一定可以组成一个Set集合,每个元素都是Map的内部类Entry的对象
Set entrySet()
//返回所有key组成的Set集合
Set keySet()
//返回所有values组成的Collection
Collection values()
  1. Map.Entry的方法:entrySet方法可以获得Set<Map.Entry>,通过Iterator进行遍历即可得到每个Map.Entry对象
//返回Entry中的key值
Object getKey()
//返回Entry中的value值
Object getValue()
//设置Entry中的value值,并返回新value值
Object setValue(V value)
  1. for-each遍历Map集合
Map map = new HashMap();
for(Object key : map.keySet()){
	System.out.println(key+"-->"+map.get(key));
}   
  1. Map的toString()方法
{key1==value1,key2==value2,...}
8.6.2 HashMap与Hashtable实现类
  1. HashMap线程不安全,Hashtable线程安全但性能低,Hashtable拥有方法elements(类似于Map中的values()),keys(类似于keySet()),Hashtable不允许null作为key(会调用key的hashCode方法)和value(写死的,如果为空就抛异常,怀疑和多线程有关),HashMap就可以
  2. HashMap/Hashtable判断key相等的要求与HashSet相同,判断value相等的要求与Collection相同,equals方法比较返回true。即对于方法containsValue(),两个对象equals方法相同,即返回true
8.6.3 LinkedHashMap实现类

与LinkedHashSet相同

8.6.4 Properties实现类:用于读写配置文件
  1. Properties为Hashtable子类,该类可把Map对象和属性文件关联起来,从而将Map中的key-value对写入属性文件,也可以将属性文件中"属性名=属性值"加载到Map对象中,注意key与value都应该是String类型
  2. Properties提供的方法
//从文件(以输入流表示)加载key-value对,追加到Properties对象中,无法保证顺序,文件格式必须为key=value
void load(InputStream inStream)
//相当于返回String的Map的get方法
String getProperty(String key)
//如果不存在指定key,返回default
String getProperty(String key,String default)
//相当于Map的put方法,不会改变文件本身
Object setProperty(String key,String value)
//将comments内容输出到文件中,前面加#
void store(OutputStream out ,String comments)
  1. Properties可以将key-value对以XML文件格式保存,也可以从XML中加载key-value
8.6.5 SortedMap接口和TreeMap实现类
  1. SortedMap对key的要求与TreeSet对元素的要求完全相同
  2. TreeMap方法
//返回最小key所对应的key-value对,如果Map为空,返回null
Map.Entry firstEntry()
//返回最大key所对应的key-value对,如果Map为空,返回null
Map.Entry lastEntry()
//返回最小key,如果Map为空,返回null
Object firstKey()
//返回最大key,如果Map为空,返回null
Object lastKey()
//返回最大于key的最小key所对应的key-value对
Map.Entry higherEntry(Object key)
Object higherKey(Object key)
Map.Entry lowerEntry(Object key)
Object lowerKey(Object key)
//返回Map的子Map,是否包含由第二和第四个元素决定
SortedMap subMap(Object fromKey,Object toKey)
//fromInclusive/toInclusive决定返回的Map对象,是否包含fromKey与toKey对应的Entry
navigableMap subMap(Object fromKey,boolean fromInclusive,Object toKey,boolean toInclusive)
SortedMap tailMap(Object fromKey)
navigableMap tailMap(Object fromKey,boolean inclusize)
SortedMap headMap(Object toKey)
navigableMap headMap(Object toKey,boolean inclusize)
8.6.6 WeakHashMap实现类
  1. 与HashMap用法基本相似,一般用于缓存的实现
  2. 弱引用可以用来访问对象,但进行垃圾回收时弱引用并不会被考虑在内,仅有弱引用指向的对象仍然会被GC回收
  3. HashMap的key保留对实际对象的强引用,所以只要HashMap对象不被销毁,key对象永远不会被垃圾回收,所以key-value也不会被垃圾回收,而WeakHashMap只保留key对象弱引用,如果key对象没被其他强引用对象所引用,则这些key会被回收,其对应的key-value对也会被回收
  4. 更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情
    1. 调用两次size()方法返回不同的值
    2. 两次调用isEmpty()方法,第一次返回false,第二次返回true
    3. 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key
    4. 两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象
  5. 例:key为new String(“语文”),是匿名字符串对象,无强引用引用他,所以会被回收,key为"语文"是字符串直接量,系统会自动保存对该字符串对象的强引用,所以不会被回收
  6. 不应该让key对象具有任何强引用,否则会失去WeakHashMap的意义
8.6.7 IdentityHashMap实现类

Identity意思为"身份",IdentityHashMap判断key值相等的条件与HashMap不同

  1. HashMap:equals返回true、hashCode值相同
  2. IdentityHashMap:==返回true
8.6.8 EnumMap实现类:
  1. 对key的要求与EnumSet对元素的要求一致
  2. EnumMap的key,必须为构造方法所传入的枚举类的枚举值
//key必须为枚举类Season的枚举值中的一个
EnumMap a = new EnumMap(Season.class)
8.6.9 Map实现类的性能分析:

与Set相同,一般使用HashMap

8.7 操作集合的工具类:Collections

8.7.1 排序操作
//自然顺序对list升序排序
static void sort(List list)
//根据c对list升序排序
static void sort(List list,Comparator c)
//反转list中的元素顺序
static void reverse(List list)
//shuffle:洗牌 list中元素随机排序
static void shuffle(List list)
//swap:交换 交换i,j处元素
static void swap(List list,int i,int j)
//rotate:旋转 如果distance为正,将后distance个元素放在list开头,如果distance为负,将前distance元素放在list尾巴
static void rotate(List list,int distance)
8.7.2 查找与替换
//以二分法搜索list,返回元素o的索引,List中值必须处于有序状态,否则返回值不正确,元素多时,比indexOf效率高
static int binarySearch(List list,Object o)
//返回o出现次数
static int frequency(Collection c,Object o)
//返回target集合在source中第一次出现的位置索引,如果没有出现过,返回-1
static int indexOfSubList(List source,List target)
static int lastIndexOfSublist(List source,List target)
//根据元素自然排序,返回最大元素
static Object max(Collection coll)
static Object max(Collection coll,Comparator comp)
//根据comp排序,返回最小元素
static Object min(Collection coll,Comparator comp)
//用obj替换list中所有元素
static void fill(List list,Object obj)
//使用newVal替换list中所有oldVal元素
static boolean replaceAll(List list,Object oldVal,Object newVal)
8.7.3 获取线程安全的集合
//实现原理:
//1. 将集合转为了一个SynchronizedCollection对象,该对象为Collections工具类中的一个静态内部类,该内部类中成员变量c保存传入的集合,成员变量mutex保存自身,且这两个成员变量为final定义,不允许指向别处
//2. 当对该对象做add操作时,synchronized (mutex),即同一时间,只有一个线程可以对该集合进行add操作
//Xxxx一般为Collection、Set、List、Map
Xxxx c = Collections.synchronizedXxxx(Xxxx o);
8.7.4 获取不可变的集合

即只能访问集合元素,但不能修改集合元素

//返回空的不可改变的集合对象,返回值可以为List,Set,SortedSet,Map,SortedMap等等
static Xxxx emptyXxxx()
//返回只包含元素o的不可变的集合
static Xxxx singletonXxxx(Object o)
//返回包含o集合中所有元素的不可变的集合
static Xxxx unmodifiableXxxx(Collection/Map o)

8.8 Enumeration迭代器

  1. Enumeration作用与Iterator相同
  2. Enumeration几乎只能迭代Vector,Hashtable这种古老的集合
  3. Vector有使用elements方法获、Hashtable使用elements和keys方法,取该迭代器
  4. Enumeration中方法
boolean hasMoreElements():
Object  nextElement():
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值