集合框架的简单回顾整理
集合框架
集合框架图
这是java集合框架关系图;来自网友的ThinkJava图贴;
基于此顺带了解一下基本的UML图(统一建模语言 Unified Model Language)用法;
短虚线圈起来的是集合接口;
长虚线圈起来的是抽象类;
实线框起来的是实现类;
虚线指向表明继承或是实现;
实现表明是继承;
集合框架简化图
初识集合框架
集合是用于存放对象的容器;类似数组是可以存放基本类型或者对象的容器;那么为什么还要用集合呢?因为数组是必须定义长度;集合可以不定容量的不断添加存放对象,而且jdk自带的集合类有完备成熟的操作对象集合类工具,能更好的提升开发效率;所以下面对jdk自带的集合类中常用的几个接口做一个简单的汇总。
集合的主干
基于java的封装对象思想,一如Object类为万物基类;集合基本上有Collection和Map接口为集合最上游抽象的接口;可以看出Collection是基于对象容器的最宽泛的抽象接口,而Map作为键值对容器的抽象接口。基于集合框架图,不难看出Map也是依赖Collection的。
Collection接口及其继承实现;
Collection主要分为三大分支,List,Set,Queue;
这里我们只针对常用的List,Set来讲解。
基于开发思维来讲,作为对象的动态容量容器的集合概念,应当能分为有序的可以重复的集合概念分类和无序的不能重复的集合概念分类。那么与之对应的List和Set的接口也就营运而生了;
List的接口及其实现;
实现List的集合类有:ArrayList,LinkedList,Vector,Stack。本次主要分析下常用的ArrayList,LinkedList,Vector三种类;
有序排列从数据结构讲,主要有俩种方式一种是数组,一种是链表;链表又细分为单向链表和双向链表。
对于JVM中这俩中数据结构的理解,可以将JVM作为一个组织首脑来看,堆这块地方可以看做组织所管辖的国土区域,数组就是在这块国土上划分一片土地,这块土地划分为连续序号标识的N家区域;链表(双向链表作为示例)则是,在这块国土里不同的地方分散买地建房,相当于全国不同省份有房,JVM也没记住房子在哪里,只记住了第一个房子的位置,不过每个房子都记录了上一个房子和下一个房子的地址。
数组和双向链表相比具有查询快,增删慢的特点;以上面的比喻示例来看,数组是连续的存储空间,根据索引查找非常快 ;但是增删后,需要对前后影响到的元素进行重新索引划分,效率低下;同理,双向链表,查询总是要通过遍历进行的,所以查询慢,但是增删只需要改变相邻节点的指针指向就好,所以增删效率好。
ArrayList
ArrayList是一个动态的数组。数组有自己的初始变量,和负载因子;初始变量指的是初始化后,是有默认的容量的,此时本质上是默认容量的对象数组,当往其中不断添加元素,当元素个数>当前数组容量负载因子时;会进行扩容(建一个新的变量的数组,将当前数组的对象放置其中)。如果继续添加元素,下一次发生元素个数>当前数组容量负载因子时,继续扩容;
那么基于数组结构的类,不难看出本集合,有查询快,增删满;由于ArrayList是非同步的,那么线程就不安全;
Vector
Vector基本与ArrayList相同,不同在于Vector是同步的,线程安全的;
LinkedList
LinkedList是基于双向链表的结构来实现的,那么相应的就会有查询慢,增删快的特点;LinkedList也是非同步的,线程不安全的。
Set和Map中关于数据结构(哈希表、红黑树)的部分
集合中哈希算法和哈希表、链表的理解
如下图所示,HashMap的数据结构(哈希表+链表)实现图,这里之所以讲HashMap是因为,HashSet是通过依赖HashMap实现的;
如图 键值对张三这个学生的信息,通过对key值也就是学号经过哈希函数计算得到在数组上存储的下标地址1,如果此时地址1处没有数据存放,那么张三会存放到此处;再向其中添加另一个李四的信息对象,同理,计算key值哈希计算得到数组存储地址,如果也得到1的结果(这种情况称为哈希碰撞),但是我们已经知道数组的索引下标1这里已经保存了张三,那么我们会采用链表的方式,将张三和李四串联起来。如果没有发生哈希碰撞,也就是计算得到下标的数组处未存放对象,可以直接存放对象,下次发生哈希碰撞,同理做相应处理。
这里说下哈希函数在具体的实现中,是俩个对象经过计算后发生哈希碰撞几率的主因;实际源码中判断俩个对象计算后地址是否一样是通过hashCode来进行。先进行hashCode计算,如果结果不一致,不再进行继续equal方法;如果计算结果一样,发生哈希碰撞,再通过equal方法来判定对象是否一样,如果一样,那么后面的会将前面的值覆盖掉,(因为最初的Set模块设计思想就是数据唯一不重复)。但是作为继承于万物基类Object的toString、hashCode、equal三个方法,后俩个方法在集合实际应用中的重写,可以灵活实现对上述这个过程的计算判定行为。
红黑树在java8中的HashMap和HashSet、TreeMap、TreeSet中的应用
底层的数据结构设计是为了达到我们要到的目的;数组和链表在查询和增删更有优点;哈希表+链表是HashMap和HashSet中常规数据结构;之所以这样说是java1.8后HashMap额外引入了红黑树;红黑树是一种二叉树上的延伸发展,也是一种结构,相对数组和链表来讲是一种折中的方案,在定位查询和增删中都相对可以;
如上图所示为HashMap的数据结构,刚开始采用的数据结构是数组+链表;当达到一定条件时,采用红黑树。HashSet的内部源码实际是调用HashMap,所以原理一致;
上图为TreeMap中的红黑树结构;
Set的接口及其实现;
Set接口常用的实现类有HashSet,TreeSet,LinkedHashSet。这里主要汇总下HashSet,TreeSet。
Set模块会涉及到哈希表和红黑树的数据结构;哈希表可以理解为数组和链表的结合体。
HashSet
HashSet其实源码也是通过HashMap来实现,原理上面有讲不再赘述;不可重复,无序,采用数组+链表(红黑树)结构,具有增删,查询都可以的特点;
TreeSet
TreeSet源码也是通过TreeMap来实现,采用红黑树数据结构;相比HashSet集合,它具有排序功能。
排序可以通过俩种方式实现方式,一种是自然排序,一种是定制排序。
1.自然排序
元素自身实现Comparable接口的方式;
2.定制排序
容器自身附带实现Comparator接口的方法;
下面是对于TreeMap使用自然排序实现排序的第一种例子;
package com.zjl.demo09;
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private String stunNo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStunNo() {
return stunNo;
}
public void setStunNo(String stunNo) {
this.stunNo = stunNo;
}
public Student() {
}
public Student(String name, String stunNo) {
this.name = name;
this.stunNo = stunNo;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", stunNo='" + stunNo + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) &&
Objects.equals(stunNo, student.stunNo);
}
@Override
public int hashCode() {
return Objects.hash(name, stunNo);
}
@Override
public int compareTo(Student o) {
int n2 = this.stunNo.compareTo(o.getStunNo());
return n2;
}
}
package com.zjl.demo09;
import java.util.Map;
import java.util.TreeMap;
/**
* TreeMapde 的使用;
* 存储结构:红黑树
*/
public class TreeMapTest {
public static void main(String[] args) {
TreeMap<Student, String> students = new TreeMap<>();
//添加元素
Student st1 = new Student("孙三", "1212");
Student st2 = new Student("战狼", "13");
Student st3 = new Student("王孙", "2323");
students.put(new Student("孙三", "1212"),"北京");
students.put(st1,"纽约");
students.put(st2,"天津");
students.put(st3,"天津");
System.out.println(students.size());
System.out.println(students.toString());
//删除
students.remove(st1);
System.out.println(students);
//遍历
//使用keyset
for (Student student : students.keySet()) {
System.out.println(student.toString());
}
//使用entryset
for (Map.Entry<Student, String> studentStringEntry : students.entrySet()) {
System.out.println(studentStringEntry.getKey()+":---"+studentStringEntry.getValue());
}
//判断
System.out.println(students.containsKey(new Student("王孙", "2323")));
}
}
运行结果
3
{Student{name='孙三', stunNo='1212'}=纽约, Student{name='战狼', stunNo='13'}=天津, Student{name='王孙', stunNo='2323'}=天津}
{Student{name='战狼', stunNo='13'}=天津, Student{name='王孙', stunNo='2323'}=天津}
Student{name='战狼', stunNo='13'}
Student{name='王孙', stunNo='2323'}
Student{name='战狼', stunNo='13'}:---天津
Student{name='王孙', stunNo='2323'}:---天津
true
下面采用定制排序方式举例
package com.zjl.demo08;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author zhangjingli
*/
public class TreeSetTest {
public static void main(String[] args) {
TreeSet<String> treeSet=new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int n1=o1.length()-o2.length();
int n2=o1.compareTo(o2);
return n1==0?n2:n1;
}
});
treeSet.add("as");
treeSet.add("jkhdfjahsfh");
treeSet.add("asd");
treeSet.add("uiui");
for (String s : treeSet) {
System.out.println(s);
}
}
}
下面是实际运行结果:
as
asd
uiui
jkhdfjahsfh
自然排序是集合元素对象类必须实现Comparable接口,实现compare to方法,返回一个int类型,做自然升序排序,返回正数,表示值比较大,排右边;返回负数,表示值小,排左边;返回0,表示值一样,覆盖掉(jdk1.8下验证如此);(值得注意的是基本类型的封装类和String类型都已经实现了这个接口,可以直接使用)。
定制排序是容器TreeSet创建对象时,使用有参构造,参数是比较器Comparator接口的实现类,重写compare方法;一样返回int类型,同自然排序一样的升序排序。
查看源码jdk1.8是TreeSet添加元素只会用compare方法比较是否相同,没有网上查到的与重写equal方法有关联,猜想重写equal方法是在其他场合可能用到
Map的接口及其实现;
Map作为键值对集合中最大的接口规范;以下对于Map接口常用的俩个类HashMap和TreeMap做个简单的介绍
HashMap
HashMap是采用的哈希表+红黑树(1.8后);例子与上述相同,不再多讲;
示例代码:
package com.zjl.demo09;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Map接口的使用
* 特点:1.存取键值对;2键值不能重复复,值能重复3。无须
* @author zhangjingli
*/
public class MapTest {
public static void main(String[] args) {
//创建对象
Map<String,String> map=new HashMap<>();
//添加对象
map.put("us","美国");
map.put("ua","英国");
map.put("cn","中国");
map.put("cn","zhongguo");
System.out.println(map.toString());
//删除对象
map.remove("us");
System.out.println(map.toString());
//遍历
//1.使用keySet
Set<String> keySet = map.keySet();
for (String s : keySet) {
System.out.println(s);
System.out.println(map.get(s));
}
//2.使用entryset()
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "---" + entry.getValue());
}
//3.判断
System.out.println(map.containsKey("key"));
System.out.println(map.containsValue("英国"));
}
}
运行结果:
{cn=zhongguo, ua=英国, us=美国}
{cn=zhongguo, ua=英国}
cn
zhongguo
ua
英国
cn---zhongguo
ua---英国
false
true
TreeMap
TreeMap是采用红黑树(1.8后);例子与TreeSet中上述相同,不再多讲;
Iterator迭代器;
Iterator作为迭代器贯穿整个集合框架,所有的集合都可以用Iterator来遍历,由于iterator运行时是锁住的,目前Iterator只提供了remove删除方法,并且是单向遍历;所以没有修改;最主要的方法有三个
hasNext()是否存在下一个元素
next()返回下一个元素
remove()删除迭代器返回的元素
针对集合想在遍历中进行修改,或者指定某个范围进行向前、向后遍历,由此可以用ListIterator,这个迭代器只有List集合才可以使用;相对而言,多了向前,向后遍历;返回指定范围集合,遍历中修改值的特点。