Java集合详解--Collection

1.集合和数组的区别:

在这里插入图片描述

数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:

  1. 数组能存放基本数据类型和对象,而集合类存放的都是对象,集合类不能存放基本数据类型(你存储的是简单的int,它会自动装箱成Integer)。数组和集合存放的对象皆为对象的引用地址
  2. 数组容易固定无法动态改变,集合类容量动态改变。
  3. 数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数
  4. 集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
  5. 集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率

1.1、集合概述

在这里插入图片描述

1.2 Iterable

Iterable是一个接口
它有iterator()这个方法,返回的是Iterator
再来看一下,Iterator也是一个接口,它只有三个方法:
• hasNext()
• next()
• remove()
我们在ArrayList下找到了iterator实现的身影:它是在ArrayList以内部类的方式实现的!并且,从源码可知:Iterator实际上就是在遍历集合

2.Collection

Collection的基础功能:在这里插入图片描述
List集合常用的子类有三个:
• List集合的特点就是:有序(存储顺序和取出顺序一致),可重复
• ArrayList
– 数组实现,查询快,增删慢,轻量级;(线程不安全)
• LinkedList
– LinkedList:双向链表实现,增删快,查询慢 (线程不安全)经常用在增删操作较多而查询操作很少的情况下:
• Vector
– 数组实现,重量级 (线程安全、使用少)

Set集合常用子类有三个:
• Set集合的特点是:元素不可重复
• HashSet
– 底层数据结构是哈希表(是一个元素为链表的数组)
• TreeSet(Set的子接口SortedSet接口的实现类)
– 底层数据结构是红黑树(是一个自平衡的二叉树)
– 保证元素的排序方式
• LinkedHashSet
– 底层数据结构由哈希表和链表组成。

3.Collection—>List—>ArrayList

重要的方法

1、add(E e):直接插入
步骤:

1.1先检查是否需要扩容
1.2得到最小容量
1.3足够:直接添加,不足够:扩容
1.4如果最小容量比数组长度要大,就调用grow()扩容
1.5grow()中调用Arrays.copyOf()【向新数组重新拷贝,并扩容为1.5倍】
1.6第一次扩容后,如果容量还是小于最小容量(minCapacity),就将容量扩充为最小容量

2、add(int index, E e) :插入到指定位置上
步骤:

2.1检查角标是否越界
2.2空间检查,如果有需要进行扩容(arraycopy())
2.3插入元素

3、get
步骤:

检查角标
返回元素

4、set
步骤:

检查角标
替代元素
返回旧值

5、remove
步骤:

检查角标
删除元素
计算出需要移动的个数,并移动
设置为null,让Gc回收

细节原理再说明

• ArrayList是基于动态数组实现的,在增删时候,需要数组的拷贝复制。
• ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
• 删除元素时不会减少容量,若希望减少容量则调用trimToSize()
• 它不是线程安全的。它能存放null值。

ArrayList
底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。
而在LinkedList的底层是一种双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
双向循环链表的查询效率低但是增删效率高。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。

List必会面试题

1、说一说ArrayList的扩容机制吧?

ArrayList是List接口的实现类,它是支持根据需要而动态增长的数组。
java中标准数组是定长的,在数组被创建之后,它们不能被加长或缩短。
这就意味着在创建数组时需要知道数组的所需长度,但有时我们需要动态程序中获取数组长度。
ArrayList就是为此而生的,但是它不是线程安全的,ArrayList按照插入的顺序来存放数据

ArrayList自动扩充机制 实现机制:调用ArrayList.ensureCapacity()下面的
calculateCapacity(elementData, minCapacity)方法:
首先得到当前(元素数据)elementData和(属性的长度) oldCapacity。
然后通过判断(属性长度)oldCapacity和(最小长度)minCapacity参数谁大来决定是否需要扩容, 如果(最小长度)minCapacity大于
(属性长度)oldCapacity,那么我们就对当前的List对象进行扩容。 扩容为原来的1.5倍。然后使用copyOf()数组拷贝的方法,把以前存放的数据转移到新的数组对象中
如果minCapacity不大于oldCapacity那么就不进行扩容。
从此方法中我们可以清晰的看出其实ArrayList扩容的本质就是计算出新的扩容数组的size后实例化, 并将原有数组内容复制到新数组中去。

2、 Arraylist 与 LinkedList 区别?
相同点:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

1、底层数据结构不同:
ArrayList底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点(插入和删除元素会移动后面全部的元素向前或者向后一位)。
而在LinkedList的底层是一种双向链表(JDK1.6 之前为循环链表,JDK1.7
取消了循环)。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。
2、因为底层数据结构不同,所以内容空间占用也是不同的:
ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
3、因为底层数据结构不同,插入和删除是否受元素位置的影响也是不同的:ArrayList插入和删除元素会移动后面全部的元素向前或者向后一位,增加则是列表的末尾。
LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响,如果是要在指定位置 i 插入和删除元素的话需要先移动到指定位置再插入。

3、Arraylist 与 Vector区别?

1 Vector底层也是数组,与ArrayList最大的区别就是:同步(线程安全)
2. Vector是同步的,在要求非同步的情况下,我们一般都是使用ArrayList来替代Vector的了【如果想要ArrayList实现同步,可以使用Collections的方法:List
list = Collections.synchronizedList(new ArrayList(…));,就可以实现同步了】
4. ArrayList在底层数组不够用时在原来的基础上扩展0.5倍成为原来的1.5倍,Vector是扩展1倍成为原来的2倍。

4.Collection—>List—>Vector

Vector是jdk1.2的类了,比较老旧的一个集合类。
这里主要是一些于ArrayList的区别,下面讲

5.Collection—>List—>LinkedList

LinkedList底层是双向链表
LinkedList还实现了Deque接口,因此,除了链表我们还可以操作LinkedList像操作队列和栈一样。
LinkedList变量就这么几个,因为我们操作单向链表的时候也发现了:有了头结点,其他的数据我们都可以获取得到了。(双向链表也同理)

重要的方法

1.add方法实际上就是往链表最后添加元素

public boolean add(E e) {
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

2.remove方法
就是双向链表中删除结点
在这里插入图片描述

3.get方法
内部是长度小于一半就从头遍历,否则从尾遍历

4.set方法
set方法和get方法其实差不多,根据下标来判断是从头遍历还是从尾遍历

6.Collection—>List—>小结

ArrayList:
• 底层实现是数组
• ArrayList的默认初始化容量是10,每次扩容时候增加原先容量的一半,也就是变为原来的1.5倍
• 在增删时候,需要数组的拷贝复制(navite 方法由C/C++实现)
LinkedList:
• 底层实现是双向链表[双向链表方便实现往前遍历]
Vector:
• 底层是数组,现在已少用,被ArrayList替代,原因有两个:
– Vector所有方法都是同步,有性能损失。
– Vector初始length是10 超过length时 以100%比率增长,相比于ArrayList更多消耗内存。
总的来说:查询多用ArrayList,增删多用LinkedList。
ArrayList增删慢不是绝对的(在数量大的情况下,已测试):
• 如果增加元素一直是使用add()(增加到末尾)的话,那是ArrayList要快
• 一直删除末尾的元素也是ArrayList要快【不用复制移动位置】
• 至于如果删除的是中间的位置的话,还是ArrayList要快!
但一般来说:增删多还是用LinkedList,因为上面的情况是极端的~

7.Collection—>Set—>HashSet

在这里插入图片描述

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;


	//2、我们知道Map是一个映射,有key有value,
	//既然HashSet底层用的是HashMap,那么value在哪里呢???,value是一个Object
    private transient HashMap<E,Object> map;
    
    //3、所有的value都是它,Object
    private static final Object PRESENT = new Object();
     
     //1、底层实际上是一个HashMap实例
    public HashSet() {
        map = new HashMap<>();
    }
    .......后面还有很多就不截图了

我们可以归纳HashSet的要点了:
• 实现Set接口
• 不保证迭代顺序
• 允许元素为null
• 底层实际上是一个HashMap实例
• 非同步
• 初始容量非常影响迭代性能
• HashSet是线程非安全的

所以可以直接总结出:HashSet实际上就是封装了HashMap,操作HashSet元素实际上就是操作HashMap。这也是面向对象的一种体现,重用性贼高!
在这里想先看下HashMap就去目录找到去看一下

7.1HashSet的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可能无法正常工作

7.2如何达到不能存在重复元素的目的?(7.1总结)

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

8.Collection—>Set—>TreeSet

先看看TreeSet顶部注释:
在这里插入图片描述
在这里插入图片描述

从顶部注释来看,我们就可以归纳TreeSet的要点了:
• 实现NavigableSet接口
• 可以实现排序功能
• 底层实际上是一个TreeMap实例
• 非同步

8.1TreeSet特征:

TreeSet实现了SortedSet接口,顾名思义这是一种排序的Set集合,查看jdk源码发现底层是用TreeMap实现的,本质上是一个红黑树原理。 正因为它是排序了的,所以相对HashSet来说,TreeSet提供了一些额外的按排序位置访问元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
TreeSet的排序分两种类型,一种是自然排序,另一种是定制排序。

8.2自然排序(在元素中写排序规则)

TreeSet 会调用compareTo方法比较元素大小,然后按升序排序。所以自然排序中的元素对象,都必须实现了Comparable接口,否则会抛出异常。对于TreeSet判断元素是否重复的标准,也是调用元素从Comparable接口继承而来额compareTo方法,如果返回0则是重复元素(两个元素I相等)。Java的常见类都已经实现了Comparable接口。
因为TreeSet会调用元素的compareTo方法,这就要求所有元素的类型都相同,否则也会发生异常。也就是说,TreeSet只允许存入同一类的元素。

8.3定制排序(在集合中写排序规则)

TreeSet还有一种排序就是定制排序,定制排序时候,需要关联一个Comparator对象,由Comparator提供排序逻辑。

8.4其他知识点

TreeSet是依靠TreeMap来实现的。
TreeSet是一个有序集合,TreeSet中元素将按照升序排列,缺省是按照自然顺序进行排列,意味着TreeSet中元素要实现Comparable接口。
我们可以在构造TreeSet对象时,传递实现了Comparator接口的比较器对象。

Comparable和Comparator
Comparable 接口以提供自然排序顺序。
对于那些没有自然顺序的类、或者当您想要一个不同于自然顺序的顺序时,您可以实现Comparator 接口来定义您自己的排序函数。可以将Comparator传递给Collections.sort或Arrays.sort。

Comparator接口
当一个类并未实现Comparable,或者不喜欢缺省的Comaparable行为。可以实现Comparator接口
直接实现Comparator的compare接口完成自定义比较类。
例:Arrays.sort(results, new Comparator() 数组排序 RepDataQueryExecutor
例:Collections.sort(lst,new Comparator()

9.Collection—>Set—>LinkedHashSet

先看看LinkedHashSet顶部注释:
在这里插入图片描述
从顶部注释来看,我们就可以归纳LinkedHashSet的要点了:
• 迭代是有序的
• 允许为null
• 底层实际上是一个HashMap+双向链表实例(其实就是LinkedHashMap)…
• 非同步
• 性能比HashSet差一丢丢,因为要维护一个双向链表
• 初始容量与迭代无关,LinkedHashSet迭代的是双向链表

10.1LinkedHashSet的特征

LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。
LinkedHashSet本质上也是从LinkedHashMap而来,LinkedHashSet的所有方法都继承自HashSet, 而它能维持元素的插入顺序的性质则继承自LinkedHashMap.

10.Collection—>Set—>小结

可以很明显地看到,Set集合的底层就是Map,所以我都没有做太多的分析在上面,也没什么好分析的了。
下面总结一下Set集合常用的三个子类吧:
HashSet:
• 无序,允许为null,底层是HashMap(散列表+红黑树),非线程同步
TreeSet:
• 有序,不允许为null,底层是TreeMap(红黑树),非线程同步
LinkedHashSet:
• 迭代有序,允许为null,底层是HashMap+双向链表,非线程同步
从结论而言我们就可以根据自己的实际情况来使用了。

11.几种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类型的元素

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
// 谢谢回答邀请。这是我关于《Java集合详解》的简要回答。 Java集合Java编程语言提供的一种数据容器,用于存储、操作和访问数据。Java集合框架包含了各种不同类型的集合类,如列表、集合和映射等,以满足不同的数据存储和操作需求。 Java集合框架的核心接口是Collection接口和Map接口。Collection接口是用于存储元素的容器,其中包含了常用的集合操作方法,如添加、删除、查找和遍历等。常见的Collection实现类有ArrayList、LinkedList和HashSet等。Map接口是一种键值对的映射容器,其中每个元素包含一个键和对应的值。常见的Map实现类有HashMap、TreeMap和LinkedHashMap等。 在Java集合框架中,还有一些特殊的集合类,如队列、堆栈和优先队列等。队列是以先入先出(FIFO)的方式存储元素的容器,常用的队列类有LinkedList和ArrayDeque等。堆栈是以后入先出(LIFO)的方式存储元素的容器,常用的堆栈类有Stack和ArrayDeque等。优先队列是一种按照元素优先级进行排序的容器,常用的优先队列类有PriorityQueue等。 Java集合框架提供了丰富的功能和灵活的操作方式,可以满足不同场景下的数据存储和处理需求。使用Java集合框架可以提高代码的重用性和可维护性,减少开发和维护的工作量。同时,由于Java集合框架是开源的,广泛应用于各种Java项目中,因此具有丰富的资源和工具支持,便于开发人员快速上手和解决问题。 总之,Java集合框架是Java编程中非常重要和常用的部分,掌握好Java集合的基本概念和使用方法,对于提高代码质量和开发效率都有很大的帮助。希望这个回答对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值