数组和集合的比较
在说集合之前先谈下数组的概念;
数组:声明一个引用该数组的变量,并指明整个变量可以引用的数组类型。一旦声明了数组的大小,就不能再修改。这里的数组长度也是必需的,不能少。
为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java 提供了集合类;集合类存放于java.util包中;
数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:
- 数组能存放基本数据类型和对象,而集合类存放的都是对象的引用,而非对象本身
- 数组容量固定无法动态改变,集合类容量动态改变
- 数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数
- 集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
- 集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率
Java 集合类型分为 Collection 和 Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。
各个集合接口介绍
Collection接口常用方法
List接口
List接口是Collection接口的子接口
特点是:有序、允许重复
常见的List接口的实现类
- ArrayList:底层是数组实现所以也实现了数组的特性查询快,增删慢;
- LinkedList:底层双向链表实现,增删快,查询慢
- Vector:数组实现,线程安全,大部分方法上都有synchronized,一般用于要求线程安全的属性定义。(极少用,效率低)
add方法用于向集合中添加元素,如果ArrayList中真正存放数据的数组长度不足,则新建一个数组,新数组的长度为原始长度1.5倍,并拷贝原始数组中的数据到新数组中,最后再追加新元素
数组链表基础知识
数组的优点
- 随机访问性强
- 查找速度快
数组的缺点
- 插入和删除效率低,必须移动数组
- 内存空间要求高,必须有足够的连续内存空间。
- 数组大小固定,不能动态拓展
- 数组只能存储一种类型的数据
链表的优点
- 插入删除速度快,只需通过指针指向对象地址
- 内存利用率高,不会浪费内存
- 大小没有固定,拓展很灵活
链表的缺点
- 不支持随即查找,必须从第一个开始遍历,查找效率低
有没有一种方式整合两种数据结构的优势?
散列表
什么是散列表?散列表有什么特点?
散列表也叫哈希表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
特点:
- 访问速度很快:由于散列表有散列函数,可以将指定的 Key 都映射到一个地址上,所以在访问一个 Key(键)对应的 Value(值)时,根本不需要一个一个地进行查找,可以直接跳到那个地址。所以我们在对散列表进行添加、删除、修改、查找等任何操作时,速度都很快。
- 需要额外的空间:首先,散列表实际上是存不满的,如果一个散列表刚好能够存满,那么肯定是个巧合。而且当散列表中元素的使用率越来越高时,性能会下降,所以一般会选择扩容来解决这个问题。另外,如果有冲突的话,则也是需要额外的空间去存储的,比如链地址法,不但需要额外的空间,甚至需要使用其他数据结构。
- 无序:散列表还有一个非常明显的特点,那就是无序。为了能够更快地访问元素,散列表是根据散列函数直接找到存储地址的,这样我们的访问速度就能够更快,但是对于有序访问却没有办法应对。
- 可能会产生碰撞(冲突):没有完美的散列函数,无论如何总会产生冲突,这时就需要采用冲突解决方案,这也使散列表更加复杂。通常在不同的高级语言的实现中,对于冲突的解决方案不一定一样。
哈希冲突:哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值。这时候就产生了哈希冲突。
拉链法
解决冲突的方法有很种,拉链法是其中之一。
拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。
Map
Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。
- TreeMap:有序(基于红黑树对所有的key进行排序)、非线程安全、没有调优选项(因为该树总处于平衡状态)、不允许key值为null
- HashMap:基于哈希表实现,无序、方法不同步、非线程安全、效率较高、允许null值(key和value都允许)
- HashTable:和 HashMap 类似,但它是线程安全的,无序、方法同步、线程安全、效率较低、不允许key值为null
- LinkedHashMap:使用双向链表来维护元素的顺序(不常用)
- Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
- HashMap:适用于Map中插入、删除和定位元素;对同步性或与遗留代码的兼容性没有任何要求时
- Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
底层存储结构介绍
Set 唯一(不重复)
- HashSet:底层数据结构是哈希表。(唯一,无序,最常用)依赖两个方法:hashCode()和equals()来保证元素唯一性。等值查询效率最高
- LinkedHashSet: 底层数据结构是链表和哈希表。(唯一,有序),由链表保证元素有序(FIFO先入先出队列),由哈希表保证元素唯一。主要功能用于排序,范围查询效率较高。
- TreeSet: 底层数据结构是红黑树。(唯一,有序),插入null数据时会报NullPointerException;根据比较的返回值是否是0来决定,保证元素唯一性。
三者都不是线程安全的,如果要使用线程安全可以 Collections.synchronizedSet()
三者数据插入速度对比:HashSet(无序) > LinkHashSet(FIFO队列) > TreeSet(内部排序)
TreeSet最慢,因为内部进行排序。
HashSet为什么是无序的,LinkedHashSet和TreeSet是有序的,具体是怎么实现的
有序、无序是指在进行插入操作时,插入位置的顺序性,先插的位置在前,后插的位置在后,则为有序,反之无序;
LinkedHashSet由链表保证元素有序(FIFO先入先出队列);
TreeSet由自然排序和 比较器排序保证元素有序。
自然排序要进行以下操作:
- Student类中实现 Comparable接口
- 重写Comparable接口中的Compareto方法
比较器排序
- 单独创建一个比较类,这里以MyComparator为例,并且要让其继承Comparator接口
- 重写Comparator接口中的Compare方法
- 在主类的创建实例中使用自定义比较器
总结
每个集合的存在都有它的意义和特点,根据不同的场景去合理的运用不同的集合使我们的程序变得效率更高