集合类存放于 Java.util 包中,主要有 3 种:set(集)、list(列表包含 Queue)和 map(映射)。
集合的体系结构
集合的特点:提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变
集合类的体系图:
单列模式是最后常见的设计模式之一,主要用途是保证在整个程序中,指定的对象只创建一次,用到的始终都是同一个对象;
1. Collection:Collection 是集合 List、Set、Queue 的最基本的接口。
2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
3. Map:是映射表的基础接口
Collection集合
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
Collections类常用方法
方法名 | 说明 |
public static void sort(List<T> list) | 将指定的列表按升序排序 |
public static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
public static void shuffle(List<?> list) | 使用默认的随机源随机排列指定的列表 |
方法名 | 说明 |
boolean add(E e) | 添加元素 |
boolean remove(Object o) | 从集合中移除指定的元素 |
void clear() | 清空集合中的元素 |
boolean contains(Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中元素的个数 |
boolean hasNext() | 如果迭代具有更多元素,则返回 |
Collection集合的遍历
public class IteratorDemo {
public static void main(String[] args) {
//创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
List集合
特点:有索引,可以存储重复元素,元素存取有序
List子类集合:List 一共三个实现类: 分别是 ArrayList、Vector 和 LinkedList。
ArrayList:排列有序,可重复,底层数据结构是数组,查询快,增删慢,线程不安全,效率高
当容量不够时,ArrayList是当前容量*1.5+1
ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
Vector:排列有序,可重复,底层数据结构是数组,查询快,增删慢,线程安全,效率低
当容量不够时,Vector默认扩容一倍容量
Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。
LinkedList:排列有序,可重复,底层数据结构是链表,查询慢,增删快,线程不安全,效率高
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
List集合特有的方法
方法名 | 描述 |
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List Iterator介绍
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位
public class ListIteratorDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//获取列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if(s.equals("world")) {
lit.add("javaee");
}
}
System.out.println(list);
}
}
增强for循环
格式:
for(元素数据类型 变量名 : 数组/集合对象名) {
循环体;
}
Set集合
Set 注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象 hashCode 值(java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 Object 的 hashCode 方法和 equals 方法。
特点:元素存取无序,没有索引,不能存储重复元素
哈希值:
- 什么是哈希值
是JDK根据对象的地址或字符串或数字计算出来的一串int类型的数值 - 如何获取哈希值
Object类中有一个方法:hashCode()用于获取哈希值
Set子类集合的特点:
HashSet:1,底层是哈希表结构,哈希表底层依赖2个方法hashcode,equals。2,元素存取无序 存取速度快,内部是HashMap。3.没有索引 4.不能存储重复元素。5.排列无序,不可重复
哈希表存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode 方法来获取的, HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。
哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。如图 1 表示 hashCode 值不相同的情况;图 2 表示 hashCode 值相同,但 equals 不相同的情况。
HashSet集合保证元素唯一性的原理
1.根据对象的哈希值计算存储位置
如果当前位置没有元素则直接存入
如果当前位置有元素存在,则进入第二步
2.当前的元素和已经存在的元素比较哈希值
如果哈希值不同,则将当前元素进行存储
如果哈希值相同,则进入第三步
3.通过equals()方法比较两个元素的内容
如果内容不相同,则将当前元素进行存储
如果内容相同,则不存储当前元素
为什么存储字符串的时候,字符串内容相同的只存储了一个呢 ?
通过查看add方法的源码,我们知道这个方法底层依赖2个方法:hashCode()和equals();
步骤: 首先比较哈希值,如果相同继续走,比较地址值或者equals();如果不同就直接添加到集合中
按照方法的步骤来说:
首先看hashCode值是否相同,
相同:继续走equals方法
返回true:说明元素重复,就不添加
返回false: 说明元素不重复,就添加到集合
不同:就直接吧元素添加到集合
如果类没有重写这2个方法就默认使用Object()
TreeSet:
特点:排列无序,不可重复 底层使用二叉树 实现排序存储
底层数据结构是红黑树(是一个自平衡的二叉树)
保证元素排序的方式:
自然排序:
让元素所属的类实现Compareable接口
比较器排序:
让集合构造方法接受Comparator的实现类对象
TreeSet(二叉树)
- TreeSet()是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
- Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。
- 在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序
- 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
LinkHashSet(HashSet+LinkedHashMap)
对于 LinkedHashSet 而言,它继承与 HashSet、又基于 LinkedHashMap 来实现的。LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的方法即可。
Map集合
概述:interface Map<K,V> K:键的类型;V:值的类型
特点:
- 键值对映射关系
- 一个键对应一个值
- 键不能重复,值可以重复
- 元素存取无序
- Map集合的数据结构值针对键有效,跟值无关
Map集合和Collection集合的区别
- Map集合存储元素是成对出现的,Map集合的键是唯一的,值是可重复的。可以理解为夫妻对
- Collection 集合存储元素是单独出现的,Collection的儿子Set是唯一的,List是可重复的。可理解为:光棍
Map集合的基本功能
功能有 添加 删除 判断 获取 长度功能
方法名 | 说明 |
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
V get(Object key) | 根据建获取值 |
Set<k> keySet() | 获取集合中所有键的集合 |
Collection<V> values() | 获取集合中所有值的集合 |
int size() | 集合的长度,也就是集合中键值对的个数 |
map集合获取功能
- 方法介绍
方法名 | 说明 |
V get(Object key) V是返回值 | 根据键获取值 V是返回值类型,返回的是泛型V |
Set<K> keySet() Set<k>是返回值类型 | 获取所有键的集合 Set<String> keySet = map.keySet(); |
Collection<V> values() | 获取所有值的集合 Collection<V>是返回值类型 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 Set<Map.Entry<K,V>>是返回值类型 |
- 示例代码
public class MapDemo03 {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<String, String>();
//添加元素
map.put("张无忌", "赵敏");
map.put("郭靖", "黄蓉");
map.put("杨过", "小龙女");
//V get(Object key):根据键获取值
System.out.println(map.get("张无忌"));
System.out.println(map.g+et("张三丰"));
//Set<K> keySet():获取所有键的集合
Set<String> keySet = map.keySet();
for(String key : keySet) {//遍历Set
System.out.println(key);
}
//Collection<V> values():获取所有值的集合
Collection<String> values = map.values();
for(String value : values) {
System.out.println(value);
}
}
}
Map集合的遍历(方式1)
- 键找值
- 获取所有键的集合
- 遍历键的集合,得到每一个键
- 根据键到集合中去找值
- 键值对对象找键和值
- 获取所有的键值对对象的集合
- 遍历键值对对象的集合,获取每一个键值对对象
- 根据键值对对象去获取键和值
- 遍历思路
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
- 把所有的丈夫给集中起来
- 遍历丈夫的集合,获取到每一个丈夫
- 根据丈夫去找对应的妻子
- 步骤分析
- 获取所有键的集合。用keySet()方法实现
- 遍历键的集合,获取到每一个键。用增强for实现
- 根据键去找值。用get(Object key)方法实现
Map集合的遍历(方式2)
遍历思路
- 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
- 获取所有结婚证的集合
- 遍历结婚证的集合,得到每一个结婚证
- 根据结婚证获取丈夫和妻子
步骤分析
- 获取所有键值对对象的集合
- Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
- 用增强for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
- 用getKey()得到键
- 用getValue()得到值
Map集合的案例
- HashMap集合练习之键是String值是Student
案例需求
创建一个HashMap集合,键是学号(String),值是学生对象(Student)。存储三个键值对元素,并遍历
代码实现
//学生类
public class Student {
private String name;
private int age;
}
//测试类
/*
需求:
创建一个HashMap集合,键是学号(String),值是学生对象(Student)。存储三个键值对元素,并遍历
思路:
1:定义学生类
2:创建HashMap集合对象
3:创建学生对象
4:把学生添加到集合
5:遍历集合
方式1:键找值
方式2:键值对对象找键和值
*/
public class HashMapDemo {
public static void main(String[] args) {
//创建HashMap集合对象
HashMap<String, Student> hm = new HashMap<String, Student>();
//创建学生对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
//把学生添加到集合
hm.put("itheima001", s1);
hm.put("itheima002", s2);
hm.put("itheima003", s3);
//方式1:键找值
Set<String> keySet = hm.keySet();
for (String key : keySet) {
Student value = hm.get(key);
System.out.println(key + "," + value.getName() + "," + value.getAge());
}
System.out.println("--------");
//方式2:键值对对象找键和值
Set<Map.Entry<String, Student>> entrySet = hm.entrySet();
for (Map.Entry<String, Student> me : entrySet) {
String key = me.getKey();
Student value = me.getValue();
System.out.println(key + "," + value.getName() + "," + value.getAge());
}
}
}
- HashMap集合练习之键是Student值是String
案例需求
- 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
- 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
代码实现
//学生类
public class Student {
private String name;
private int age;
}
//测试类
public class HashMapDemo {
public static void main(String[] args) {
//创建HashMap集合对象
HashMap<Student, String> hm = new HashMap<Student, String>();
//创建学生对象
Student s1 = new Student("林青霞", 30);
Student s2 = new Student("张曼玉", 35);
Student s3 = new Student("王祖贤", 33);
Student s4 = new Student("王祖贤", 33);
//把学生添加到集合
hm.put(s1, "西安");
hm.put(s2, "武汉");
hm.put(s3, "郑州");
hm.put(s4, "北京");
//遍历集合
Set<Student> keySet = hm.keySet();
for (Student key : keySet) {
String value = hm.get(key);
System.out.println(key.getName() + "," + key.getAge() + "," + value);
}
}
}
- 集合嵌套之ArrayList嵌套HashMap
案例需求
- 创建一个ArrayList集合,存储三个元素,每一个元素都是HashMap
- 每一个HashMap的键和值都是String,并遍历。
代码实现
public class ArrayListIncludeHashMapDemo {
public static void main(String[] args) {
//创建ArrayList集合
ArrayList<HashMap<String, String>> array = new ArrayList<HashMap<String, String>>();
//创建HashMap集合,并添加键值对元素
HashMap<String, String> hm1 = new HashMap<String, String>();
hm1.put("孙策", "大乔");
hm1.put("周瑜", "小乔");
//把HashMap作为元素添加到ArrayList集合
array.add(hm1);
HashMap<String, String> hm2 = new HashMap<String, String>();
hm2.put("郭靖", "黄蓉");
hm2.put("杨过", "小龙女");
//把HashMap作为元素添加到ArrayList集合
array.add(hm2);
HashMap<String, String> hm3 = new HashMap<String, String>();
hm3.put("令狐冲", "任盈盈");
hm3.put("林平之", "岳灵珊");
//把HashMap作为元素添加到ArrayList集合
array.add(hm3);
//遍历ArrayList集合
for (HashMap<String, String> hm : array) {
Set<String> keySet = hm.keySet();
for (String key : keySet) {
String value = hm.get(key);
System.out.println(key + "," + value);
}
}
}
}
- 集合嵌套之HashMap嵌套ArrayList
案例需求
- 创建一个HashMap集合,存储三个键值对元素,每一个键值对元素的键是String,值是ArrayList
- 每一个ArrayList的元素是String,并遍历。
代码实现
public class HashMapIncludeArrayListDemo {
public static void main(String[] args) {
//创建HashMap集合
HashMap<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
//创建ArrayList集合,并添加元素
ArrayList<String> sgyy = new ArrayList<String>();
sgyy.add("诸葛亮");
sgyy.add("赵云");
//把ArrayList作为元素添加到HashMap集合
hm.put("三国演义",sgyy);
ArrayList<String> xyj = new ArrayList<String>();
xyj.add("唐僧");
xyj.add("孙悟空");
//把ArrayList作为元素添加到HashMap集合
hm.put("西游记",xyj);
ArrayList<String> shz = new ArrayList<String>();
shz.add("武松");
shz.add("鲁智深");
//把ArrayList作为元素添加到HashMap集合
hm.put("水浒传",shz);
//遍历HashMap集合
Set<String> keySet = hm.keySet();
for(String key : keySet) {
System.out.println(key);
ArrayList<String> value = hm.get(key);
for(String s : value) {
System.out.println("\t" + s);
}
}
}
}
- 统计字符串中每个字符出现的次数‘
案例需求
- 键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
- 举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
代码实现
public class HashMapDemo {
public static void main(String[] args) {
//键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String line = sc.nextLine();
//创建HashMap集合,键是Character,值是Integer
// HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
TreeMap<Character, Integer> hm = new TreeMap<Character, Integer>();
//遍历字符串,得到每一个字符
for (int i = 0; i < line.length(); i++) {
char key = line.charAt(i);
//拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
Integer value = hm.get(key);
if (value == null) {
//如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
hm.put(key,1);
} else {
//如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
value++;
hm.put(key,value);
}
}
//遍历HashMap集合,得到键和值,按照要求进行拼接
StringBuilder sb = new StringBuilder();
Set<Character> keySet = hm.keySet();
for(Character key : keySet) {
Integer value = hm.get(key);
sb.append(key).append("(").append(value).append(")");
}
String result = sb.toString();
//输出结果
System.out.println(result);
}
}
Map集合子类
HashMap
- 键不可重复,值可重复
- 底层是哈希表,线程不安全
- 允许key值为null,value也可以为null
- HashMap:是基于哈希表的Map接口实现
- 哈希表的作用是用来保证键的唯一性的
- HashMap<String,String> 键:String 值: String
HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。我们用下面这张图来介绍HashMap 的结构。
大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类 Entry 的 实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。
1.capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
2.loadFactor:负载因子,默认为 0.75。
Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。
根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
Hashtable
- 键不可重复值可重复
- 底层是哈希表
- 线程安全
- key和value不允许为null
Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。
LinkedHashMap
- LinkedHashMap:是Map接口的哈希表和链表列表的实现,具有可预知的迭代顺序
有哈希表保证键的唯一性
Hashtable和HashMap的区别?
Hashtable :线程安全,效率低,不允许null键和null值
HashMap:线程不安全,效率低,允许null键和null值
3.4.4. TreeMap(可排序)
TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,
也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。
如果使用排序的映射,建议使用 TreeMap。
在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的
Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
参考:https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html
3.4.5. LinkHashMap(记录插入顺序)
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历
LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。