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()删除元素)。
ArrayList和Vector是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等。