一、概述
java.util.Collection是java集合框架的根类,表示单列的数据结构,而java.util.Map接口代表了两列类型的数据结构——键值对。另外,java集合框架中只能存放对象,而不能存放基本数据类型,在JDK1.5以后增加了基本数据类型自动拆封箱,因此存入基本类型数据到集合中的时候,会自动装箱,实际上存入集合的还是对象而不是基本数据类型。
二、List
存入List中的元素有索引,因此List中存放的元素有序可重复。Collecton中定义的迭代器默认只能向后读取,但因为List有索引,因此遍历效率高,所以除了基类中的迭代还有自己特定的迭代方式ListIterator。
注意:List中contains判断是是否包含元素,是根据元素自身的equals方法来判断。
- ArrayList和Vector
ArrayList底层使用数组结构,因此对于ArrayList对象的特点有:因为基于数组有索引,而数组在内存中会开辟连续的存储空间(用于存放对象引用),并且基于下标允许随机访问任意元素,所以集合遍历效率高,查找、修改元素效率高,但又正因为底层基于数组,因此增加和删除元素效率极低。
Vector底层也是用了数组结构,而且Vector的功能和ArrayList几乎一模一样,只是ArrayList是线程不安全的而Vector是同步的,因此ArrayList效率较高;除此之外,java api文档中指出,Vector是JDK1.0的数据结构,在JDK1.2中已经被改进的ArrayList替代;再者,对于Vector和ArrayList的迭代来说,两者都是快速失败(快速失败:如果在迭代器创建后的任意时间从结构上修改了向量——通过迭代器自身的 remove 或 add 方法之外的任何其他方式——则迭代器将抛出 ConcurrentModificationException),但是Vector中的elements方法返回的Enumeration不是快速失败的;最后,对于两者的初始化来说,ArrayList默认长度是10,超出以后会按照50%的速度递增,Vector默认长度也是10,不过超出后的递增速度是100%,所以如果能提前确认list的长度,直接用ensureCapacity来增加提高效率,否则每增加一次长度都会new新的数组在拷贝数据,导致效率低下。
因此,一般情况下,推荐使用ArrayList而不是Vector。
package list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for(int i=0; i<5; i++)
list.add("元素"+i);
/*
* for循环遍历list
*/
for(int i=0; i<list.size(); i++)
System.out.print(list.get(i)+" ");
System.out.println();
/*
* foreach循环遍历list
*/
for(String i:list)
System.out.print(i+" ");
System.out.println();
/*
* Iterator遍历list
*/
for(Iterator<String> it=list.iterator(); it.hasNext(); )
System.out.print(it.next() + " ");
System.out.println();
/*
* List特有:ListIterator遍历list
*/
ListIterator<String> it=list.listIterator();
for(; it.hasNext(); )
System.out.print(it.next() + " ");
for(; it.hasPrevious(); )
System.out.print(it.previous() + " ");
}
}
package list;import java.util.Enumeration;import java.util.Iterator;import java.util.ListIterator;import java.util.Vector;public class VectorDemo {public static void main(String[] args) {Vector<String> vector = new Vector<>();for(int i=0; i<5; i++)vector.add("元素"+i);/* * for循环遍历vector */for(int i=0; i<vector.size(); i++)System.out.print(vector.get(i) + " ");System.out.println();/* * foreach循环遍历vector */for(String i:vector)System.out.print(i + " ");System.out.println();/* * Iterator遍历vector */for(Iterator<String> it=vector.iterator(); it.hasNext(); )System.out.print(it.next() + " ");System.out.println();/* * List特有:ListIterator遍历vector */ListIterator<String> it = vector.listIterator();for(; it.hasNext(); )System.out.print(it.next() + " ");for(; it.hasPrevious(); )System.out.print(it.previous() + " ");System.out.println();/* * Vector独有:elements */for(Enumeration<String> e=vector.elements(); e.hasMoreElements(); )System.out.print(e.nextElement() + " ");}}
- LinkedList
LinkedList底层使用链表结果,因此对于LinkedList对象的特点有:因为底层使用链表结构,而链表在内存地址上可以不连续,只有相通过相邻元素才能进行查找遍历,因此查找、修改、遍历的效率较低,但又正因为使用了链表结构,因此在增加、删除元素的时候,只需要修改相邻元素存有的地址即可,故而删除、增加的效率较高。
因为LinkedList使用了链表数据结构,因此插入元素的时候有头插法addFirst和尾插法addLast,默认add和offer是添加到尾部;获取元素有get(int index),element和getFirst获取第一个元素,getLast获取最后一个元素。LinkedList也是下了Queue接口,因此也有队列的方法,offer插入元素,peek获取栈顶元素但不删除,poll获取但删除等,LinkedList太杂了,使用的时候还是看看开发文档吧。
package list;
import java.util.Iterator;
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
for(int i=0; i<5; i++)
list.add("元素"+i);
for(Iterator<String> it=list.iterator(); it.hasNext(); )
System.out.print(it.next() + " ");
System.out.println(list.peek());
System.out.println(list.peekFirst());
System.out.println(list.poll());
System.out.println(list);
}
}
三、Set
set的特点是存入的元素无序不重复,Set的底层使用了Map。
- HashSet
HashSet底层结构是哈希表,存入元素无序不允许重复,当第二个元素及后续的元素存入HashSet中的时候,会首先判断该元素的哈希值是否与集合中的元素有相同,不同则存入,相同则调用元素的equals方法判断,false则存入,true则存入失败。当两个元素的哈希值相同时,在set中存放的位置相同,会根据存入的先后顺序在该位置存放链表,这样虽然能保证元素的存入但是会导致Set集合性能低下。因此当要存入自定义对象到HashSet中的时候,应该要重写该对象的hashCode和equals方法,保证hashCode相同的元素equals方法返回true。
特别注意:HashSet中保证元素唯一性是根据存入元素的hashCode和equals方法。
为了保证HashSet良好的性能,因此要求存入HashSet中的元素都要重写hashCode和equals方法,要求hashCode相等则equals也必须相等,代码示例如下。
package set;
/*
* 存入HashSet中的元素,重写hashCode和equals
* 要求:hashCode相等则equals也必须相等
*/
public class Person {
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
/*
* 重写equals方法
*/
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(this == obj)
return true;
if (!(obj instanceof Person))
return false;
Person per = (Person) obj;
return this.name.equals(per.getName()) && this.age == per.getAge() && this.gender.equals(per.getGender());
}
/*
* 重写hashCode方法
*/
@Override
public int hashCode() {
// TODO Auto-generated method stub
return this.name.hashCode()+age+this.gender.hashCode();
}
}
package set;import java.util.HashSet;public class HashSetDemo {public static void main(String[] args) {HashSet<Person> set = new HashSet<>();set.add(new Person("zhangsan", 10, "male"));set.add(new Person("zhangsan", 10, "male"));set.add(new Person("lisi", 20, "female"));System.out.println(set);/* * 输出结果: * [lisi--20--female, zhangsan--10--male] */}}
- TreeSet
TreeSet底层使用二叉树结构,因此TreeSet能够对放入集合中的元素进行排序,这就要求存入的对象具有比较性或告知TreeSet如何排序,对应着自然排序和定制排序。通常情况下,如果定制排序和自然排序同时存在,则定制排序会覆盖自然排序,即定制排序具有更高的优先级。
TreeSet作为Set类型,也具备了Set类型的特点:存入元素无序不可重复。无序——虽然TreeSet会对存入元素进行排序,但是元素存入的顺序和元素在集合中保存的顺序是不一致的,不可重复——HashSet通过hashCode和equals方法保证元素的唯一性,而TreeSet则通过排序来保证元素的唯一性。
1. 自然排序:要求存入TreeSet中对象元素自身具有比较性,实现java.lang.Comparable接口,并重写compareTo方法,this.compareTo(Object obj)返回值等于0,则表示集合中已存有相同元素,则拒绝存入;否则按照compareTo返回值进行存放并排序,其返回值大于0则表示this大obj,返回值小于0,则表示this小于obj。
2. 定制排序:当自然排序和定制排序同时存在的时候,定制排序会覆盖自然排序,定制排序要求在实例化HashSet对象的时候,传入一个比较器,该比较器实现了java.util.Comparator接口并重写了compare(Object obj1, Object obj2)方法,如果obj1等于obj2,则返回0,如果obj1大于obj2则返回值大于0,如果obj1o小于bj2则返回值小于0;
特别注意:TreeSet中保证元素的唯一性是根据元素自身实现的compareTo方法或根据TreeSet的比较器(比较器的compare方法)。
自然排序实现代码示例如下:
package set;
/*
* 自然排序:存入TreeSet中的元素,必须实现java.lang.Comparable接口
* 要求:hashCode相等则equals也必须相等
*/
public class Person implements Comparable<Person>{
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + "--" + this.age + "--" + this.gender;
}
@Override
public int compareTo(Person per) {
// TODO Auto-generated method stub
return this.name.compareTo(per.getName()) +
this.gender.compareTo(per.getGender()) +
this.age - per.age;
}
}
package set;import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {TreeSet<Person> set = new TreeSet<>();set.add(new Person("zhangsan", 10, "male"));set.add(new Person("zhangsan", 10, "male"));set.add(new Person("lisi", 20, "female"));System.out.println(set);/* * 输出结果: * [lisi--20--female, zhangsan--10--male] */}}
定制排序实现示例代码如下:
package set;
public class Per {
private String name;
private int age;
private String gender;
public Per(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + "--" + this.age + "--" + this.gender;
}
}
package set;import java.util.Comparator;import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {TreeSet<Per> set = new TreeSet<>(new Comparator<Per>() {/* * 重写compare方法 */@Overridepublic int compare(Per per1, Per per2) {// TODO Auto-generated method stubreturn per1.getName().compareTo(per2.getName()) + per1.getGender().compareTo(per2.getGender()) + per1.getAge() - per2.getAge();}});set.add(new Per("zhangsan", 10, "male"));set.add(new Per("zhangsan", 10, "male"));set.add(new Per("lisi", 20, "female"));System.out.println(set);/* * 输出结果: * [lisi--20--female, zhangsan--10--male] */}}当自然排序和定制排序同时存在的时候,定制排序会覆盖自然排序,如下例,自然排序只要求姓名和性别比较,而定制排序要求姓名、性比和年龄都进行比较:
package set;
/*
* 自然排序:存入TreeSet中的元素,必须实现java.lang.Comparable接口
* 要求:hashCode相等则equals也必须相等
*/
public class Person implements Comparable<Person>{
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + "--" + this.age + "--" + this.gender;
}
@Override
public int compareTo(Person per) {
// TODO Auto-generated method stub
/*
* 自然排序只要求姓名和性别相同
*/
return this.name.compareTo(per.getName()) +
this.gender.compareTo(per.getGender());
}
}
package set;import java.util.Comparator;import java.util.TreeSet;public class TreeSetDemo {public static void main(String[] args) {TreeSet<Per> set = new TreeSet<>(new Comparator<Per>() {/* * 重写compare方法 */@Overridepublic int compare(Per per1, Per per2) {// TODO Auto-generated method stubreturn per1.getName().compareTo(per2.getName()) + per1.getGender().compareTo(per2.getGender()) + per1.getAge() - per2.getAge();}});set.add(new Per("zhangsan", 10, "male"));set.add(new Per("zhangsan", 12, "male"));set.add(new Per("lisi", 20, "female"));System.out.println(set);/* * 输出结果: * [lisi--20--female, zhangsan--10--male, zhangsan--12--male] */}}
四、Map
Collection中都是存储单列元素,而Map则存储双列元素。
Map用于存储具有映射关系的数据,因此Map集合一个元素中保存着两个值,一个是Key,一个是Value,Key和Value存在一对一的关系(通过制定的Key有且只有一个Value与其对应)并且两者可以是任何数据类型,包括基本数据类型(Collection是不允许存入基本数据类型的,存入的基本类型都会被自动装箱成相应的对象),Map中的Key基于Set实现,因此Map中的Key无序并且不允许重复。另外,Map接口提供三种collection视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序。
对于Map的存放,put存入元素的键值,返回值不是boolean类型,而是value;对于Map的删除,根据指定的Key删除对应的value,返回值不是boolean,也是所删除的key对应的value;
- HashMap和HashTable
HashMap和HashTable都是基于哈希表结构,两者非常相似,具有以下区别:
1. HashTable在JDK1.0中出现,而HashMap作为HashTable的改进版本在JDK1.2中出现;
2. HashMap允许存入null键null值,而HashTable不允许使用null键null值;
3. HashMap线程不安全,而HashTable是线程安全,因此HashMap性能较高,通常情况下,推荐使用HashMap而不是HashTable;
4. HashTable由于出现在早期的JDK,因此包含了elements和keys两个比较老的方法用于返回值集和键集,现在一般使用values和keySet两个方法代替;
重点:HashMap和HashTable通过value对象自身的equals方法判断是否相等,而保证Key对象的唯一性是通的过自身的HashCode和equals方法判断是否相等,判断规则和HashSet一致。因此如果要自定义存入map中的Key,需要重写这两个方法,如果自定义的对象是可变的,则在存入后修改对象有可能获取不到元素。
package map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
map.put("001", "zhangsan");
map.put("002", "lisi");
map.put("003", "wanger");
map.put("004", "mazi");
/*
* map删除元素返回值不是true/false,而是删除key的value
* 成功删除004,将会返回mazi
*/
System.out.println(map.remove("004"));
/*
* HashMap允许null键null值
*/
map.put(null, null);
System.out.println(map);
/*
* values方法获取map中所有值
*/
Collection<String> valuesCol = map.values();
for(Iterator<String> it=valuesCol.iterator(); it.hasNext(); )
System.out.println(it.next());
/*
* keySet方法获取map中所有Key
*/
Set<String> keySet = map.keySet();
for(Iterator<String> it=keySet.iterator(); it.hasNext(); )
System.out.println(it.next());
/*
* Map中定义的内部类Map.Entry的一个实例表示一个元素(键值对)
* entrySet获取map中所有的元素(键值对)
*/
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Iterator<Map.Entry<String, String>> it=entrySet.iterator(); it.hasNext(); ){
Map.Entry<String, String> entry = it.next();
System.out.println(entry.getKey()+" = "+entry.getValue());
}
}
}
package map;import java.util.Collection;import java.util.Enumeration;import java.util.Hashtable;import java.util.Iterator;import java.util.Map;import java.util.Set;public class HashTableDemo {public static void main(String[] args) {Hashtable<String, String> map = new Hashtable<>();map.put("001", "zhangsan");map.put("002", "lisi");map.put("003", "wanger");map.put("004", "mazi");/* * map删除元素返回值不是true/false,而是删除key的value * 成功删除004,将会返回mazi */System.out.println(map.remove("004"));/* * Hashtable不允许null键null值,这句将报异常:java.lang.NullPointerException * map.put(null, null); * System.out.println(map); *//* * values方法获取map中所有值 */Collection<String> valuesCol = map.values();for(Iterator<String> it=valuesCol.iterator(); it.hasNext(); )System.out.println(it.next());/* * keySet方法获取map中所有Key */Set<String> keySet = map.keySet();for(Iterator<String> it=keySet.iterator(); it.hasNext(); )System.out.println(it.next());/* * Map中定义的内部类Map.Entry的一个实例表示一个元素(键值对) * entrySet获取map中所有的元素(键值对) */Set<Map.Entry<String, String>> entrySet = map.entrySet();for(Iterator<Map.Entry<String, String>> it=entrySet.iterator(); it.hasNext(); ){Map.Entry<String, String> entry = it.next();System.out.println(entry.getKey()+" = "+entry.getValue());}/* * HashTable中特有的方法elements和keys */for(Enumeration<String> en = map.elements(); en.hasMoreElements(); ){System.out.println(en.nextElement());}for(Enumeration<String> en = map.keys(); en.hasMoreElements(); ){System.out.println(en.nextElement());}}}
- TreeMap
TreeMap基于红黑树结构,线程不同步,可以给Map中的键进行排序。
重点:TreeMap中的values对象通过自身的equals判断是是否相同,而保证Key对象的唯一性是通过自然排序(Key实现java.lang.Comparable重写compareTo方法)和定制排序(在建立Map实例的时候传入比较器,该比较器实现了java.util.Compator并重写compare方法)。
1. 自然排序
和TreeSet类似,要做为TreeMap的key,则要求该类实现了java.lang.Comparable接口,并重写compareTo方法
package map;
/*
* 自然排序:存入TreeMap中的元素,必须实现java.lang.Comparable接口并重写compareTo方法
*/
public class Person implements Comparable<Person>{
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name + "--" + this.age + "--" + this.gender;
}
@Override
public int compareTo(Person per) {
// TODO Auto-generated method stub
/*
* 自然排序只要求姓名和性别相同
*/
return this.name.compareTo(per.getName()) +
this.gender.compareTo(per.getGender()) + (this.age - per.getAge());
}
}
package map;import java.util.Iterator;import java.util.Map;import java.util.Set;import java.util.TreeMap;public class TreeMapDemo {public static void main(String[] args) {TreeMap<Person, String> map = new TreeMap<>();map.put(new Person("zhangsan", 10, "male"), "001");map.put(new Person("zhangsan", 10, "male"), "002");map.put(new Person("lisi", 20, "female"), "003");Set<Map.Entry<Person, String>> entrySet = map.entrySet();for(Iterator<Map.Entry<Person, String>> it=entrySet.iterator(); it.hasNext(); ){Map.Entry<Person, String> entry = it.next();System.out.println(entry.getKey()+"--"+entry.getValue());}/*输出结果:lisi--20--female--003zhangsan--10--male--002*/}}
2. 定制排序
和TreeSet一样,要求在实例化Map对象的时候,传入指定的比较器,该比较器实现了java.util.Compator接口,并重写了compare方法,示例代码如下。
package map;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<Person, String> map = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person per1, Person per2) {
// TODO Auto-generated method stub
return per1.getName().compareTo(per2.getName()) +
per1.getGender().compareTo(per2.getGender()) +
per1.getAge() - per2.getAge();
}
});
map.put(new Person("zhangsan", 10, "male"), "001");
map.put(new Person("zhangsan", 10, "male"), "002");
map.put(new Person("lisi", 20, "female"), "003");
Set<Map.Entry<Person, String>> entrySet = map.entrySet();
for(Iterator<Map.Entry<Person, String>> it=entrySet.iterator(); it.hasNext(); ){
Map.Entry<Person, String> entry = it.next();
System.out.println(entry.getKey()+"--"+entry.getValue());
}
/*输出结果:
lisi--20--female--003
zhangsan--10--male--002*/
}
}
五、Collections和Arrays
- Collections
Collections是集合框架的工具类,里面封装了对集合进行操作的静态方法。使用Collections,可以对List进行排序(基于对象的自然排序或自定义比较器传入sort方法),可以将线程不安全的List/Set/Map转变成程安全的List/Set/Map等等,具体详情查看一下开发文档即可。
- Arrays
Arrays是用于操作数组的工具类,里面封装了对数组进行操作的静态方法。使用ArrayList,可以对数组进行排序、查找等相关功能,具体的使用查看开发文档即可。
六、集合和数组互相转换
1. 通过Arrays的静态方法asList可以将数组转换成List来操作,这样可以使用List的方法来操作原来的数组对象,但是要注意转换之后的List对象不能进行增删等导致List结构变化的操作,除此之外,还需要注意如果数组存储的是引用类型,则转换成List的时候数组中的元素会直接专程集合中的元素,但如果数组中存储的是基本类型的数据,则会将数组本身作为一个元素存入List,而不会将基本数据类型的元素自动装箱转成集合中的元素;
2. 通过Collection自身的toArray方法可以将集合转换成数组,这样可以限定集合对元素的操作,因为集合可以进行增删等操作导致集合的结构变化,如果转成数组则将保持其结构,但是要注意,调用头Array泛型方法时需要传入一个数组,如果传入的数组长度小于集合长度,则会重新new一个数组,如果传入的数组长度大于集合长度,则多出来的部分传入默认值null;
实现代码如下所示。
package collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CollectionsDemo {
/*
* Arrays
*/
public void arraysDemo(){
Integer[] intObj = new Integer[]{1, 3, 5, 7};
List<Integer> list = Arrays.asList(intObj);
System.out.println(list);//输出结果:[1, 3, 5, 7]
int[] intBase = new int[]{1, 3, 5, 7};
List<?> list2 = Arrays.asList(intBase);//实际上是int[]类型
System.out.println(list2);//输出结果:[[I@7852e922]
}
/*
* Collections
*/
public void collecionsDemo(){
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
//传入的数组长度小于集合长度
String[] shortArr = list.toArray(new String[0]);
for(String i:shortArr)
System.out.print(i + " ");//输出结果:1 2 3
System.out.println();
//传入的数组长度大于集合长度
String[] longArr = list.toArray(new String[5]);
for(String i:longArr)
System.out.print(i + " ");//输出结果:1 2 3 null null
}
public static void main(String[] args) {
CollectionsDemo demo = new CollectionsDemo();
demo.arraysDemo();
demo.collecionsDemo();
}
}
附注:
本文如有错漏之处,烦请不吝指正,谢谢!