集合学习笔记

本文详细介绍了Java集合框架,包括List接口的ArrayList、LinkedList和Vector的特性与底层实现,特别是ArrayList的扩容机制。同时,讲解了Set接口的无序性和不可重复性,以及HashMap的底层实现原理,强调了重写hashCode()和equals()的重要性。最后,提到了Collection接口中的常用方法和迭代器的使用。
摘要由CSDN通过智能技术生成

一、集合框架

1.集合/数组都是对多个数据进行存储操作的结构,简称Java容器

2.1 数组存储多个数据方面的特点:

>一旦初始化以后,其长度就确定了。
>数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据。

比如:String[] arr;int[] arr1;Object[] arr2;

2.2 数组在存储多个数据方面的缺点:

>一旦初始化以后,其长度就不可修改。
> 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
>获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
>数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

二、集合框架

集合框架被设计成要满足以下几个目标。

>该框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。

>该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性。

>对一个集合的扩展和适应必须是简单的

Collection接口
具体类
List(ArrayList、LinkedList):线性列表的存储方式,长度可动态改变。
Map(HashMap):key->value对的存储方式,长度可动态改变
Set接口
(HashSet、LinkedSet):equals()、hashCode()

(TreeSet为例):Comparable:compareTo(Object obj)
(Comparator):compare(Object o1,Object o2)
list接口:存储有序的、可重复的数据。 -->“动态”数组

List接口概述

ArrayList、LinkedList、Vector:三个类都实现了List接口,存储数据的特点相同,存储有序的、可重复的数据。

jdk7情况下

ArrayList:

作为List主要实现类,线程不安全的,效率高;
底层使用Object[] elementDate(数组)存储,查找效率相对LinkedList要快

ArrayList list = new ArrayList()底层代码意义:创建长度是10的Object[]数组elementDate,当添加元素超过数组容量后,开始判断并进行扩容操作。

底层存储原理:

list.add(object);
elementData[0] = new Integer(object);
……
list.add(object);//如果此次的添加导致底层elementDate数组容量不够,则扩容。
默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:
ArrayList list = new ArrayList(int capacity)

jdk8情况下

ArrayList list = new ArrayList()底层Object[] elementDate初始化为空的{}。并没有创建长度为10的数组。
list.add(object);//第一次调用add()时,底层才创建了长度10的数组,并将数据Object添加到elementDate
……
后续添加和扩容操作与jdk 7 无异

jdk7中的ArrayList的创建类似于单列的饿汉式。而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

LinkedList:

对于频繁的插入、删除操作,使用此效率比ArrayList高,底层是双向链表存储。

LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null

list.add(object);将object 封装到Node中,创建了Node对象。

其中,Node定义: 体现了LinkedList的双向链表的说法

private static class Node{
E item
Node next;
Node pre;
Node(Node prev,E element,Node next){
this.item = element;
this.next = next;
this.prev = prev;
}
}

Vector:

作为List接口的古老实现类,线程安全,效率低;底层使用Object[] elementDate(数组)存储。

jdk7与jdk8中通过vector()构造器创建对象时,底层创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。

有stack子类 栈

Set接口概述

存储无序的、不可重复的数据 与高中“集合”相似

Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

要求:想Set中添加的数据,其所在类一定要重写hashCode()和equals()
要求: 重写的hashCode()和equals()保持一致性:相等的对象必须具有相等的散列码;

Set方法中存储查找都需要先通过hash值进行查找数据位置。

经典例题:

public void test3(){
HashSet set = new HashSet();
Person p1 = new Person (1001 , “AA”);
Person p2 = new Person (1002, “BB”);
set.add(p1);
set.add(p2);
System.out.println(set);
p1.name = “CC”;
set.remove(p1); //执行remove操作,先进行p1的hash值的计算找到相应位置,p1内容改变相应hash值也跟随变化,找不到set内第一次存储的p1;
System.out.println(set);
set.add(new Person(1001,“CC”));//计算将添加对象的hash值位置为空,添加成功
set.add(new Person(1001,“AA”));//对应位置的hash值与将添加元素的hash值不同,能添加。
System.out.println(set);
}

特性:
1.无序性:不等于随机性。存储的数据在底层数组中不是按照数组索引顺序添加,而是根据哈希值进行添加。
(元素存放位置的无序性)
2.不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

添加元素的过程:
HashSet------先计算元素哈希值,通过哈希值找到存储位置,当哈希值与某一元素的位置相同,再进行equal()比较。

向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值。

此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置)。

通过哈希值找到索引位置后进行数据唯一性的判断

判断数组此位之上是否已经有元素:
如果此位之上没有其他元素,则元素a添加成功。
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不同,则元素a添加成功。
如果hash值相同,进而需要调用元素a所在类equals()方法:
equals()返回true,则元素a添加失败
equals()返回false,则元素a添加成功

对于添加成功的情况2和情况3而言:元素a 与指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。(指针向上指,指针尾部为添加新元素)
jdk 8 : 原来的元素在数组中,指向元素a(指针向下指,新元素为被指方向)
七上八下—指针指向问题。
数组+链表结合

LinkedHashSet:是HashSet的子类,再添加数据的同时,每个数据还维护了两个引用,记录数据前后顺序。

数据之间有了排列顺序。类似于在大厅内排队的人自由走动,但是每个人都有自己的序号,每个人可能不知道前后的人是谁但是都知道自己前面后面的人是几号。(乱序但有序)

	优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
	缺点:但是占用空间较大
Map接口:

双列集合,用来存储一对(key-value)数据。
HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

HashMap:作为Map主要实现类

---->LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。

TreeMap:保证按照添加的key-balue对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序。
底层使用红黑树

Hashtable:作为古老的实现类;线程安全,效率低; 不能存储null的key和value。

Properties:常用来处置配置文件。key和value都是String类型。

HashMap的底层:
数组+链表(jdk7及之前)
数组+链表+红黑树(jdk8)


Map结构的理解:
Map中的key:无序的,不可重复的,使用Set存储所有key —>可以所在的类要重写equals()和hashCode()

Map中的value:无序的、可重复的,使用Collection存储所有value—>value所在的类要重写equals()

一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有entry


HashMap的底层实现原理

jdk7

HashMap map = new HashMap()
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
………执行过多次添加put………
map.put(key1,value1);

先调用key1所在类的hashCode()计算key1的哈希值,通过一些算法计算,得到在Entry数组中的存放位置。

①如果这个位置为空,则key1-value1添加成功----情况1
②如果这个位置上不空,则代表该位置存在至少一个数据(以链表形式存储),比较key1和已经存在的一个或多个数据的哈希值
(1)key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功-----情况2
(1)key1的哈希值和已经存在的某一个数据(key2-vaue2)的哈希值相同了(哈希值的计算也会出现计算相同的情况),则继续比较:调用key1所在类的equals(key2)
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。

讲人话就是:想找到要存储的元素的存放位置,如果该位置没有元素则直接存储成功,如果位置已经被存放一些数据,则计算元素的哈希值与这些数据的哈希值进行比较,如果不同跟随这些数据继续存储成功,如果哈希值也相同则调用要存储元素的所在类的equals()方法,如果数据不同跟随添加成功,如果比较数据相同,则替换原有数据。

情况2情况3:此时key1-value1和原来的数据以链表的方式存储。

添加过程中的扩容,默认扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

jdk8 相较于jdk7在底层实现方面不同:

>1.new HashMap():底层没有创建一个长度为16的数组
2.jdk8底层的数组是:Node[],而非Entry[]
3.首次调用put()方法时。底层创建长度为16的数组
4.jdk7底层结构只有:数组+链表。jdk8中地层结构:数组+链表+红黑树
当数组的某一索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。

DEFAULT_INITIAL_CAPACITY:HashMap的默认容量:16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threashold:扩容的临界值, = 容量填充因子:160.75 = > 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object balue)
查询:get(Object key)
长度:size()
遍历:keySet()//values()//entrySet

三、Collection接口中的方法使用。

add(Object obj),addAll(Collection coll),size(),isEmpty(),clear();
contains(Object obj),containsAll(Collection coll),remove(objectt obj),removeAll(Collection coll),
retainsAll(Collection coll)求交集,equals(Object obj);
hasCode(),toArray(),iterator();

Iterator迭代器
提供一种方法访问一个容器对象中各个元素,而又不许需要暴露该对象的内部细节。迭代器为容器而生。
Iterator只是一个迭代器,并不是再生成一个新的集合进行迭代。
hashNext() /next() 一起使用实现迭代

while (iterator.hasNext()) {
System.out.println(iterator.next());
}

hashNext()判断的时候还有下一个元素
next()指针下移并将下移后集合位置上的元素返回

增强for循环

for(Object obj: arr) {
System.out.println(obj);
}

迭代可以通过Iterator内部定义的remove()方法删除某些元素iterator.remove(“Object”)
但需要注意remove()需要在.next()以后才调用,否则指针不下移无法实现。

Java集合分为Collection和Map两种接口

Collection借楼:单列数据,定义了存取一组对象的方法的集合
>List:元素有序、可重复的集合
>Set:元素无序、不可重复的集合
Map接口:双列数据,保存具有映射关系“key-valued对”的集合。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值