Java基础系列之容器

9.1 Java Collections框架是什么?

Java Collections框架中包含了大量集合接口以及这些接口的实现类和操作他们的算法(例如排序、查找、反转、替换、复制、取最小元素、取最大元素),主要提供了List(列表)、Queue(队列)、Set(集合)、Stack(栈)和Map(印射表,存放键值对)。其中List、Queue、Set、Stack都继承Collection接口。

Collection是整个集合框架的基础,它里面存储一组对象,表示不同类型的Collections,它的作用只是提供维护一组对象的基本接口。

  1. Set表示集合。主要特点是集合中元素不能重复,因此存入Set的每个元素都必须定义equals()方法来确保对象的唯一性。该接口有两个实现类:HashSet和TreeSet。其中TreeSet实现了SortedSet接口,因此TreeSet容器中的元素是有序的。
  2. List称为有序的Collection。按顺序保存对象,所以能对列表中的每个元素的插入和删除位置进行精确的控制。可以保存重复对象。LinkedList、ArrayList和Vector都实现了List接口。
  3. Map提供了一个从键映射到值的数据结构。用于保存键值对,值可以重复,但键是唯一。Java中实现Map接口的类:HashMap、TreeMap、LinkedHashMap、WeakHashMap和IndentityHashMap。虽然实现了相同接口,但效率却不完全相同。HashMap是基于散列表实现的,采用对象的HashCode可以进行快速查询。LinkedHashMap采用列表维护内部的顺序。TreeMap基于红黑树的数据结构来实现的,内部元素是按需排列的。

在这里插入图片描述
在这里插入图片描述

9.2 什么是迭代器?

迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器对象中的各个元素,而又不必暴露该对象内部细节的方法。迭代器的代价小,因此迭代器通常被称为轻量级的容器。

迭代器注意事项:

  1. 使用容器的iterator()方法返回一个Iterator,然后通过Iterator的next()方法返回第一个元素
  2. 使用Iterator 的hasNext()方法判断容器中是否还有元素,若有,可以使用next()方法获取下一个元素。
  3. 可以通过remove()方法删除迭代器返回的元素。
public class Test {
	public static void main(String[] args) {
		LinkedList<String> list = new LinkedList<>();
		list.add("first");
		list.add("second");
		list.add("third");
		list.add("fourth");
		Iterator<String> iter = list.iterator();
		while (iter.hasNext()) {
			System.out.println(iter.next());
		}
	}
}

运行结果:first
     second
     third
     fourth

在使用iterator()方法时经常会遇到ConcurrentModificationException异常,这是由于在使用Iterator遍历容器的同时又对容器做增加或删除操作所导致的,或者由于多线程操作导致。当一个线程使用迭代器遍历容器的同时,另外一个线程对这个容器进行增加或删除操作。

public class TestConcurrentModificationException {
	public static void main(String[] args) {
		LinkedList<String> list = new LinkedList<>();
		list.add("first");
		list.add("second");
		list.add("third");
		list.add("fourth");
		Iterator<String> iter = list.iterator();
		while (iter.hasNext()) {
			String str = iter.next();
			System.out.println(str);
			if(str.equals("second"))
				list.add("five");
		}
	}
}

运行结果:first
     second
     异常
抛出上述异常的原因是当调用容器的iterator()方法返回Iterator对象时,把容器中包含对象的个数赋值给了一个变量expectedModCount,在调用next()方法时会比较变量expectedModCount与容器中实际对象的个数modCount的值是否相等,若二者不相等,则会抛出ConcurrentModificationException异常,因此在使用Iterator遍历容器过程中,如果对容器进行增加或删除操作,就会改变容器中的对象,从而抛出异常。
解决方法:在遍历过程中把需要删除的对象保存到一个集合中,等遍历结束后在调用removeAll()方法删除。

在多线程访问容器的过程中抛出ConcurrentModificationException异常怎么解决?

  1. JDK 1.5版本引入了线程安全的容器,有ConcurrentHashMap和CopyOnWriteArrayList等。
  2. 在使用迭代器遍历容器时对容器的操作放到synchronized代码块中,但当引用程序并发程度较高时,严重影响程序的性能。

Iterator与ListIterator有什么区别?
Iterator只能正向遍历,适用获取元素。ListIterator继承于Iterator,专门针对List,可以双向遍历List,同时支持元素修改。

9.3 ArrayList、Vector和LinkedList有什么区别?

ArrayList、Vector和LinkedList均为可伸缩数组,即可以动态改变长度的数组。

ArrayList和Vector都是基于存储元素的 Object[] array 来实现的,他们会在内存开辟一块连续的存储空间,因此,支持下标访问元素,但插入删除元素较慢。 ArrayList和Vector最大区别是synchronization(同步)的使用,没有一个ArrayList的方法是同步的,而Vector的绝大数方法都是直接或间接同步的,所以Vector是线程安全的,ArrayList线程不安全。性能略于ArrayList。
LinkedList采用双向列表来实现。对数据的索引要从头开始遍历,访问效率低,但插入删除效率高。LinkedList是非线程安全的容器

9.4 HashMap、Hashtable、TreeMap和WeakHashMap有哪些区别?

Map接口包括3个实现类:HashMap、Hashtable和TreeMap。Map中,是通过对象来进行索引,用来索引的对象叫Key,其对应的对象叫做value。

HashMap是一个常用的Map,他根据键的HashCode值存储数据,根据键可以直接获取它的值,访问速度快。

HashMap和Hashtable都采用了hash法进行索引,因此二者有许多相似之处:

  1. HashMap是Hashtable的轻量级实现(非线程安全),都实现了Map接口,主要区别在于HashMap允许空键值,而Hashtable不允许。
  2. HashMap把Hashtable的contains方法去掉了,改成了containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable继承自Dictionary类,而HashMap是Java1.2 引进的Map interface的一个实现。
  3. Hashtable方法实是线程安全的,HashMap不支持线程的同步。在多个线程访问Hashtable时,不需要进行线程同步,而HashMap,必须提供同步机制。效率而言,Hashtable较高。
  4. Hashtable使用Enumeration,HashMap使用Iterator。
  5. Hashtable和HashMap采用的hash/rehash算法几乎一样。
  6. Hashtable中,hash数组默认大小 11,增加方式 Old × 2 + 1。HashMap中,hash数组默认大小是16,而且一定是2的指数。
  7. hash值的使用不同,Hashtable直接使用对象的hashCode。

HashMap里面存入的键值对在取出时没有固定的顺序,是随机的。一般而言,在Map中插入、删除和定位元素,选择HashMap。而TreeMap实现了SortedMap接口,能够把它保存的记录根据键排序,取出的也是排序的键值对,如果需要自然排序或自定义排序遍历键,选择TreeMap。LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的相同,选择LinkedHashMap实现,它还可以按读取顺序来排列。

WeakHashMap与HashMap类似,不同之处:

WeakHashMap中key采用的是“弱引用”的方式,只要WeakHashMap中的key 不再被外部引用,就可以被垃圾回收器回收。而HashMap中key采用“强引用”方式,当HashMap中的key没有被外部引用时,只有在这个key从HashMap中删除后,才可以被垃圾回收器回收。

在Hashtable上下文中,同步指的是什么?

同步是一个时间点只能有一个线程可以修改hash表,任何线程在执行Hashtable的更新操作前都需要获取对象锁,其他线程则等待锁的释放。

如何实现HashMap的同步?

HashMap可以通过 Collections.synchronizedMap(new HashMap())来达到同步效果。该方法返回了一个同步的Map,该Map封装了底层的HashMap的所有方法,使得底层的HashMap在多线程的情况下是安全的。

9.5 用自定义类型作为HashMap或Hashtable的key需要注意哪些问题?

public class Test {
	public static void main(String[] args) {
		System.out.println("Use user defined class as key:");
		HashMap<String,String> map = new HashMap<>();
		map.put("a", "aa");
		map.put("a", "bb");
		
		Iterator iter = map.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry)iter.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			System.out.println(key + " " + value);
		}
	}
}

运行结果:Use user defined class as key:
    a bb

后面添加元素会覆盖之前的key。

package org.base;

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

public class Test {
	public static void main(String[] args) {
		System.out.println("Use String as key:");
		HashMap<Person,String> map = new HashMap<>();
		Person p1 = new Person("1", "name1");
		Person p2 = new Person("1", "name1");
		map.put(p1, "address1");
		map.put(p2, "address1");
		
		Iterator iter = map.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry)iter.next();
			Person key = (Person) entry.getKey();
			String value = (String) entry.getValue();
			System.out.println(key + " " + value);
		}
	}
}

class Person{
	String id;
	String name;
	
	public Person(String id, String name){
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}
}

运行结果:Use String as key:
     Person [id=1, name=name1] address1
     Person [id=1, name=name1] address1
     
表面上看,向HashMap中添加的两个键值对的key是相同的,为什么在后面添加的键值对没有覆盖前面的value呢?

添加键值对需要以下几个过程:

  1. 首先,调用key的hashCode()方法生成一个hash值h1,如果h1在HashMap中不存在,那么直接将键值对添加到HashMap中;如果这个h1不存在,那么找出HashMap中所有hash值为h1的key;
  2. 然后分别调用key的equals()方法判断当前添加的key是否与存在的key值相同。如果equals()方法返回true,说明添加的key已存在,那么HashMap会使用新的value值覆盖掉旧的值;如果equals()方法返回false,说明新增加的key不存在,因此会创建新的映射关系。
  3. 一般而言,对于不同的key值可能会得到相同的hash值,因此需要冲突处理。一般而言,处理冲突的方法有开放地址法、再hash法、链地址法等。

HashMap使用的是链地址法来解决冲突。

从HashMap中通过key查找value时,首先调用key的hashCode()方法来获取到key对应的值h,这样就可以确定键为key的所有值存储的首地址。通过调用key的equals()方法判断key内容是否相等。当equals()方法返回值为true时,对应的value才正确。

向HashMap中添加元素。

在上例中,使用自定义类作为HashMap的key,而没有重写hashCode()方法和equals()方法,默认使用的是Object类的hashCode()方法和equals()方法。Object类的方法规则比较如下:当参数obj引用的对象与当前对象为同一对象时,,返回true。hashCode()方法会返回对象存储的首地址,因此在向HashMap中添加对象时,调用equals()方法返回值为false,HashMap会认为它们是两个不同的对象,就会分别创建不同的映射关系。
因此为了实现在向HashMap中添加键值对,可以根据对象的内容来判断两个对象是否相等,这就需要重写hashCode()和equals()方法。

package org.base;

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

public class Test {
	public static void main(String[] args) {
		System.out.println("Use String as key:");
		HashMap<Person,String> map = new HashMap<>();
		Person p1 = new Person("1", "name1");
		Person p2 = new Person("1", "name1");
		map.put(p1, "address1");
		map.put(p2, "address2");
		
		Iterator iter = map.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry)iter.next();
			Person key = (Person) entry.getKey();
			String value = (String) entry.getValue();
			System.out.println(key + " " + value);
		}
	}
}

class Person{
	String id;
	String name;
	
	public Person(String id, String name){
		this.id = id;
		this.name = name;
	}
	
	
	@Override
	public int hashCode() {
		return id.hashCode();
	}


	@Override
	public boolean equals(Object obj) {
		Person p = (Person)obj;
		if(p.id.equals(this.id))
			return true;
		else
			return false;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}
}

运行结果:Use String as key:
     Person [id=1, name=name1] address2

自定义类作为HashMap的key时,需要注意:

  1. 如果要根据对象的相关属性来自定义对象是否相等的逻辑,此时需要重写equals()方法,一旦重写了equals()方法,那么就必须重写hashCode()方法。
  2. 自定义类的多项作为HashMap的key时,最好把这个类设计为不可变类。
  3. 如果两个对象相等,则这两个对象有相同的hashCode,反之不成立。

9.6 Collection和Collections有什么区别?

Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。实现该类接口主要有List和Set,该接口的设计目标是为各种具体的集合提供最大化的同一的操作方式。
Collections是针对集合类的一个包装类,它提供了一系列静态方法以实现对各种集合的搜索、排序、线程安全化等操作,大多数方法用来处理线性表。Collections如同一个工具类,服务于Collection框架。若在使用Collections类的方法时,对应的collection的对象为null,则这些方法会抛出NullPointerException。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值