18 Java的集合框架

JavaSE 基础之十八

18 Java的集合框架

① 集合的概念

  集合的概念:集合的出现是为了解决数组在开发过程中所遇到的问题。

② 集合的结构

1. Collection

1. List

(1)基本概念
  1. Collection的直接子接口,代表有序的collection,List采用某种特定的插入顺序来保证里面的数据都是有顺序的。
  2. 用户可以对List中存放的数据根据位置来进行精确的控制,同时可以根据元素的索引来获取到元素的内容。
(2)特点
  1. List中的元素是有顺序的。
  2. List允许元素的重复出现。
  3. List可以支持null数据。
  4. 可以使用索引来获取List的元素。
(3)具体实现
Ⅰ. ArrayList
  1. ArrayList是一个动态数组,最常用最常见的集合,允许任何符合规则的元素插入,包括null也可以插入。
  2. ArrayList底层是数组,但是这个数组可以进行动态扩容的。底层默认初始化的容量为10,该容量代表数组的大小,随着集合中元素的内容不断增加,容器的大小也会随着变化,每次向容器中加入数据的时候都要去检测当前容量是否够用,当快要溢出的时候,进行扩容。但是扩容的效率比较低。
  扩容:创建一个新的数组。将以前的数据赋值到新的数组,将以前数组废弃。
  ArrayList扩容的效率比较低,若能提前能确定空间,优先使用构造器初始化数组容量。

源码解析:
  1.当调用add方法的时候,检查数组是否够用,如果不够用,将创建一个容器大一倍的数组,将原来数组的数据拷贝到新数组中,再将原来的数组废弃。
  2. 当调用remove方法的时候,将指定元素移除后,将后面的原整体向前移动,通过system.copyof()来实现的。
  3.一次删除会有多个元素移动,add的时候,添加到指定的位置也会整体移动。

Ⅱ. LinkedList
  1. 底层是一个双向链表,通过一些方法操作链表的头和尾。
  2. 由于两种集合底层数据结构不同,LinkedList不能随机访问,所有的操作都是按照双向列表来执行,在列表中索引会采用从头或者结尾来遍历数据,这样做的目的是在操作集合的时候删除、增加可以提高效率。
  3. 双向列表:列表中的每个元素都是独立的,每个空间都存放本身的数据,以及上一个对象的引用和下一个对象的引用(索引)。

源码解析:
  基于链表的数据结构,每个元素都包含了上一个和下一个元素的引用,所以add/remove方法只会影响到上一个和下一个元素。
链表结构:
  1. 单链表:单链表中链表对象维护了一个first引用,该引用指向节点链表中的第一个节点元素,每个节点对象维护了一个next引用,next指向下一个元素的引用。(node:自己的数据+下一个元素的引用) 
  2. 双向链表:链表对象维护了一个first引用和last引用,这两个引用分别指向链表的手和尾。每个节点对象维护了对象的数据、prev、next引用,用来指向前后节点对象。

Ⅲ. Vector
  1. 与ArrayList相似,底层都是动态数组,但是Vector是线程同步的,在操作数据的方法上面加上了synchronized关键字线程安全的。
  2. JDK1.0就开发出来了Vector,JDK1.2之后才提出集合框架,有很多程序还是用Vector来完成的,为了向后兼容,将Vector放在list下面,保留以前Vector的特性,但是现在一般都用ArrayList。   3. 效率比ArrayList低。

三者的区别:
1. ArrayList底层是一个动态数组,LinkedList底层是双向链表来实现的。
2. 对于随机访问数据,get、set等,ArrayList效率 比LinkedList的效率高。因为ArrayList底层是一个动态数组,查找元素在一个连续的空间找,速度很快。LinkedList移动指针(索引)去判断当前元素下一个是否是我们需要的。
3. ArrayList结构对数据进行增删效率比较低,中间插入一个元素,以后的所有元素都需要挪动位置,下标都需要变化。LinkedList前一个元素只需要改变引用,后一个只需要改变引用。
4. Vector是线程安全的,效率非常低的。
5. ArrayList扩容的效率比较低,若能提前能确定空间,优先使用构造器初始化数组容量。

泛型:
1. 在集合中存放数据的时候,可以存放任何类型的数据,底层是一个Object,取出数据的时候默认也是Object对象。如果要将当前对象转化为要使用的对象必须知道取出来的值是什么类型。 不然经常出现java.lang.ClassCastException。 
2. 泛型在定义了接口是指定参数类型,这个参数类型默认用E(element)来表示元素,类型参数值是在申明变量或者创建对象的时候确定。
3. 如果给集合知道泛型,意味着操作内容必须传递指定的参数类型。在获取集合中的数据的时候不用转换对象类型,默认返回传递的泛型类型。
4. E表示一个占位符。
5. 一旦传入了参数类型泛型就确定了。传递参数的时候必须和泛型同样的数据。通过?设置占位符,在运行时再确定具体的内容。根据子类的泛型确定父类的泛型。

2. Set

(1)特点
  1. 存放的元素不能重复,当加入一个元素到容器中,要比较元素的内容是否重复,自动取掉重复的内容。(如果存放的是对象,比较对象是否一样,需要重写hashcode和equals方法。) 
  2. 存放在容器中的元素可能有顺序,也可能没有顺序。 
  3. 元素在容器中没有顺序,所以不能通过下标获取里面的元素。 
(2)具体实现
Ⅰ. HashSet
基于hash算法的Set接口实现
  1. 元素不能重复
  2. 元素没有顺序
  3. 不能使用get、set随机访问
  4. 元素允许为null

  • 存放对象,调用add方法需要进行比较,调用对象hashcode和equals方法比较对象是否一样,如果一样不会存进去。

  • 判断两个对象是否相等:

    1. 判断两个对象的hashcode码是否一样(hashcode一旦确定,元素在内存中位置也将确定下来,桶运算),如果两个hashcode不一样,两个对象不相等。
    2. 如果hashcode码相等,再调用对象的equals方法来比较值是否相等。如果值相等,才能确定这两个对象相等。
  • 深入挖掘
    hashSet数据结构:采用hash表
    JDK1.8之前:哈希表=数组+链表
    JDK1.8及之后:哈希表=数组+链表 或者 数组+红黑树(查询效率非常快)
  • JDK1.8中,hash存储采用数组+链表+红黑树来存数据,当产生hash冲突(hash值一样,但是内容却不一样)的时候,值就会用链表来存,但是链表长度超过8的时候,自动变成红黑树来进行排序。

Ⅱ. TreeSet
  1. 可以允许排序的一种集合,继承SortedSet接口。
  2. 元素不能重复。
  3. 底层是基于TreeMap来实现的,基于红黑树算法。TreeSet的数据来源于TreeMap的key值,采用自然排序或自定义排序。

HashSet:速度快,不排序
TreeSet:速度慢,排序

3. 迭代器 Iterator
  • Iterator接口成为迭代器,用于遍历集合的一种接口。
  • 遍历:从集合中取出每一个元素的过程,在collection中有一个对象,通过这个对象可以遍历集合中的所有元素。
  • 提供给集合用,输出数据。
4. Collection与Collections的区别

  Collection:接口,集合的顶层框架,主要用于制定集合的规范。
  Collections:集合工具包,用于操作Collection所定义的各种集合,所有方法都是静态的。比如排序、反转、对元素进行搜索、线程安全操作等。

2. Map接口

(1)特点
  由一系列键值对组成的集合,提供了key-value的映射,并没有继承Collection。
  1. key-value有一一对应的关系,一个key对应一个唯一的值。
  2. 因为Map的键采用set来存放,键不能重复,value可以一样。
  3. HashMap(hash算法)和TreeMap(红黑树结构)。
  4. 将Map的所有key放在一起,那就是一个Set集合,key可能有顺序,也可能没有顺序。
  5. 将所有的值取出来放在一起,就变成一个Collection。
  6. 在没有定义泛型的时候,键和值均可以是任何类型。 
(2)具体实现
Ⅰ. HashMap与HashTable

分析源码:
1. HashMap基于hash表来实现的,数组+链表(单链) 数组+红黑。
2. 底层采用Entry(JDK1.7以及以前,JDK1.8Node表示)+链表。
3. HashMap采用key-value的形式来存数据,允许为null,但是只能出现一次。
4. HashMap线程不安全。
5. HashMap存入的顺序和取出的顺序可能不一样。
6. HashMap保存数据的时候,根据key的hash值来决定存放位置。(hash冲突)

分析代码的属性和方法:
1. initialCapacity:初始容量,值得是HashMap在初始化的时候自身的容量,可以在构造方法中传递进来,如果不指定值,默认16。
2. size:当前HashMap的键值对数量。
3. LoadFactor(DEFAULT_LOAD_FACTOR)当HashMap的值到达一定范围后,开始扩容,加载因子默认是0.75,也可以通过构造来指定加载因子,默认值为0.75(16*0.75=12),当容量到达12的时候,就开始扩容。
4. threshold:扩容阀值:一般默认扩容为原来的2倍。扩容阀值=HashMap总量*加载因子。

与HashTable区别:
  1. 两者在API的操作是一样的,没有太大的区别。都实现了Map接口。
  2. HashMap允许null键null值(null键只能出现一次), HashTable不允许出现null键null值。
  3. HashMap线程不安全,HashTable是线程安全的。
  4. HashMap效率高,HashTable更低。
Ⅱ. TreeMap
  底层是采用红黑树算法来进行数据的查找,红黑树的每一个节点都是一个Entry,里面包含了K、V、left、right、parent、color,root节点默认是黑色的,Entry根据Key来进行排序的,Entry节点包含的内容为Value。
  Entry排序是按照Key来排的,Key是放在TreeSet、Entry的Key要比较大小根据的比较器Comparator来进行判断的。
  size:红黑树的节点个数 。


posted on 2019-03-22 21:51  PenjayFeng  阅读(156)  评论(0编辑  收藏  举报

导航