集合大家族

概述

Java 的所有集合类都位于 java.util 包,其中提供了一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法。

需要注意的是,集合只能存放对象。比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。在集合存放的是多个对象的引用,对象本身还是放在堆内存中。集合可以存放不同类型,不限数量的数据类型。

  • 线程安全: Vector,HashTable
  • 非线程安全:ArrayList,HashMap,LinkedList,TreeMap,HashSet,TreeSet

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Collection接口

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后 一个构造函数允许用户复制一个Collection。
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:

 Iterator it = collection.iterator(); // 获得一个迭代子
    while(it.hasNext()) {
      Object obj = it.next(); // 得到下一个元素
    }

在这里插入图片描述
  由Collection接口派生的两个接口是List和Set。
  
  在这里插入图片描述

List接口

List 接口为 Collection 直接接口。List 所代表的是有序的 Collection,即它用某种特定的插入顺序来维护元素顺
序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位
置)访问元素,并搜索列表中的元素。实现 List 接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

  • (1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
  • (2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
  • (3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
  • (4)Stack :栈是Vector的一个子类,它实现了一个标准的后进先出的栈。

在这里插入图片描述

ArrayList
  • ArrayList 是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括 null。
  • 每一个 ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
  • size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n)时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。
  • ArrayList 擅长于随机访问。同时 ArrayList 是非同步的。
LinkedList
  • 同样实现 List 接口的 LinkedList 与 ArrayList 不同,ArrayList是一个动态数组,而 LinkedList
    是一个双向链表。所以它除了有 ArrayList 的基本操作方法外还额外提供了 get,remove,insert
    方法在LinkedList 的首部或尾部。
  • 由于实现的方式不同,LinkedList 不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引
    的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在 List 中进 行插入和删除操作。
  • 与 ArrayList 一样,LinkedList 也是非同步的。如果多个线程同时访问一个 List,则必须自己实现访问同步。

一种解决方法是在创建 List 时构造一个同步的 List:

List list = Collections.synchronizedList(new LinkedList());
Aarraylist 和 Linkedlist异同点(区别)
  • 1.ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
  • 2.对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。
  • 3.对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。
    (这一点要看实际情况的。若只对单条数据插入或删除,ArrayList 的速度反而优于 LinkedList。但若是批量随机的插入删除数据,LinkedList 的速度大大优于 ArrayList. 因为 ArrayList 每插入一条数据,要移动插入点及之后的所有数据。)

Vector

与 ArrayList 相似,但是 Vector 是同步的。所以说 Vector 是线程安全的动态数组。它的操作与 ArrayList 几乎一样。区别是Vector是重量级的组件,使用使消耗的资源比较多。

Aarraylist 和 Vector异同点(区别)

ArrayList有三个构造方法:

public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。    
public ArrayList()      //默认构造一个初始容量为10的空列表。    
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表

Vector有四个构造方法:

public Vector()//使用指定的初始容量和等于0的容量增量构造一个空向量。    
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。    
public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量    
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量    

ArrayList和Vector都是用数组实现的,主要有这么三个区别:

  • (1).Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;
  • (2)两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。
  • (3)Vector可以设置增长因子,而ArrayList不可以。
  • (4)Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。
  • (5)如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,arraylist增长率为50%.在集合中使用数据量比较大的数据,用vector有一定的优势。

结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。

Stack 类

Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口

  • Set 是一种不包括重复元素的 Collection。它维持它自己的内部排序,所以随机访问没有任何意义。
  • 与 List 一 样,它同样运行 null 的存在但是仅有一个。
  • 由于 Set 接口的特殊性,所有传入 Set 集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。
  • 实现了 Set 接口的集合有:EnumSet、HashSet、TreeSet。
EnumSet

是枚举的专用 Set。所有的元素都是枚举类型。

这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。

HashSet

HashSet 堪称查询速度最快的集合,因为其内部是以 HashCode 来实现的。它内部元素的顺序是由哈希码来决
定的,所以它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。

HashSet的特征
  • 不仅不能保证元素插入的顺序,而且在元素在以后的顺序中也可能变化(这是由HashSet按HashCode存储对象(元素)决定的,对象变化则可能导致HashCode变化)
  • HashSet是线程非安全的
  • HashSet元素值可以为NULL
HashSet常用方法:
public boolean contains(Object o) :如果set包含指定元素,返回true 
public Iterator iterator()返回set中元素的迭代器 
public Object[] toArray() :返回包含set中所有元素的数组public Object[] toArray(Object[] a) :返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
public boolean add(Object o) :如果set中不存在指定元素,则向set加入
public boolean remove(Object o) :如果set中存在指定元素,则从set中删除 
public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素 
public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true

实现Set接口的HashSet,依靠HashMap来实现的。

  • 我们应该为要存放到散列表的各个对象定义hashCode()和equals()。

HashSet的equals和HashCode
Set集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet如何判断元素重复呢?

HashSet需要同时通过equals和HashCode来判断两个元素是否相等,具体规则是,如果两个元素通过equals为true,并且两个元素的hashCode相等,则这两个元素相等(即重复)。

所以如果要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写前后hashCode返回的结果相等(即保证保存在同一个位置)。所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

试想如果重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet当作两个元素保存起来,这与我们设计HashSet的初衷不符(元素不重复)。

另外如果两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。

如果重写了equals方法但没有重写hashCode方法,则HashSet可能无法正常工作,比如下面的例子。
在这里插入图片描述
上面注释了hashCode方法,所以你将会看到下面的结果。

false  

取消注释,则结果就正确了

true  

如何达到不能存在重复元素的目的?
“键”就是我们要存入的对象,“值”则是一个常量。这样可以确保,我们所需要的存储的信息值是“键”。而“键”在Map中是不能重复的,这就保证了我们存入Set中的所有的元素都不重复。
HashSet如何过滤重复元素
调用元素HashCode获得哈希码–》判断哈希码是否相等,不相等则录入
—》相等则判断equals()后是否相等,不相等在进行 hashcode录入,相等不录入

LinkedHashSet

底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。

TreeSet

  • 基于 TreeMap,生成一个总是处于排序状态的 set,内部以 TreeMap
    来实现。它是使用元素的自然顺序对元素进行排序,或者根据创建 Set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  • 底层数据结构采用二叉树来实现,元素唯一且已经排好序;
  • 唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同,分为自然排序(无参构造)和比较器排序(有参构造)
  • 自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;
  • 比较器排序需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;
自然排序(在元素中写排序规则)

TreeSet 会调用compareTo方法比较元素大小,然后按升序排序。所以自然排序中的元素对象,都必须实现了Comparable接口,否则会跑出异常。对于TreeSet判断元素是否重复的标准,也是调用元素从Comparable接口继承而来额compareTo方法,如果返回0则是重复元素(两个元素I相等)。Java的常见类都已经实现了Comparable接口,下面举例说明没有实现Comparable存入TreeSet时引发异常的情况。
在这里插入图片描述
运行程序会抛出如下异常
在这里插入图片描述
将上面的Err类实现Comparable接口之后程序就能正常运行了
在这里插入图片描述
还有个重要问题是,因为TreeSet会调用元素的compareTo方法,这就要求所有元素的类型都相同,否则也会发生异常。也就是说,TreeSet只允许存入同一类的元素。例如下面这个例子就会抛出类型转换异常
在这里插入图片描述

比较器排序(在集合中写排序规则)

TreeSet还有一种排序就是比较器排序,比较器排序时候,需要关联一个 Comparator对象,由Comparator提供排序逻辑。下面就是一个使用Lambda表达式代替Comparator对象来提供定制排序的例子。 下面是一个比较器排序的列子

在这里插入图片描述
当然将Comparator直接写入TreeSet初始化中也可以。如下。

在这里插入图片描述

几种Set的比较
  • HashSet外部无序地遍历成员。 成员可为任意Object子类的对象,但如果覆盖了equals方法,同
    时注意修改hashCode方法。
  • TreeSet外部有序地遍历成员; 附加实现了SortedSet, 支持子集等要求顺序的操作
    成员要求实现Comparable接口,或者使用Comparator构造
  • TreeSet。成员一般为同一类型。
  • LinkedHashSet外部按成员的插入顺序遍历成员 成员与HashSet成员类似
  • HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。
  • HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。
  • 一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。
  • 一般说来,先把元素添加到HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。
各种Set集合性能分析
  • HashSet和TreeSet是Set集合中用得最多的I集合。HashSet总是比TreeSet集合性能好,因为HashSet不需要额维护元素的顺序。
  • LinkedHashSet需要用额外的链表维护元素的插入顺序,因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高。因为插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。
  • EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum类型的元素,不过已经被迭代器取代。

Set和List的区别

  1. Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。

  2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。

  3. List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。

Map接口

  • Map 与 List、Set 接口不同,它是由一系列键值对组成的集合,提供了 key 到 Value 的映射。请注意!!!, Map 没有继承 Collection 接口
  • 在 Map 中它保证了 key 与 value 之间的一一对应关系。也就是说一个 key 对应一个 value,所 以它不能存在相同的key 值,当然 value 值可以相同。
  • 实现 map 的有:HashMap、TreeMap、HashTabl e、Properties、EnumMap。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
5)Map的其它类:
IdentityHashMap和HashMap的具体区别,IdentityHashMap使用 == 判断两个key是否相等,而HashMap使用的是equals方法比较key值。有什么区别呢?
对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等; 如果作用于引用类型的变量,则比较的是所指向的对象的地址。
对于equals方法,注意:equals方法不能作用于基本数据类型的变量
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
在这里插入图片描述
(6)小结:

  • HashMap 非线程安全
  • HashMap:基于哈希表实现。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。
  • TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。

在这里插入图片描述

如何使用迭代器

通常情况下,你会希望遍历一个集合中的元素。例如,显示集合中的每个元素。

一般遍历数组都是采用for循环或者增强for,这两个方法也可以用在集合框架,但是还有一种方法是采用迭代器遍历集合框架,它是一个对象,实现了Iterator 接口或ListIterator接口。

迭代器,使你能够通过循环来得到或删除集合的元素。ListIterator 继承了Iterator,以允许双向遍历列表和修改元素。

在这里插入图片描述

遍历 ArrayList

import java.util.*;
 
public class Test{
 public static void main(String[] args) {
     List<String> list=new ArrayList<String>();
     list.add("Hello");
     list.add("World");
     list.add("HAHAHAHA");
     //第一种遍历方法使用 For-Each 遍历 List
     for (String str : list) {            //也可以改写 for(int i=0;i<list.size();i++) 这种形式
        System.out.println(str);
     }
 
     //第二种遍历,把链表变为数组相关的内容进行遍历
     String[] strArray=new String[list.size()];
     list.toArray(strArray);
     for(int i=0;i<strArray.length;i++) //这里也可以改写为  for(String str:strArray) 这种形式
     {
        System.out.println(strArray[i]);
     }
     
    //第三种遍历 使用迭代器进行相关遍历
     
     Iterator<String> ite=list.iterator();
     while(ite.hasNext())//判断下一个元素之后有值
     {
         System.out.println(ite.next());
     }
 }
}

解析:

三种方法都是用来遍历ArrayList集合,第三种方法是采用迭代器的方法,该方法可以不用担心在遍历的过程中会超出集合的长度。
遍历 Map

import java.util.*;
 
public class Test{
     public static void main(String[] args) {
      Map<String, String> map = new HashMap<String, String>();
      map.put("1", "value1");
      map.put("2", "value2");
      map.put("3", "value3");
      
      //第一种:普遍使用,二次取值
      System.out.println("通过Map.keySet遍历key和value:");
      for (String key : map.keySet()) {
       System.out.println("key= "+ key + " and value= " + map.get(key));
      }
      
      //第二种
      System.out.println("通过Map.entrySet使用iterator遍历key和value:");
      Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
      while (it.hasNext()) {
       Map.Entry<String, String> entry = it.next();
       System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
      }
      
      //第三种:推荐,尤其是容量大时
      System.out.println("通过Map.entrySet遍历key和value");
      for (Map.Entry<String, String> entry : map.entrySet()) {
       System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
      }
    
      //第四种
      System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
      for (String v : map.values()) {
       System.out.println("value= " + v);
      }
     }
}

Queue

队列,它主要分为两大类,一类是阻塞式队列,队列满了以后再插入元素则会抛出异常,主要包括 ArrayBlockQ
ueue、PriorityBlockingQueue、LinkedBlockingQueue。另一种队列则是双端队列,支持在头、尾两端插入
和移除元素,主要包括:ArrayDeque、LinkedBlockingDeque、LinkedList。

本篇文章参考了:https://blog.csdn.net/leeioy/article/details/54293074
https://blog.csdn.net/feiyanaffection/article/details/81394745
https://blog.csdn.net/zc474235918/article/details/51753198

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值