集合(2)

1、Set

    Set是不能包含相同元素的集合接口,向Set添加相同元素会失败,HashSet、TreeSet、EnumSet是Set接口的实现类。

    HashSet根据元素的hash值来决定元素的存储位置,其元素类型可以不同,元素值也可以是null,它判断元素是否相等的依据是根据equals()和hashCode()都返回相等。

    如果需要重写HashSet元素的equals()方法,那么也应该重写hashCode()方法,重写规则是:如果两个对象通过equals()比较返回true,那么这两个对象的hashCode值也也应该相同。对象中参与equals()、hashCode()计算的成员在加入到hashSet后不应该再修改其值,否则有可能导致无法精确访问该对象。对象包含多个成员时hashCode值的计算可以是多个成员的hash值与一个质数相乘后的和,如:hashCode = mem1.hashCode * 19 + mem2.hashCode * 31。

    可以通过构造函数的参数来指定HashSet的初始容量(默认16,超过负载极限后成倍增长)和负载极限(默认0.75)。

package xu;
import java.util.*; 

class Foo
{
	public String str;
	Foo(String s){str = s;}
	public boolean equals(Object o)
	{
		if(this == o)
			return true;
		if(o != null)	
			return str == ((Foo)o).str ? true : false;
		return false;
	}
	public int hashCode()
	{
		return str.hashCode();
	}
	public String toString()
	{
		return str;
	}
}


public class Test
{
	public static void main(String[] args)
	{	
		HashSet hs = new HashSet();
		
		hs.add(new Foo("c++"));
		hs.add(new Foo("java"));
		hs.add(new Foo("c#"));
		//hs.add(new String("php")); //元素类型可以不同
		hs.add(null); //元素可以为null
		
		System.out.println(hs); //输出[c#, null, c++, java]
		Foo[] ary = (Foo[])hs.toArray(new Foo[hs.size()]); //通过toArray方法将元素保存到数组中
		String s = Arrays.toString(ary); //s为[c#, null, c++, java]
		
		hs.contains(new Foo("c++")); //true
		hs.contains("php"); //true
		hs.contains(null); //true
	}
}

    LinkedHashSet是HashSet的子类,它使用链表来维护元素插入时的次序,使集合看起来是以插入顺序保存的。因为其额外使用链表来维护内部顺序,所以插入、删除元素时会比HashSet性能略低,但迭代遍历时效率很高。

    TreeSet是SortedSet接口的实现类,它采用红黑树数据结构来存储集合元素,其元素是有序的。TreeSet里的对象元素必须是实现了Comparable接口的类,或者在定义TreeSet时指定管理的Comparable。Comparable接口是一个仅包含抽象方法compareTo()的函数式接口。TreeSet只能添加同一种类型的元素,它只通过equals()方法判断两个元素是否相同。

    类似HashSet,当元素加入到TreeSet后不应再修改元素值,因为此时TreeSet不会进行排序。因为在Comparable接口实现compareTo方法中需要将比较对象强制转换成相同类型才能进行比较大小,所以TreeSet中元素类型应该相同,而且不能为null。

package xu;
import java.util.*; 

class Foo implements Comparable
{
	Foo(int i){ num = i;}
	public int num;
	public boolean equals(Object o)
	{
		if(this == o)
			return true;
		if(o != null && o.getClass() == Foo.class)
			return (num == ((Foo)o).num);
		return false;
	}
	public int compareTo(Object o)
	{
		Foo f = (Foo)o;
		return num > f.num ? -1 : num < f.num ? 1 : 0;
	}
	public String toString()
	{
		return "" + num;
	}
}

public class Test
{
	public static void main(String[] args)
	{	
		/*TreeSet ts = new TreeSet((Object o1, Object o2)->{
			Foo f1 = (Foo)o1;
			Foo f2 = (Foo)o2;
			return f1.num > f2.num ? -1 : f1.num < f2.num ? 1 : 0;
		});*/
		TreeSet ts = new TreeSet();
		
		ts.add(new Foo(5));
		ts.add(new Foo(3));
		ts.add(new Foo(10));
		
		System.out.println(ts); //输出为[10, 5, 3]
		
		String s = Arrays.toString(ts.toArray(new Foo[ts.size()])); //调用toString()方法将元素保存到数组中
	}
}

    EnumSet中元素必须是指定枚举类型的枚举值,其元素顺序是按照枚举值在枚举类中定义的顺序,不允许插入null元素,我们通过EnumSet中的noneOf()、allOf()、of()等方法来生成EnumSet对象:

package xu;
import java.util.*; 

enum Season
{
	SPRING, SUMMER, FALL, WINTER
}

public class Test
{
	public static void main(String[] args)
	{	
		EnumSet es1 = EnumSet.noneOf(Season.class); //EnumSet类型的空集合,集合元素是Season类的枚举值
		es1.add(Season.SPRING);
		System.out.println(es1); //输出为[SPRING]
		
		EnumSet es2 = EnumSet.allOf(Season.class); //集合包括Season类的所有枚举值
		System.out.println(es2); //输出为[SPRING, SUMMER, FALL, WINTER]
		
		EnumSet es3 = EnumSet.of(Season.SUMMER, Season.FALL);
		System.out.println(es3); //输出为[SUMMER, FALL]
	}
}
    EnumSet在内部以位向量的形式存储,是性能最高的,但其元素只能是同一枚举类的枚举值。
    HashSet性能比TreeSet要高,但TreeSet是按照元素值的大小有序保存的。

    LinkedHashSet是HashSet的子类,它通过链表使集合看起来是按照插入时顺序排序的,所以在插入、删除上的性能要比HashSet低,但在迭代遍历集合的时候效率很高。

2、List

    List是一个可重复的集合接口,可以通过索引来访问List中元素。除了Iterator迭代器,List还增加了一个ListIterator迭代器,以支持向前迭代,ListIterator还支持add()添加元素(Iterator只能通过remove()删除元素)。

    ArrayListVector是List接口的实现类,他们内部封装了一个自动增加大小的动态Object[]数组,可以在其构造函数中指定数组初始大小(数组默认大小是10),也可以调用ensureCapacity()增加数组的大小。Vector是老旧的集合,推荐使用ArrayList。因为ArrayList内部基于动态数组,所以其在随机访问时性能很好,应该使用随机访问方法get()来遍历集合中元素。

package xu;
import java.util.*; 


public class Test
{
	public static void main(String[] args)
	{	
		ArrayList l = new ArrayList();
		
		l.add(new String("c++"));
		l.add(new String("java"));
		l.add(new String("c#"));
		//l.add(32); //元素类型可以不同
		//l.add(null); //元素可以为null
		
		String s = Arrays.toString(l.toArray(new String[l.size()])); //toArray方法获得元素数组,s为[c++, java, c#]
		
		
		l.sort((o1, o2)->((String)o1).length() - ((String)o2).length()); //以字符串长短排序
		System.out.println(l); //输出为[c#, c++, java]
		
		l.replaceAll(item->((String)item).length()); //替换元素值为原元素的长度
		int s = l.size();
		for(int i = 0; i < s; i++)
			System.out.println(l.get(i)); //输出为2, 3, 4
		
		ListIterator it = l.listIterator();
		while(it.hasNext())
		{
			System.out.println(it.next()); //输出为-1, 4, -1, 3, -1, 2
			it.add(-1);
		}
			
		while(it.hasPrevious())
			System.out.println(it.previous()); //输出为4, 3, 2
	}
}

    LinkedList也是List的实现类,它同时也实现了Deque接口,其内部以链表为底层实现,所以在中间插入、删除元素时有更好的性能,应该使用迭代器来遍历集合中元素。

package xu;
import java.util.*; 


public class Test
{
	public static void main(String[] args)
	{	
		LinkedList l = new LinkedList();
		l.add(new String("c++"));
		l.add(new String("java"));
		l.add(1, new String("c#"));
		//l.add(34); //元素类型可以不同
		//l.add(null); //元素可以为null
		
		System.out.println(l); //输出为[c++, c#, java]
		
		String s = Arrays.toString(l.toArray(new String[l.size()])); //s为[c++, c#, java]
		
		ListIterator it = l.listIterator();
		while(it.hasNext())
		{
			System.out.println(it.next());
			it.add(new String("php"));
		}
		System.out.println(l); //输出为[c++, php, c#, php, java, php]
	}
}

    Stack是Vector的子类,它用来模拟LIFO的栈,同样推荐使用ArrayDeque来替换Stak。

    工具类Arrays里的asList()会返回一个List类型对象,不能增加和删除这个集合里的元素,这个List实际上是Arrays的内部类类型,属于固定长度的List集合。

3、Queue

    Queue接口用于模拟FIFO的队列,它允许重复的元素,并且通常不允许随机访问队列中元素。通过使用add()/offer()方法添加元素到队尾,调用peek()/poll()访问队头元素,其实现类有优先级队列PriorityQueue。

    PriorityQueue是优先级队列,它按照元素的大小进行排序,队首元素是最小的元素,队尾元素是最大的元素。类似TreeSet,其元素通过实现Comparable接口来实现自定义的排序。

    Deque接口用于双端队列,可以从两端来添加、删除元素,它是Queue的子接口,Deque的实现类也可以当成栈来使用(包含push()、pop()方法)。Deque的实现类有ArrayDeque和LinkedList。

    ArrayDeque是一个基于动态可再分配的数组实现的双端队列(数组默认长度为16),它可以用作(双端)队列和栈。

4、map

   Map的key不允许重复,常用方法:

    Object get(Object key):获得指定key的value,key不存在的话返回null。
    Object getOrDefault(Object key, V DefaultValue): 获得指定key的value,key不存在的话返回DefaultValue。
    put(Object key, Object value):增加一个元素(已存在的话会替换)。
    replace(key, newValue)/replace(key, oldValue, newValue):替换指定元素(根据元素的key或key-value)的value(不存在的话不会添加)。
    containsKey()/containsValue():判断是否存在指定的key/value。
    keySet():获得所有key组成的集合。
   merge(Object key, Object DefaultValue, BiFunction func):如果key对应的value为null则使用DefaultValue替换旧的value,否则使用func方法替换旧的value,如果key不存在的话会以key-DefaultValue增加元素。
    forEach():遍历集合。

    set entrySet():将Map转换成set,set元素类型为Entry。

    Entry为Map接口中内部类,它类似c++中pair,代表键值对类型,包含成员方法getKey()、getValue()、setValue()。

    HashMap、Hashtable都是Map接口的实现类,Hashtable是老旧的类,不推荐使用。HashMap的key与HashSet中元素类似,必须实现hashCode()和equals()方法,不要修改元素的key值,key类型可以不同,key可以为null。

package xu;
import java.util.*; 


public class Test
{
	public static void main(String[] args)
	{	
		Map m = new HashMap();
		
		m.put(80, "java");
		m.put(10, "python");
		m.put(90, "c++");
		m.put(120, null);
		//m.put("number", 60); //key类型可以不同
		//m.put(null, null); //key可以为null
		
		System.out.println(m); //输出为 {80=java, 120=null, 10=python, 90=c++}
		
		int k = 10;
		m.merge(k, "unknown", (oldValue, defaultValue)->k + ":" + oldValue + "-" + defaultValue);
		
		for(Object key : m.keySet())
		{
			String str = (String)(m.get(key));
			System.out.println(str); //输出为java, null, 10:python-unknown, c++
		}
		
		m.forEach((key, value)->System.out.println(key + ":" + value));
		//输出为
		//80:java
		//120:null
		//10:10:python,unknown
		//90:c++
	}
}

    LinkedHashMap是HashMap的子类,它通过双向链表来维护元素插入时的顺序,迭代遍历其中的所有元素会有较好的性能。

    TreeMap类实现了SortedMap接口, SortedMap接口由Map接口派生,类似TreeSet,它采用红黑树结构来保存元素,根据key为元素排序存储。与TreeSet类似,其key类型不能不同,key不能为null。

    HashSet、TreeSet实际上是value为null的HashMap、TreeMap。

		TreeMap m = new TreeMap();
		m.put(80, "java");
		m.put(10, "python");
		m.put(90, "c++");
		m.put(120, null);
		
		System.out.println(m); //输出为 {10=python, 80=java, 90=c++, 120=null}

    WeakHashMap中key是对象的弱引用,如果key所引用的对象没有被其它强引用变量所引用,那么key所引用的对象可能被垃圾回收,而此时WeakHashMap也可能自动删除这个key-value元素。

   IdentityHashMap与HashMap不同的是:HashMap以key的equals()方法和hashCode()来判断两个key是否相等,IdentityHashMap以==运算符来判断两个key是否相同。

    EnumMap中的key必须是指定枚举类的枚举值。

    Properties类相当于是一个key和value都是String类型的Map,使用它可以将key-value值写入到ini或xml等属性文件,或者从属性文件中读取key-value值,其常用方法有:

    setProperty():增加key-value值。
    store():写入到文件。
    load():从文件加载。

    getProperty():获取指定的key-value。

package xu;
import java.util.*;
import java.io.*;

public class Test
{
	public static void main (String[] args)throws Exception
	{
		//向ini文件写入
		Properties props = new  Properties();
		props.setProperty("name", "leon");
		props.setProperty("city", "peking");
		props.store(new FileOutputStream("config/data.ini"), "section");
		
		//从ini文件读出
		Properties props = new  Properties();
		props.load(new FileInputStream("config/data.ini"));
		String strName = props.getProperty("name");
		String strCity = props.getProperty("city");
	}
}

5、各集合的特点

    Set中不能包含相同元素,Map中也不能包含相同的key,List、Queue则可以包含相同的元素。
   List、Queue按元素插入时的顺序存储,Set、Map中顺序为乱序(HashSet、HashMap)或者按照元素大小(TreeSet、TreeMap)排序。
    Map包含通过key查找value的方法get(Object o),List、Queue中查找元素方法get(int idx)只能通过索引值,Set中仅包含判断元素是否存在的contains(Object o)方法。

    ArrayList、ArrayDeque适合经常遍历集合或者通过索引随机访问元素的情况。

6、Collections

    ①、工具类Collections中提供了对集合进行操作的静态方法,如对List进行查找、取最大最小、统计元素出现次数(frequency)、统计子List位置、替换(replaceAll)、反转、排序、随机排序(shuffle)、元素交换、元素移动(rotate)等。

    ②、Collections提供了unmodifiableXxx()方法来返回指定集合的只读视图:

		Map m = new HashMap();
		Map um = Collections.unmodifiableMap(m);

    ③、java集合中除了Vector、Stack、Hashtable其它都不是线程安全的,可以使用Collections的synchronizedXxx()方法来包装指定集合为线程安全的:

Map m = Collections.synchronizedMap(new HashMap());

    使用Collections的synchronizedXxx()方法包装指定集合为线程安全实际上是将add()、remove()、get()、set()等方法使用synchronized关键字来声明为线程安全的方法,在遍历集合的时候还需要使用synchronized对集合进行加锁:

			Map m = Collections.synchronizedMap(new HashMap());
			m.put(3, 4);
			m.put(5, 5);
			
			synchronized(m)
			{
				m.forEach((key, value)->System.out.println(key + "" + value));
			}
   线程安全的集合的另一种方法是使用ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque、CopyOnWriteArrayList、CopyOnWriteArraySet等。

猜你喜欢

转载自blog.csdn.net/milanleon/article/details/80801740
今日推荐