集合的由来:
Java是面向对象语言,如果我们要针对多个对象进行操作,就必须对多个对象进行存储。 而对多个元素进行存储,前面我们学习过数组,数组的弊端,长度固定。这样,数组将不能满足变化的要求。所以,Java就提供了集合供我们使用。
集合的特点:
长度可以发生改变。
只能存储对象。
可以存储多种类型对象(一般存储的还是同一种,因为1.5JDK的新特性 泛型)。
常用集合的分类:
Collection 接口的接口 对象的集合(单列集合)
- List 接口:有序,可重复
- LinkedList 接口实现类, 链表结构(双向循环链表),(添加删除快,循环遍历慢)插入删除,没有同步, 线程不安全
- ArrayList 接口实现类, 数组+线性长度,(添加删除慢,循环遍历快)随机访问, 没有同步, 线程不安全
- Vector 接口实现类 数组, 同步, 线程安全 (早期出现,和ArrayList使用方法相同,了解,不常用)
- Stack:栈 是Vector类的实现类,先进后出 (在一端添加删除,特殊的线性结构)
- push:进栈(添加数据)
- pop:出栈(删除数据)
- peek:指向(栈顶,位置)
- Queue:队列 先进先出
- 在一端添加,另一端删除(队尾添加,队头删除)
- Stack:栈 是Vector类的实现类,先进后出 (在一端添加删除,特殊的线性结构)
- Set 接口: 无序。不可重复
- HashSet 不可重复,基于HashMap实现,在添加的时候,是根据元素的哈希码值相关(hashCode())做比较
- LinkedHashSet 链表维护元素的插入次序(输出的顺序就是添加的顺序)
- TreeSet 基于TreeMap实现,底层实现为红黑树结构(特殊的二叉树结构,自动平衡),元素排好序
- HashSet 不可重复,基于HashMap实现,在添加的时候,是根据元素的哈希码值相关(hashCode())做比较
Map 接口 键值对的集合 (双列集合)
-
Hashtable 接口实现类, 同步, 线程安全,效率低,不可以null键 null值
- Properties:key和value是String类型,获取时读取 *.properties
-
HashMap 接口实现类 ,没有同步, 线程不安全。效率高,可以null键 null值,添加时和已存在的key作比较(根据哈希码值相关的)
-
LinkedHashMap 双向链表和哈希表实现
-
TreeMap 红黑树对所有的key进行排序(key元素类型一定要实现Comprable接口)
-
Collections:集合的工具类,辅助类
-
集合详解:
Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口是 map 系列集合的顶层接口)
- Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
- boolean hasNext():判断容器内是否还有可供访问的元素
- void remove():删除迭代器刚越过的元素
所以除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历。
注意:我们可以在源码中追溯到集合的顶层接口,比如 Collection 接口,可以看到它继承的是类 Iterable
说明一下 Iterator 和 Iterable 的区别:
Iterable :存在于 java.lang 包中。
我们看到里面封装了 Iterator 接口。所以只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
Iterator :存在于 java.util 包中。核心的方法next(),hasnext(),remove()。
这里我们引用一个Iterator 的实现类 ArrayList 来看一下迭代器的使用:暂时先不管 List 集合是什么,只需要看看迭代器的用法就行了
1 //创建一个 List 集合,典型实现为 ArrayList。
2 List list = new ArrayList();
3 //添加三个元素
4 list.add("Jason");
5 list.add("Peter");
6 list.add("Code");
7 //构造 List 的迭代器
8 Iterator it = list.iterator();
9 //通过迭代器遍历元素
10 while(it.hasNext()){
11 Object obj = it.next();
12 System.out.println(obj);
13 }
Collection:List 接口和 Set 接口的父接口
看一下 Collection 集合的使用例子:
1 //我们这里将 ArrayList集合作为 Collection 的实现类
2 Collection collection = new ArrayList();
3
4 //添加元素
5 collection.add("Tom");
6 collection.add("Bob");
7
8 //删除指定元素
9 collection.remove("Tom");
10
11 //删除所有元素
12 Collection c = new ArrayList();
13 c.add("Bob");
14 collection.removeAll(c);
15
16 //检测是否存在某个元素
17 collection.contains("Tom");
18
19 //判断是否为空
20 collection.isEmpty();
21
22 //利用增强for循环遍历集合
23 for(Object obj : collection){
24 System.out.println(obj);
25 }
26 //利用迭代器 Iterator
27 Iterator iterator = collection.iterator();
28 while(iterator.hasNext()){
29 Object obj = iterator.next();
30 System.out.println(obj);
31 }
List :有序,可以重复的集合。
由于 List 接口是继承于 Collection 接口,所以基本的方法如上所示。
List 接口的三个典型实现:
- List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
- List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
- List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
我在网上看到可以帮助理解记忆的一段:
数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
List 接口遍历可以使用普通 for 循环进行遍历,还可以在指定位置添加元素,替换元素等等。
1 //产生一个 List 集合,典型实现为 ArrayList
2 List list = new ArrayList();
3 //添加三个元素
4 list.add("Tom");
5 list.add("Bob");
6 list.add("Marry");
7 //构造 List 的迭代器
8 Iterator it = list.iterator();
9 //通过迭代器遍历元素
10 while(it.hasNext()){
11 Object obj = it.next();
12 //System.out.println(obj);
13 }
14
15 //在指定地方添加元素
16 list.add(2, 0);
17
18 //在指定地方替换元素
19 list.set(2, 1);
20
21 //获得指定对象的索引
22 int i=list.indexOf(1);
23 System.out.println("索引为:"+i);
24
25 //遍历:普通for循环
26 for(int j=0;j<list.size();j++){
27 System.out.println(list.get(j));
28 }
Set:典型实现 HashSet()是一个无序,不可重复的集合
Set接口的几个实现:
- Set hashSet = new HashSet();
- HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
- 其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
- 对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
- 当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
- 如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
- 如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
- hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
- hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
- 当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
-
Set linkedHashSet = new LinkedHashSet();
不可以重复,有序
因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
-
Set treeSet = new TreeSet();
- TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
- 如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素
- 必须放入同样类的对象.(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制
- 自动排序:添加自定义对象的时候,必须要实现 Comparable 接口,并要覆盖 compareTo(Object obj) 方法来自定义比较规则
- 如果 this > obj,返回正数 1
- 如果 this < obj,返回负数 -1
- 如果 this = obj,返回 0 ,则认为这两个对象相等
Set set = new TreeSet();
set.add("a");
set.add(1);
System.out.println(set);
两个对象通过 Comparable 接口 compareTo(Object obj) 方法的返回值来比较大小, 并进行升降序排列
数据类型 | 排序规则 |
---|---|
BigDecimal,BigInteger,Byte,Double,Float,Integer,Long,Short | 按数字大小排序 |
Character | 按字符的Unicode值得数字大小排序 |
String | 按字符串中字符的Unicode值排序 |
定制排序: 创建 TreeSet 对象时, 传入 Comparator 接口的实现类.
要求: Comparator 接口的 compare 方法的返回值和 两个元素的 equals() 方法具有一致的返回值
public class TreeSetTest {
public static void main(String[] args) {
Person p1 = new Person(1);
Person p2 = new Person(2);
Person p3 = new Person(3);
Set<Person> set = new TreeSet<>(new Person());
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set); //结果为[1, 2, 3]
}
}
class Person implements Comparator<Person>{
public int age;
public Person(){}
public Person(int age){
this.age = age;
}
@Override
/***
* 根据年龄大小进行排序
*/
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
if(o1.age > o2.age){
return 1;
}else if(o1.age < o2.age){
return -1;
}else{
return 0;
}
}
@Override
public String toString() {
// TODO Auto-generated method stub
return ""+this.age;
}
}
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果
以上三个 Set 接口的实现类比较:
共同点:
- 都不允许元素重复
- 都不是线程安全的类,解决办法:Set set = Collections.synchronizedSet(set 对象)
不同点:
- **HashSet:**不保证元素的添加顺序,底层采用 哈希表算法,查询效率高。判断两个元素是否相等,equals() 方法返回 true,hashCode() 值相等。即要求存入 HashSet 中的元素要覆盖 equals() 方法和 hashCode()方
- **LinkedHashSet:**HashSet 的子类,底层采用了 哈希表算法以及 链表算法,既保证了元素的添加顺序,也保证了查询效率。但是整体性能要低于 HashSet
- **TreeSet:**不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用 红-黑 树算法(树结构比较适合范围查询)
Map:key-value 的键值对,key 不允许重复,value 可以
- Map 并不像一个集合,而是两个集合之间 的映射关系。
- 这两个集合的每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
- 因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历。
HashMap有几个关键点:(了解,真的只是了解一下)
- 初始容量:16
- 加载因子:75%
- resize():扩容的方法
- hash(key):作比较的方法
- modCount:统计修改的计数
- 节点数量超过8(阈值)链表结构转换成红黑树结构
- 节点数量小于6(阈值)红黑树转换成链表结构
TreeMap和HashMap的方法使用上大同小异,就不写过多的例子了,要不然就该看烦了
要注意一点:TreeMap会对所有的key进行排序,但是key元素类型一定要实现Comprable接口
Map<String,Object> hashMap = new HashMap<>();
//添加元素到 Map 中
hashMap.put("key1", "value1");
hashMap.put("key2", "value2");
hashMap.put("key3", "value3");
hashMap.put("key4", "value4");
hashMap.put("key5", "value5");
//删除 Map 中的元素,通过 key 的值
hashMap.remove("key1");
//通过 get(key) 得到 Map 中的value
Object str1 = hashMap.get("key1");
//可以通过 添加 方法来修改 Map 中的元素
hashMap.put("key2", "修改 key2 的 Value");
//通过 map.values() 方法得到 Map 中的 value 集合
Collection<Object> value = hashMap.values();
for(Object obj : value){
//System.out.println(obj);
}
//通过 map.keySet() 得到 Map 的key 的集合,然后 通过 get(key) 得到 Value
Set<String> set = hashMap.keySet();
for(String str : set){
Object obj = hashMap.get(str);
//System.out.println(str+"="+obj);
}
//通过 Map.entrySet() 得到 Map 的 Entry集合,然后遍历
Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
for(Map.Entry<String, Object> entry: entrys){
String key = entry.getKey();
Object value2 = entry.getValue();
System.out.println(key+"="+value2);
}
System.out.println(hashMap);