黑马程序员_Java基础:集合总结
一、集合概念
相信大家都知道,java是一门面向对象的编程语言,而对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储进行存储,集合就是存储对象最常用的一种方式,我们可以把集合看成是一个容器。
同样,数组也是一种容器,那么集合和它有什么不同?
1.数组虽然也可以存储对象,但是长度是固定的,而集合长度是可变的。
2.数组中可以存储基本数据类型数据,集合只能存储对象。
3.数组中储存的基本数据类型数据或对象要类型相同,而集合可以存储不同类型的对象。
所以,当事先不知道要存放数据的个数,或者需要一种比数组下标存取机制更灵活的方法时,就需要用到集合类。
二、集合的构成与分类
下面是一张网上找到的图片,对于集合中的类和接口有比较清晰的表现:
1.Collection接口:
Collection是最基本的集合接口,一个Collection代表一组对象,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不提供直接继承自Collection的类,Java JDK提供的类都是继承自Collection的”子接口“如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,另一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。其实后一个构造函数就是复制一个Collection。
那么如何取出Collection中的元素?无论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代器,使用该迭代器即可逐一访问Collection中每一个元素。典型的用法如下:
1 Iterator it = collection.iterator(); // 获得一个迭代器。 2 while(it.hasNext()) { //通过迭代器判断集合中是否还有元素。 3 Object obj = it.next(); // 得到下一个元素,类型为Object。 4 }
Collection定义了集合框架的共性功能(方法):
①添加:add(e); addAll(collection);
②删除:remove(e); removeAll(collection); clear();
③判断:contains(e); isEmpty();
④获取:iterator(); size();
⑤获取交集:retainAll();
⑥集合变数组:toArray();
Collection接口派生的两个接口是List和Set。
(1)List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List允许有相同的元素,List集合判断元素是否相同是通过对象的equals()方法。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。
实现List接口的常用类有ArrayList,LinkedList,Vector。
ArrayList类:
ArrayList实现了可变大小的数组。它允许所有元素,包括null。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组(数组结构)的大小。这个容量可随着不断添加新元素而自动增加。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
注意ArrayListList是非同步的,如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建ArrayList时构造一个同步的List来包装它:
1 List list = Collections.synchronizedList(new ArrayList(...));
数组结构(每个元素都有角标):

LinkedList类:
LinkedList实现了List接口,存储数据的方式是双链表,允许null元素。此外LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意LinkedList是非同步的,如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建LinkedList时构造一个同步的List来包装它:
1 List list = Collections.synchronizedList(new LinkedList(...));
双链表结构(每个元素只知道前后的元素):

Arraylist和Linkedlist的不同处:
①ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
②对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
③对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
Vector类:
Vector非常类似ArrayList,也是数组结构存储数据,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。同样,当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。另外,Vector还有特有的取出元素的方式,通过它的elements()获取Enumeration枚举来得到元素,实际上Enumeration和Iterator功能是一样的。
Vector与ArrayList不同之处是:
①Vector是线程同步的,所以它也是线程安全的,而Arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
②如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
③Vector还有特有的取出元素的方式,Enumeration枚举。
(2)Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
另外要注意,必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object1.equals(Object2)=true将导致一些问题。
实现Set接口的常用类有HashSet和TreeSet。
HashSet类:
Hashset存储数据的结构是哈希表,它不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变。也就是说,HashSet中的元素是无序的。它是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。基于这种唯一性标准,HashSet中判断元素是否存在,以及删除等操作,依赖的方法也是元素的hashCode()和equals()方法。
注意HashSet是非同步的,如果多个线程同时访问一个HashSet,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。一种解决方法是在创建HashSet时构造一个同步的Set来包装它:
1 Set s = Collections.synchronizedSet(new HashSet(...));
哈希表结构(当元素比较hashCode不同时,元素处于不同区域;当元素比较hashCode相同,equals比较不同时,元素会在同个区域):

TreeSet类:
TreeSet存储数据的结果是二叉树(红黑树),基于实现NavigableSet接口,它是使用元素的自然顺序对元素进行排序(当元素自身具有比较性时),或者根据创建 set 时提供的 Comparator进行排序(当元素自身不具备比较性,或者比较性不合需求时),具体取决于使用的构造方法。也就是说,TreeSet中的元素是有序的。它是通过元素的compareTo()或者构造时使用的比较器Comparator中的compare()方法来保证元素的唯一性。
注意TreeSet是非同步的,如果多个线程同时访问一个TreeSet,而其中至少一个线程修改了该 set,那么它必须 外部同步。一种解决方法是在创建TreeSet时构造一个同步的Set来包装它:
1 SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
二叉树结构(比较后,小的元素在左子树,大的元素在右子树):

2.Map接口
Map没有继承Collection接口,Map提供key(键)到value(值)的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。Map集合没有直接取出元素的方法,想获取Map集合中的元素,需要先用entrySet()方法获取键-值映射关系的Set集合,或者使用keySet()方法获取键的Set集合。
Map集合的共性功能(方法):
①添加:put(K key, V value); putAll(Map<? extends K,? extends V> m);
②删除: remove(Object key); clear();
③判断:containsValue(Object value); containsKey(Object key); isEmpty();
④获取:get(Object key); size(); values(); entrySet(); keySet();
实现Map接口的常用类有HashMap,TreeMap。
HashMap类:
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null键和null值(替代Hashtable)。其键集上的对象是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
注意HashMap是非同步的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。一种解决方法是在创建HashMap时构造一个同步的Map来包装它:
1 Map m = Collections.synchronizedMap(new HashMap(...));
TreeMap类:
基于二叉树的 Map 接口的实现。它所包含的映射关系是有序的(以键为标准),其键的排序方式和TreeSet中的元素相同。
注意TreeMap是非同步的,如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须外部同步。一种解决方法是在创建TreeMap时构造一个同步的Map来包装它:
1 SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
三、集合的应用
1.例子一(将自定义对象作为元素存到ArrayList集合中,并去除重复元素):
1 import java.util.ArrayList; 2 import java.util.Iterator; 3 4 /* 5 将自定义对象作为元素存到ArrayList集合中,并去除重复元素。 6 比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。 7 8 思路: 9 1,对人描述,将数据封装进人对象。 10 2,定义容器,将人存入。 11 3,取出。 12 13 List集合判断元素是否相同,依据是元素的equals方法。 14 */ 15 class Person { 16 private String name; 17 private int age; 18 Person(String name,int age) { 19 this.name = name; 20 this.age = age; 21 } 22 23 public boolean equals(Object obj) { 24 if(!(obj instanceof Person)) 25 return false; 26 Person p = (Person)obj; 27 return this.name.equals(p.name) && this.age == p.age; 28 } 29 public String getName() { 30 return name; 31 } 32 public int getAge() { 33 return age; 34 } 35 } 36 37 public class ArrayListTest { 38 public static void main(String[] args) { 39 ArrayList<Person> al = new ArrayList<Person>(); //使用泛型指定集合元素类型。 40 41 al.add(new Person("lisi01",30)); 42 al.add(new Person("lisi02",32)); 43 al.add(new Person("lisi02",32)); 44 al.add(new Person("lisi04",35)); 45 al.add(new Person("lisi03",33)); 46 al.add(new Person("lisi04",35)); 47 48 al = singleElement(al); 49 System.out.println("remove 03 :"+al.remove(new Person("lisi03",33)));//remove方法底层也是依赖于元素的equals方法。 50 51 Iterator<Person> it = al.iterator(); 52 while(it.hasNext()) { //判断集合中是否还有元素。 53 Person p = it.next(); 54 System.out.println(p.getName()+"::"+p.getAge()); 55 } 56 } 57 58 // 去掉重复元素。 59 public static ArrayList<Person> singleElement(ArrayList<Person> al) { 60 //定义一个临时容器。 61 ArrayList<Person> newAl = new ArrayList<Person>(); 62 Iterator<Person> it = al.iterator(); 63 while(it.hasNext()) { 64 Person p = it.next(); 65 66 if(!newAl.contains(p)) 67 newAl.add(p); 68 } 69 return newAl; 70 } 71 }
运行结果为:
remove 03 :true
lisi01::30
lisi02::32
lisi04::35
2.例子二(使用LinkList模拟一个堆栈或者队列数据结构):
1 import java.util.LinkedList; 2 3 /* 4 使用LinkList模拟一个堆栈或者队列数据结构。 5 6 堆栈:先进后出,如同一个杯子。 7 队列:先进先出,如同一个水管。 8 */ 9 class DuiLie { 10 private LinkedList link; 11 private LinkedList link_2; 12 13 DuiLie() { 14 link = new LinkedList(); 15 link_2 = new LinkedList(); 16 } 17 18 public void myAdd(Object obj) { 19 link.addFirst(obj); 20 link_2.addFirst(obj); 21 } 22 23 public Object myGet() { 24 return link.removeFirst(); 25 } 26 27 public Object myGet_2() { 28 return link.removeLast(); 29 } 30 31 public boolean isNull() { 32 if (link.isEmpty()) { 33 try { 34 return link.isEmpty(); 35 } 36 finally { 37 link.addAll(link_2); 38 } 39 } 40 return link.isEmpty(); 41 } 42 } 43 44 45 public class LinkedListTest { 46 public static void main(String[] args) { 47 DuiLie dl = new DuiLie(); 48 49 dl.myAdd("java01"); 50 dl.myAdd("java02"); 51 dl.myAdd("java03"); 52 dl.myAdd("java04"); 53 54 //堆栈形式;先进后出 55 while (!dl.isNull()) 56 System.out.println(dl.myGet()); 57 System.out.println(); 58 59 //队列形式:先进先出 60 while (!dl.isNull()) 61 System.out.println(dl.myGet_2()); 62 } 63 }
运行结果为:
java04
java03
java02
java01
java01
java02
java03
java04
3.例子三(Vector枚举):
1 import java.util.Enumeration; 2 import java.util.Vector; 3 4 /* 5 枚举就是Vector特有的取出方式。 6 发现枚举和迭代器很像。 7 其实枚举和迭代是一样的。 8 9 因为枚举的名称以及方法的名称都过长。 10 所以被迭代器取代了。 11 */ 12 public class VectorDemo { 13 public static void main(String[] args) { 14 Vector<String> v = new Vector<String>(); 15 16 v.add("java01"); 17 v.add("java02"); 18 v.add("java03"); 19 v.add("java04"); 20 21 Enumeration<String> en = v.elements(); 22 while(en.hasMoreElements()) { //判断集合中是否还有元素。 23 System.out.println(en.nextElement()); 24 } 25 } 26 }
运行结果为:
java01
java02
java03
java04
4.例子四(往HashSet集合中存入自定义对象):
1 import java.util.HashSet; 2 import java.util.Iterator; 3 4 /* 5 往HashSet集合中存入自定义对象。 6 以人为例: 7 姓名和年龄相同视为同一个人,重复元素。 8 */ 9 public class HashSetTest { 10 public static void main(String[] args) { 11 HashSet<Person> hs = new HashSet<Person>(); 12 13 hs.add(new Person("a1",11)); 14 hs.add(new Person("a2",12)); 15 hs.add(new Person("a3",13)); 16 hs.add(new Person("a2",12)); 17 // 查看是否包含指定元素。 18 System.out.println("a1:"+hs.contains(new Person("a2",12))); 19 // 删除一个元素。 20 System.out.println(hs.remove(new Person("a3",13))); 21 22 Iterator<Person> it = hs.iterator(); 23 while (it.hasNext()) { 24 Person p = it.next(); 25 System.out.println(p.getName()+"..."+p.getAge()); 26 } 27 } 28 } 29 30 31 class Person { 32 private String name; 33 private int age; 34 35 Person(String name,int age) { 36 this.name = name; 37 this.age = age; 38 } 39 40 public String getName() { 41 return name; 42 } 43 44 public int getAge() { 45 return age; 46 } 47 48 public int hashCode() { 49 return name.hashCode()+age*39; 50 } 51 52 public boolean equals(Object obj) { 53 if (!(obj instanceof Person)) 54 return false; 55 Person p = (Person)obj; 56 return this.name.equals(p.name) && this.age == p.age; 57 } 58 }
运行结果为:
a1:true
true
a2...12
a1...11
5.例子五(使用TreeSet添加字符串,字符串按长度排序):
1 import java.util.Comparator; 2 import java.util.Iterator; 3 import java.util.TreeSet; 4 5 /* 6 按照字符串长度排序。 7 8 字符串本身具备比较性,但是它的比较方式不是所需要的。 9 这时只能使用比较器。 10 */ 11 public class TreeSetTest { 12 public static void main(String[] args) { 13 // 元素本身的比较性不合需求,构造时加上比较器。 14 TreeSet<String> ts = new TreeSet<String>(new StrLenCompare()); 15 16 ts.add("abcd"); 17 ts.add("cc"); 18 ts.add("cba"); 19 ts.add("aaa"); 20 ts.add("z"); 21 ts.add("hahaha"); 22 23 Iterator<String> it = ts.iterator(); 24 while (it.hasNext()) 25 { 26 String s = (String)it.next(); 27 System.out.println(s+"...length..."+s.length()); 28 } 29 } 30 } 31 32 //比较器比较内容。 33 class StrLenCompare implements Comparator<String> { 34 public int compare(String s1,String s2) { 35 int num = new Integer(s1.length()).compareTo(new Integer(s2.length())); 36 if (num==0) 37 return s1.compareTo(s2); 38 return num; 39 } 40 }
运行结果为:
z...length...1
cc...length...2
aaa...length...3
cba...length...3
abcd...length...4
hahaha...length...6
6.例子六(使用HashMap添加不重复的元素(映射关系)):
1 import java.util.HashMap; 2 import java.util.Iterator; 3 import java.util.Map; 4 import java.util.Set; 5 6 /* 7 每一个学生都与对应的归属地。 8 学生Student,地址String。 9 学生属性:姓名,年龄。 10 注意:姓名和年龄相同的视为同一个学生。 11 保证学生的唯一性。 12 13 思路: 14 1,描述学生。 15 2,因为学生和地址存在映射关系。定义Map容器,将学生作为键,地址作为值存入。 16 3,获取Map集合中的元素。 17 */ 18 class Student implements Comparable<Student> { 19 private String name; 20 private int age; 21 Student(String name,int age) { 22 this.name = name; 23 this.age = age; 24 } 25 26 public int compareTo(Student s) { 27 int num = new Integer(this.age).compareTo(new Integer(s.age)); 28 if (num==0) { 29 return this.name.compareTo(s.name); 30 } 31 return num; 32 } 33 34 public int hashCode() { 35 return this.name.hashCode()+this.age*34; 36 } 37 38 public boolean equals(Object obj) { //这里重写的是父类Object的equals方法,参数类型必须Object,否则重写失败。 39 if(!(obj instanceof Student)) 40 throw new ClassCastException("类型不匹配"); 41 Student s = (Student)obj; 42 return this.name.equals(s.name) && this.age==s.age; 43 } 44 45 public String getName() { 46 return name; 47 } 48 49 public int getAge() { 50 return age; 51 } 52 53 public String toString() { 54 return name+":::"+age; 55 } 56 } 57 58 59 public class HashMapTest { 60 public static void main(String[] args) { 61 HashMap<Student,String> hm = new HashMap<Student,String>(); 62 63 hm.put(new Student("lisi1",21),"beijing"); 64 hm.put(new Student("lisi1",21),"tianjin"); 65 hm.put(new Student("lisi2",22),"shanghai"); 66 hm.put(new Student("lisi3",23),"nanjing"); 67 hm.put(new Student("lisi4",24),"wuhan"); 68 69 //第一种取出方式:keySet 70 Set<Student> keySet = hm.keySet(); 71 Iterator<Student> it = keySet.iterator(); 72 73 while (it.hasNext()) { 74 Student s = it.next(); 75 String addr = hm.get(s); 76 System.out.println(s+"..."+addr); 77 } 78 System.out.println(); 79 80 /* //第二种取出方式:entrySet 81 Set<Map.Entry<Student,String>> entrySet = hm.entrySet(); 82 Iterator<Map.Entry<Student,String>> iter = entrySet.iterator(); 83 84 while (iter.hasNext()) { 85 Map.Entry<Student,String> me = iter.next(); 86 Student s = me.getKey(); 87 String addr = me.getValue(); 88 System.out.println(s+"....."+addr); 89 }*/ 90 } 91 }
运行结果为:
lisi3:::23...nanjing
lisi1:::21...tianjin
lisi2:::22...shanghai
lisi4:::24...wuhan
7.例子七(使用TreeMap添加有序的元素(映射关系)):
1 import java.util.Comparator; 2 import java.util.Iterator; 3 import java.util.Map; 4 import java.util.Set; 5 import java.util.TreeMap; 6 7 /* 8 需求:每一个学生都与对应的归属地。 9 学生Student,地址String。 10 学生属性:姓名,年龄。 11 对学生对象的年龄进行升序排序。 12 13 因为数据是以键值对形式存在的。 14 所以要是哟昂可以排序的Map集合,TreeMap。 15 */ 16 class StuNameComparator implements Comparator<Student> { 17 public int compare(Student s1,Student s2) { 18 int num = s1.getName().compareTo(s2.getName()); 19 if (num==0) 20 return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge())); 21 return num; 22 } 23 } 24 25 public class TreeMapTest { 26 public static void main(String[] args) { 27 //因为Student本身定义有比较性,可以直接满足需求。如果是按姓名排序,则需要加上StuNameComparator比较器。 28 TreeMap<Student,String> tm = new TreeMap<Student,String>(/*new StuNameComparator()*/); 29 30 tm.put(new Student("blisi3",23),"nanjing"); 31 tm.put(new Student("lisi1",21),"beijing"); 32 tm.put(new Student("alisi14",24),"wuhan"); 33 tm.put(new Student("lisi1",21),"tianjin"); 34 tm.put(new Student("alisi11",21),"tianjin"); 35 tm.put(new Student("alisi12",22),"shanghai"); 36 37 Set<Map.Entry<Student,String>> entrySet = tm.entrySet(); 38 Iterator<Map.Entry<Student,String>> it = entrySet.iterator(); 39 while (it.hasNext()) { 40 Map.Entry<Student,String> me = it.next(); 41 Student s = me.getKey(); 42 String addr = me.getValue(); 43 System.out.println(s+"..."+addr); 44 } 45 } 46 } 47 48 49 class Student implements Comparable<Student> { 50 private String name; 51 private int age; 52 Student(String name,int age) { 53 this.name = name; 54 this.age = age; 55 } 56 57 public int compareTo(Student s) { 58 int num = new Integer(this.age).compareTo(new Integer(s.age)); 59 if (num == 0) { 60 return this.name.compareTo(s.name); 61 } 62 return num; 63 } 64 65 public int hashCode() { 66 return this.name.hashCode()+this.age*34; 67 } 68 69 public boolean equals(Object obj) { 70 if(!(obj instanceof Student)) 71 throw new ClassCastException("类型错误"); 72 Student s = (Student)obj; 73 return s.name.equals(this.name) && this.age == s.age; 74 } 75 76 public String getName() { 77 return name; 78 } 79 80 public int getAge() { 81 return age; 82 } 83 84 public String toString() { 85 return name+":::"+age; 86 } 87 }
运行结果为:
alisi11:::21...tianjin
lisi1:::21...tianjin
alisi12:::22...shanghai
blisi3:::23...nanjing
alisi14:::24...wuhan
四、总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类或者在类意外加上同步。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
浙公网安备 33010602011771号