集合 - Collection & Map
1、集合
1.1、集合的相关概念:
- 集合不能直接存储基本数据类型。(可以是基本数据类型的包装类)
- 集合不能直接存储java对象。(只能是对象的内存地址)
- 集合中存储的是java对象的内存地址。(或者说是引用)
1.2、集合的存储方式:
- 单个方式存储元素,这一类集合中超级父接口:
java.util.Collection - 键值对方法存储元素,这一类集合中超级父接口:
java.util.Map
1.3、集合的存储类图:

[========]

上面两个类图的总结:
集合实例讲解
2.1、Collection接口常用方法
//创建对象
Collection c = new ArrayList(); //Collection是接口,不能直接new;因此这里是多态。
//向集合中添加元素
c.add(1); // 自动装箱,存进去的是对象的内存地址。Integer x = new Integer(1);
//获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size()); // 1
//清空集合
c.clear();
//判断集合中是否包含元素1
System.out.println(c.contains(1)); // true
//删除某个元素
c.remove(1);
//判断集合是否为空
System.out.println(c.isEmpty()); // false
//将集合转换成Object数组
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
// 遍历数组
Object o = objs[i];
System.out.println(o); //集合中的1是Integer类型,Integer类型已经重写了toString方法。
}
重点:迭代器Iterator
package com.bjpowernode.javase.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTest02 {
public static void main(String[] args) {
// 在Map集合中不能用。在所有的Collection以及子类中使用。
// 创建集合对象
Collection c = new ArrayList();
// 添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
// 第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator(); //c继承了Iterable,Iterable有iterator方法,因此可以调用,返回的是Iterator。
// 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
/*
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext()如果仍有元素可以迭代,则返回 true。
Object next() 返回迭代的下一个元素。
*/
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
迭代器图解:
深入contains
package com.bjpowernode.javase.collection;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionTest04 {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 向集合中存储元素
String s1 = new String("abc"); // s1 = 0x1111
c.add(s1); // 放进去了一个"abc"
String s2 = new String("def"); // s2 = 0x2222
c.add(s2);
// 集合中元素的个数
System.out.println("元素的个数是:" + c.size()); // 2
// 新建的对象String
String x = new String("abc"); // x = 0x5555
// c集合中是否包含x?结果猜测一下是true还是false?
System.out.println(c.contains(x)); //true
}
}
解析:
- contains比较的是内容(底层也是使用equals比较的),不是内存地址。元素是String类型的,已经重写
toString()方法了,因此比较的是内容,即c包含abc。 - 如果不是String类型的话,是自己创建的类的话,需要手动重写;如果不重写,调用的是Object的equals,Object的equals比较的是内存地址。
关于上面代码的图解:
remove的使用
- 当集合的结构发生改变时,迭代器必须重新获取;否则会出现异常。
- 不要使用集合中的
remove方法删除元素,要使用迭代器Iterator的remove方法删除元素。
图解集合和迭代器中remove方法:
迭代器迭代的时候参照一个快照进行迭代(也可以理解为迭代快照),每迭代一次快照中的元素,都会和集合进行对比,看迭代的该元素在集合中是否存在,若存在,则又继续迭代下一个;若不存在,则直接报错。这就可以解释两个remove方法的使用了。如果使用集合中的remove方法删除集合中的元素,此时,迭代器并没有更新(快照中的该元素没有删除),也就是说,这时快照中的元素和集合中的元素对应不上,所以报错。如果使用迭代器的remove方法删除元素,快照中的元素和集合中的元素同时删除,这时,迭代元素就对应上了。
实例:
package com.bjpowernode.javase.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTest06 {
public static void main(String[] args) {
// 创建集合
Collection c2 = new ArrayList();
//添加元素
c2.add("abc");
c2.add("def");
c2.add("xyz");
//在添加元素进集合之后才获取迭代器
Iterator it2 = c2.iterator();
//迭代集合
while(it2.hasNext()){
Object o = it2.next(); //返回值类型必须是Object
// 这是使用集合中的remove方法删除元素,在迭代过程中删除集合中的元素后,并没有更新迭代器。导致迭代器的快照和原集合状态不同。
//c2.remove(o);
// 使用迭代器的remove方法删除元素,会自动更新迭代器,并且更新集合(删除集合中的元素)。
it2.remove(); // 删除的一定是迭代器指向的当前元素。
System.out.println(o);
}
System.out.println(c2.size()); //0
}
}
List接口
- 常用方法:
List myList = new ArrayList();
myList.add(1,"king"); //在指定下标添加元素。
System.out.println(myList.get(1)); //根据下标取元素
System.out.println(myList.indexOf("C")); // 获取指定对象第一次出现处的索引。
System.out.println(myList.lastIndexOf("C")); // 获取指定对象最后一次出现处的索引。
System.out.println(myList.remove(0)); // 删除指定下标位置的元素
System.out.println(myList.set(2, "Soft")); // 修改指定下标元素的元素
- List是有序可重复的,因此有特别的遍历方式:
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
ArrayList集合
-
非线程安全。
-
默认初始化容量10。(底层先创建一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)。
-
集合底层是一个Object[]数组。
-
ArrayList集合的扩容:是增长到原容量的1.5倍。 -
ArrayList集合的优化:底层是数组,在初始化时预估计一下元素的个数,给定一个初始化容量,较少扩容。- 数组优点:检索效率高。(每个元素占用空间相同、内存地址连续、知道首元素内存地址)
- 数组缺点:随机增删元素效率比较低;无法存储大容量数据。
- 注:在末尾添加元素效率很高,不受影响。
-
转换成线程安全的:
List myList = new ArrayList(); Collections.synchronizedList(myList); //java.util.Collections里面的方法
ArrayList的构造方法
-
无参构造。
List myList1 = new ArrayList(); -
有参构造,参数是初始化容量的。
List myList2 = new ArrayList(100); -
有参构造,参数是集合,是将该集合转换成
ArrayList集合。List myList3 = new ArrayList(c);,其中c是集合。
单向链表的数据结构
链表的优点:
- 链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
链表的缺点:
- 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。
ArrayList和LinkedList
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)LinkedList:把随机增删发挥到极致。- 加元素都是往末尾添加,所以
ArrayList用的比LinkedList多。 - 两者使用起来都差不多,使用那个看情况而定。
双向链表的数据结构
Vector集合
- 使用方法和
ArrayList差不多。 - 底层也是数组。
- 默认容量是10;扩容是原来的2倍。10→20→40→80
- 线程安全,下效率低,使用少。
Set集合
- 无序不可重复。
HashSet集合
- 无序不可重复。
- 放到
HashSet集合中的元素实际上放到HashMap集合中的key部分。
Map接口常用方法
- Map集合以key和value的方式存储数据:键值对。
- key和value都是引用数据类型。
- key和value都是存储对象的内存地址。
- key起到主导的地位,value是key的一个附属品。
package com.bjpowernode.javase;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class maptest {
public static void main(String[] args) {
//创建Map集合对象
Map<Integer,String> map=new HashMap<>();
//往集合中添加键值对
map.put(1,"你好");
//通过key获取value
System.out.println(map.get(1));
//获取键值对的数量
System.out.println(map.size());
//通过key删除key和value
map.remove(1);
//往集合中添加键值对
map.put(2,"张三");
map.put(3,"李四");
//判断集合中是否包含某个key
//contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(map.containsKey(2));
//判断集合中是否包含某个value
System.out.println(map.containsValue("张三"));
//获取集合中所有value
Collection<String> collection=map.values();
for (String s:collection){
System.out.println(s);
}
//清空集合
map.clear();
//判断集合是否为空
System.out.println(map.isEmpty());
}
}
Map集合的遍历
package com.bjpowernode.javase;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class maptest01 {
public static void main(String[] args) {
//创建集合
Map<Integer,String> map=new HashMap<>();
//往集合中添加元素
map.put(1,"张三");
map.put(2,"李四");
map.put(3,"王五");
//获取集合中的所有key,把map集合中的key转成set集合
Set<Integer> set=map.keySet();
//获取迭代器
Iterator<Integer> it=set.iterator();
//使用while遍历
while (it.hasNext()){
//取出其中一个key
Integer integer=it.next();
//通过key获取value
String string=map.get(integer);
//输出
System.out.println(integer+"="+string);
}
//使用for增强遍历
for (Integer i:set){ //Integer是迭代的类型,i是标识符,set是需要迭代的集合元素
System.out.println(i+"="+map.get(i));
}
System.out.println("======================================");
//第二种方式
//把Map集合直接全部转换成Set集合
//Set集合中的元素类型是:Map.Entry
//这种方式效率比较高,获取key和value都是直接从node对象中获取的属性值。
//适合大数据量。
Set<Map.Entry<Integer,String>> set1=map.entrySet();
//遍历Set集合,每一次取出一个node
//获取迭代器
Iterator<Map.Entry<Integer,String>> it2=set1.iterator();
//使用while遍历
while(it2.hasNext()){
Map.Entry<Integer,String> node=it2.next();
Integer key=node.getKey();
String value=node.getValue();
System.out.println(key+"="+value);
}
//使用for增强遍历
for (Map.Entry<Integer,String> node:set1){
Integer key1=node.getKey();
String value1=node.getValue();
System.out.println(key1+"--->"+value1);
}
}
}
第二种遍历方法图解:
HashMap集合
- 非线程安全。
HashMap的key可以为null。- 在jdk8之后,如果哈希表单向链表中元素超过8个,单向链表会变成红黑树;当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表。
HashMap集合的默认初始化容量为16,默认加载因子是0.75;即HashMap集合底层数组的容量达到75%的时候,数组开始扩容。注:初始化容量必须是2的倍数。
哈希算法的数据结构图解:

上面图解重点解析:
- 哈希表是一个数组和单向链表的结合体,充分发挥各自的优点:【数组:查询效率高,随机增删效率低】、【单向链表:查询效率低,随机增删效率高。】
HashMap集合底层是哈希表/散列表的数据结构。哈希表/散列表:实际上就是一维数组,数组中的每一个元素是一个单链表。- 哈希值是
key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。 - 哈希表需要分布均匀才能发挥性能。
HashMap集合key部分的元素其实就是放到HashSet集合中了;所以HashSet集合中的元素也需要同时重写hashCode()和equals()方法。HashMap的特点:无序不可重复;- 无序:不一定挂在哪个单链表上。
- 不可重复:使用
equals方法来保证HashMap集合的key不可重复,如果key重复,value会覆盖。
图解中的两个重要方法:
-
map.put(k,v) -
v = map.get(k)
hashCode和equals讲解
- 向
Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法! - equals方法有可能调用,也有可能不调用。
- 拿
put(k,v)和get(k)举例。k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
- 拿
- 如果一个类的
equals方法重写了,那么hashCode()方法必须重写。如果不重写hashCode的话,可以放进两个相同的key,两个相同的key各占数组中的一个元素,也就是两个key存放的数组下标(哈希值)不同。如果重写hashCode的话,equals方法返回如果是true,hashCode()方法返回的值必须一样。equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。 - 终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
- 对于哈希表数据结构来说:
- 如果o1和o2的hash值相同,一定是放到同一个单向链表上。
- 当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
Hashtable集合
Hashtable的key和value都不能为null;否则会报空指针异常。Hashtable是线程安全的,效率较低,使用较少。Hashtable的初始化容量是11;默认加载因子是:0.75f;扩容是:原容量*2+1。
属性类:Properties
- 继承Hashtable,Properties的key和value都是String类型。
- Properties被称为属性类对象。
- Properties是线程安全的。
实例:
package com.bjpowernode.javase;
import java.util.Properties;
public class properties {
public static void main(String[] args) {
Properties pro=new Properties();
//存
pro.setProperty("url","jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
//取
String url=pro.getProperty("url");
System.out.println(url);
}
}
SorteMap集合
TreeSet集合
-
无序不可重复,但可排序;也称可排序集合。
-
TreeSet集合底层实际上是一个TreeMap;TreeMap集合底层是一个二叉树。放到TreeSet集合中的元素,等同于放到TreeMap集合key部分。 -
对自定义的类型来说,TreeSet无法排序。例如:自定义类(Person),因为没有指定Person对象之间的比较规则。谁大谁小并没有说明,所以会出现异常以下异常
java.lang.ClassCastException: class com.bjpowernode.javase.collection.Person cannot be cast to class java.lang.Comparable 出现的原因: Person类没有实现java.lang.Comparable接口。
实例:
package com.bjpowernode.javase.collection;
import java.util.TreeSet;
public class TreeSetTest02 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// 添加String
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
// 遍历
for(String s : ts){
// 按照字典顺序,升序!
System.out.println(s);
}
}
}
/*
数据库中有很多数据:
userid name birth
-------------------------------------
1 zs 1980-11-11
2 ls 1980-10-11
3 ww 1981-11-11
4 zl 1979-11-11
编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的。
*/

浙公网安备 33010602011771号