Java集合详解,一秒就读懂

这篇博客我好一阵子以前就写了一点儿了,一直放在草稿箱里都快忘了,这几天突然又看见了,索性加加点,写完了吧,这里面写了我对集合的一些理解啥的,还有一些专业概念“官话”啥的,自己也背的没有那么专业味儿十足,就在bing上翻的,还有一些地方就是jdk1.8帮助文档的东西了,都是手敲的,自己脑子里的东西,希望能让大家读懂集合这个玩意儿吧。

前言

谈到集合,我们肯定会想到数组这个概念,大家应该都清楚,在Java中,数组是一种效率最高的储存和随机访问对象引用序列的方式,数组就是一个十分简单的线性序列,因此使得元素访问非常快速。但是,也正是因为他的高效率的优点,使得他的数组对象的大小被固定了,它的容量需要事先被定义好,不能随着需求的变化而扩容,这也使得在工作中,数组并不实用。也正是由于数组自身存在的这一种劣势,我们才需要一种更强大的、更灵活的、容量可改变的“集合(Collection)”来储存我们的对象。额外的也不多说了,直接开始吧。

集合概述

  • List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象。
  • Set接口注重独一无二的性质,是不允许重复的集合,不会有多个元素引用相同的对象。
  • Map接口使用键值对存储,Map会维护与Key有关联的值。两个Key可以引用相同的对象,但是Key不能重复。典型的Key是String类型的,但也可以是任何对象。

结构及集合接口

Java集合框架主要包括两种类型的容器:Collection和Map,Collection主要存储元素集合,Map则用来存储键值对映射。 在这里插入图片描述
在这里插入图片描述

Collection集合方法

在这里插入图片描述

概括常用集合分类

Collection接口中有两个我们常用的子类型接口List和Set,他们常用的实现类有ArrayList、LinkedList、HashSet和TreeSet等等。
在这里插入图片描述
在这里插入图片描述

List接口

List介绍:

  • 有序集合(也称序列)。用户可以精确控制列表中每个元素插入的位置,用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。
  • 与Set不同,列表通常允许重复的元素,更正式的,列表通常允许元素e1和e2成对使得e1.equals(e2),并且如果他们允许空元素,他们通常允许多个空元素。
  • List接口提供四种位置(索引)访问列表元素的方法。列表(如Java数组)为零。请注意,这些操作可能与某些实现的索引值(例如LinkedList类)成时执行。因此,如果调用者不知道实现,则通过迭代列表中的元素通常优先于索引。
  • 所述List接口提供了一个特殊的迭代器,成为ListIterator,其允许元素插入和更换,并且除了该Iterator接口提供正常操作的双向访问。提供了一种方法来获取从列表中的指定位置开始的列表迭代器。
  • List接口提供了两种方法来搜索指定的对象。从性能角度来说,谨慎使用这些方法。在许多实现中,他们将执行昂贵的线性搜索。
  • List接口提供了两种方法来有效的插入和删除列表中任意一点的多个元素。

注意:虽然列表允许将其自身作为元素,但建议您非常小心: equals和hashCode方法在这样的列表中不再被很好地定义。

某些列表实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,对于不完成不会导致将不合格元素插入到列表中的不合格元素的操作,可能会在执行选项时抛出异常或成功。 此异常在此接口的规范中标记为“可选”。

List实现类的特点:

  • ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
  • LinkedList:底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
  • Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以储存重复元素

List接口具有如下特点:

  • 容量不固定,随着容量的增加而动态扩容(阈值基本不会达到)·
  • 有序集合(插入的顺序==输出的顺序)
  • 插入的元素可以为null·
  • 增删改查效率更高(相对于LinkedList来说)
  • 线程不安全

ArrayList和LinkedList优缺点分析:
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。

LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。

Vector:是一种老的动态数组,线程同步,效率十分低,一般不建议使用。

因此,当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。

Set接口

Set介绍:

  • Set是不包含重复元素的集合。 更正式地,集合不包含一对元素e1和e2 ,使得e1.equals(e2) ,并且最多一个空元素。 正如其名称所暗示的那样,这个接口模拟了数学集抽象。
  • Set注重独一无二的性质,该体系集合可以知道某物是否已近存在于集合中,不会存储重复的元素
    用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。
  • Set接口除了继承自Collection接口的所有构造函数的合同以及add,equals和hashCode方法的合同外 , 还 增加了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set接口,但它们不包含任何附加的规定。)
  • 构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。

注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响equals比较的方式更改,而对象是集合中的元素, 则不指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。

一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。

HashSet:

  • 基于HashMap实现的,底层采用HashMap来保存元素。
  • 此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证订单在一段时间内保持不变。 这个类允许null元素。
  • 这个类提供了基本操作(add,remove,contains和size)固定的时间性能,假定哈希函数将分散的桶中正确的元素。 迭代此集合需要与HashSet实例的大小(元素数量)和后台HashMap实例(桶数)的“容量”的总和成比例的时间。 因此,如果迭代性能很重要,不要将初始容量设置得太高(或负载因子太低)是非常重要的。
  • 此实现不同步。

HashSet实现过程中判别元素是否重复
为了保证元素不重复,我们应该为保存到HashSet中的对象覆盖hashCode()和equals(),因为再将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此哈希值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加。

注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。

判断过程中使用HashCode判断和使用Equals判断存在先后,先判断HashCode,如果返回的哈希值相同,才会继续判断Equals,如果返回的哈希值不同,则直接判断为不重复元素。

TreeSet:
TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排列需要在TreeSet初始化时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法。
注意:此实现不同步。

TreeSet的自然排序的特点:

  • TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet内部实现的是红黑树(二叉树)算法排序,默认整形排序为从小到大。
  • TreeSet会调用集合元素的compareTo(Objec obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这就是自然排序。
  • Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现该接口的类必须实现该方法,实现了该接口的类必须实现该方法,实现接口的类就可以比较大小了。当调用一个一个对象调用该方法与另一个对象进行比较时,obj1.compareTo(obj2)如果返回0表示两个对象相等;如果返回正整数则表明obj1大于obj2,如果是负整数则相反。

存入TreeSet集合中的元素要具备比较性
比较性要实现Comparable接口,重写该接口的compareTo方法
TreeSet属于Set集合,该集合的元素是不能重复的,TreeSet如何保证元素的唯一性
通过compareTo或者compare方法中的来保证元素的唯一性。
添加的元素必须要实现Comparable接口。当compareTo()函数返回值为0时,说明两个对象相等,此时该对象不会添加进来。

TreeSet集合排序的两种方式:
一,让元素自身具备比较性。
也就是元素需要实现Comparable接口,覆盖compareTo 方法。
这种方式也作为元素的自然排序,也可称为默认排序。
年龄按照搜要条件,年龄相同再比姓名。
二,让容器自身具备比较性,自定义比较器。
需求:当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。
那么这时只能让容器自身具备。
定义一个类实现Comparator 接口,覆盖compare方法。
并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。
当Comparable比较方式,及Comparator比较方式同时存在,以Comparator
比较方式为主。

List和Set的总结

  • List子接口:有序,可以有重复元素。
  • List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
  • Set子接口:无序,不允许重复。
  • Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

Map接口

Map用于保存具有映射关系的数据,Map中保存着两组数据:Key和Value,它们可以是任何引用类的数据,但是Key不能重复。所以,每个 key 只能映射一个 value ,所以可以通过键查找值。
Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。
Map接口不是一个Collection接口的继承,而是从自己的用于维护键值关系的接口层次结构入手。定义该接口描述了从不重复的键到值的映射。

Map主要方法:
在这里插入图片描述

Map.Entry接口:
Map的entrySet()方法返回一个实现Map.Entry接口的对象集合。集合中的每一个对象都是底层Map中的一个特定的键值对。
接口中的方法有:
在这里插入图片描述

HashMap:
基于哈希表的实现的Map接口。 此实现提供了所有可选的Map操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证键值对的顺序。
在Map 中插入、删除和定位元素,HashMap 是最好的选择。
用作Key的对象必须实现HashCode和Equals方法。
尽量不要使用可变对象作为它们的Key。
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

TreeMap:
基于红黑树对所有的Key进行排序;排序方式分为自然排序和定制排序;TreeMap的Key以TreeSet的形式存储,因此对Key的要求与TreeSet对元素的要求基本一致。
TreeSet存在以下集中特殊方法:
在这里插入图片描述

Map的遍历方式:

Map<Integer, String> map1=new HashMap<Integer, String>();

//1.通过map.keySet()获取值的方式遍历	
for (Integer aa:map1.keySet()) {   //使用map.keySet()返回所有的Key值
		String cc = map.get(aa);    //得到每个Key所对应的Value值
        System.out.println(aa+"  "+cc);
        }


//2.通过Map.entrySet使用iterator遍历Key和Value
Iterator<Map.Entry<Integer, String>>  bb=map1.entrySet().iterator();
		while (bb.hasNext()) {
			Map.Entry<Integer, String> entry = bb.next();
			}


//3.使用for循环,通过Map.entrySet遍历Key和Value值  (推荐使用!!)
for (Map.Entry<Integer, String> entry:map1.entrySet()) {
			//map.entrySet返回map1中键值对
			System.out.println("Key="+entry.getKey()+"   Value="+entry.getValue());
			}


//4.通过Map.value遍历所有的value值,但是不能遍历key
for (String dd:map1.values()) {
			System.out.println("Value="+dd);
			}

Map总结

  • HashMap 非线程安全
  • HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
  • HashMap:适用于Map中插入、删除和定位元素。
  • TreeMap:非线程安全基于红黑树实现。
  • TreeMap没有调优选项,因为该树总处于平衡状态。
  • Treemap:适用于按自然顺序或自定义顺序遍历键(key)。

结束语

其实这样看下来,集合这些东西都不难理解,主要是你得记得住这些乱七八糟的功能区别啥的,怎么说的,我也正在Java这条路上一步步往前走着,大家一起努力吧,加油!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值