javaSE--4集合、Collection、Map

一、集合概述

1.1集合是一个容器,是一个载体,可以一次容纳多个对象

1.2集合不能直接存储基本数据类型,另外集合也不能存储java对象,集合当中存储的都是java对象的内存地址(或者说集合中存储的是引用)

list.add(100);//自动装箱Integer

1.3在java中每个不同的集合,底层会对应不同的数据结构,往不同集合中存储元素,等于将数据放到了不同的数据结构中

什么是数据结构?数据存储的结构就是数据结构,不同的数据结构,数据存储方式不同

如:数组、二叉树、链表、哈希表。。。

1.4所有的类和集合接口都在java.util包下

1.5集合的继承结构图

1.6在java中集合分为两大类:

一类是单个方式存储元素:单个方式存储元素,这一类集合种超级父接口:java.util.Collection;

一类是以键值对儿的方式存储元素:以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

迭代 == 遍历

背过!!!!

Collection结构图

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

Map结构图

在这里插入图片描述

总结(所有的实现类):

ArrayList:底层是数组

LinkedList:底层是双向链表

Vector:底层是数组,线程安全的,效率较低,使用较少

HashSet:底层时HashMap,方法HashSet集合中的元素等同于放到HashMap集合key部分

TreeSet:底层时TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分

HashMap:底层是哈希表

Hashtable:底层是哈希表,线程安全,效率较低,使用较少

Properties:是线程安全的,且key和value只能存储字符串String

TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序

List集合存储元素的特点:

有序可重复

有序:存进去的顺序和取出的顺序相同,每一个元素都有下标

可重复:存进去1,可以再存储一个1

Set集合存储元素的特点:

无序不可重复

无序:存进去的顺序和取出的顺序不一定相同

不可重复:存进去1,不能再存储1

SortSet集合存储元素的特点:

首先是无序不可重复,但SortSet集合中的元素是可排序的

可排序:可以按照大小顺序排列

Map集合中的key,就是一个Set集合

在Set集合中存放数据,实际上放到了Map集合的key部分

二、Collection

1.Collection中能存放什么元素?

没有使用“泛型”之前,Collection中可以存储Object的所有子类型

使用了“泛型”之后,Collection中只能存储某个具体的类型

只要是Object的子类型,Collection中就可以存储

集合中不能直接存储基本数据类型,也不能存储java对象,只是存储java对象的内存地址

2.Collection中的常用方法

boolean add(Object e) 向集合中添加元素

int size() 获取集合中元素的个数

void clear() 清空集合

boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false

boolean isEmpty() 判断该集合中元素个数是否为0

Object[ ] toArray() 调用该方法可以把集合转换成数组

例子见CollectionTest01

3.Collection集合的迭代(重点)

以下遍历方式/迭代方式,是所有Collection通用的方式

第一步:通过集合对象的迭代器对象Iterator

Iterator it = c.iterator();

第二步:通过以上获取的迭代器对象开始迭代/遍历集合

以下两个方法是迭代器对象Iterator中的方法:

boolean hasNext() 如果仍有元素可以迭代,则返回true

Object next() 返回迭代的下一个元素

例:

//创建集合对象

Collection c1 = new ArrayList();//ArrayList集合:有序可重复

//添加元素

c1.add(1);

c1.add(2);

c1.add(3);

c1.add(4);

//迭代集合

Iterator it = c1.iterator();

while(it.hasNext()){

System.out.println(it.next());

}

4.Collection集合中的contains方法

boolean contains(Object o):判断集合中是否包含某个对象o,若包含,返回true,反之,返回false

注意:

测试contains方法:存放一个集合中的类型,一定要重写equals方法

//创建集合对象

Collection c = new ArrayList();

//创建用户对象

User u1 = new User(“jack”);

User u2 = new User(“jack”);

//加入集合

c.add(u1);

//判断集合中是否包含u2

//没有重写equals方法之前,结果为false

//重写equals方法之后,结果为true

System.out.println(c.contains(u2));

5.remove方法

remove方法会调用equals方法,s1和s2一样时,删除时一起删除

Collection c2 = new ArrayList();

String s1 = new String(“hello”);

c2.add(s1);

String s2 = new String(“hello”);

c2.remove(s2);

System.out.println(c2.size());//0

迭代:

//创建集合

Collection c = new ArrayList();

//添加元素

c.add(1);

c.add(2);

c.add(3);

//获取迭代器

Iterator it = c.iterator();

while(it.hasNext()){

//编写代码时,next()方法返回值类型必须是Object

//Integer i = it.next();

Object obj = it.next();

System.out.println(obj);

}

注意:集合结构只要发生改变,迭代器必须重新获取

在迭代集合元素过程中,不能调用集合对象的remove方法,删除元素

c.remove(o);迭代过程中不能这样

会出现:java.util.ConcurrentModficationException

//使用迭代器删除可以?

it2.remove();//删除的一定是迭代器指向的当前元素

迭代器删除时,会自动更新迭代器,且更新集合(删除集合中的元素)

6.List接口

集合存储元素特点:有序可重复

有序:List集合中的元素有下标,从0开始,以1递增

可重复:存储一个1,还可以在存储1

List接口的特有的常用的方法:

例子见ListTest01

//在列表指定位置插入指定元素(第一个参数就是下标)

void add(int index,E element)

//根据下标获取元素

Object get(int index)

int indexOf(Object o)

int lastIndexOf(Object o)

Object remove(int index)

//修改指定位置的元素

Object set(int index,Object element)

//通过下标遍历

//list集合特有的遍历方式

for (int i = 0;i < list.size();i++){

Object obj = list.get(i);

System.out.println(obj);

}

ArrayList:

①初始化容量为10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10)

②底层是Object类型的数组Object[]

③构造方法:

new ArrayList();

new ArrayList(20);

④ArrayList集合的扩容:

扩容到原容量的1.5倍

ArrayList集合底层是数组,怎么优化?

尽可能少扩容,因为数组扩容效率较低,建议在使用ArrayList集合时预估计元素个数,给定一个初始化容量

数组的优点:检索效率较高(每个元素占用空间大小相同,内存地址连续,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高)

数组的缺点:随机增删元素的效率较低,数组无法存储大数据量

注意:向数组末尾添加元素时,效率较高

面试题:

这么多集合中,你用哪个集合最多?

答:ArrayList集合

因为向数组末尾添加元素,效率不受影响

另外,我们检索/查找某个元素的操作较多

List list1 = new ArrayList();

List list2 = new ArrayList(100);

Collection c = new HashSet();

c.add(100);

c.add(200);

c.add(900);

c.add(50);

//通过此构造方法可以将HashSet集合转换成List集合

List list3 = new ArrayList©;

for(int i = 0;i < list3.size();i++){

System.out.println(list3.get(i));

}

7.单向链表数据结构

对于链表数据结构,基本的单元节点Node

对于单向链表来说,任何一个节点Node中都有两个属性:

①存储的数据

②下一节点的内存地址
在这里插入图片描述
链表优点:

由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高,在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList

链表缺点:不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头结点开始遍历,直到找到为止,所以LinkedList集合检索/查找的效率较低

ArrayList:把检索发挥到极致

LinkedList:把随机增删发挥到极致

加元素都是往末尾添加,所以ArrayList用的比LinkedList多

LinkedList集合没有初始化容量,最初链表中没有任何元素,first和last引用都是null

LinkedList

①双向链表

②对于链表数据结构来说,随机增删效率较高,检索效率较低

③链表中的元素在空间存储上,内存地址不连续

Vector

①底层是数组

②初始化:10

③如何扩容?

扩容之后是元容量的2倍,10---->20----->40---->80

ArrayList集合扩容特点:

扩容是元容量的1.5倍

④Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率较低,使用较少

⑤怎么讲一个线程不安全的ArrayList集合转换成线程安全的呢?

使用集合工具类:

java.util.Collections;

java.util.Collection是集合接口

java.util.Collections;是集合工具类

三、泛型机制

1.泛型这种语法机制:只在程序编译阶段起作用,只是给编译器参考(运行阶段泛型没用)

2.使用泛型有什么好处?

①集合中存储的元素类型统一

②从集合中取出的元素类型泛型指定的类型,不需要进行大量的“向下转型”

3.泛型的缺点:

导致集合中存储的元素缺乏多样性

大多数业务中,集合中元素的类型还是统一的,所以这种泛型特性被大家所认可

见GenericTest01

4.自动类型推断机制(钻石表达式)

List myList = new ArrayList**<>****();**

自定义泛型

自定义泛型时,<>尖括号中的是一个标识符,随便写

java中常见的是:和

E:是Element单词首字母

T:是Type单词首字母

public class GenericTest03<标识符随便写> {public void doSome(标识符随便写 o){
​        System.out.println(o);}public static void main(String[] args) {**//new对象时指定了了泛型是:String类型****GenericTest03<String> gt = new GenericTest03<>();****//类型不匹配****//gt.doSome();****gt.doSome("abc");****GenericTest03<Integer> gt2 = new GenericTest03<>();****gt2.doSome(123);****//类型不匹配****//gt2.doSome("abc");**}
}

例2:

class MyIterator{

​ public T get(){

​ return null;

​ }

}

foreach

缺点:没有下标

语法:

for(元素类型 变量名 :数组或集合){

System.out.println(变量名);

}

例:

//增强for循环(foreach)

for (int data : arr){

//data就是数组中的元素

System.out.println(data);

}

集合也可使用foreach

HashSet集合:无序不可重复

TreeSet集合:无序不可重复(取出来是按大小排序的,也称为可排序集合)

四、Map

1.Map和Collection没有继承关系

2.Map集合以key和value的方式存储数据:键值对

key和value都是引用数据类型

key和value都是存储对象的内存地址

key起到主导的地位,value是key的一个附属品

3.Map集合中的常用方法(记住)

V put(K key,V value):想Map集合中添加键值对

V get(Object key):通过key获取value

void clear():清空Map集合

boolean containsKey(Object key):判断Map中是否包含某个key

boolean containsValue(Object value):判断Map中是否包含某个value

boolean isEmpty():判断Map集合中元素个数是否为0

Set keySet():获取Map集合所有的key(所有的键是一个set集合)

V remove(Object key):通过key删除键值对

int size():获取Map集合中键值对的个数

Collection values():获取Map集合中所有的value,返回一个Collection

Set<Map.Entry<K,V>> entrySet():将Map集合转换成Set集合

详见MapTest01

4.Map集合的遍历【非常重要】

//第一种方式:获取所有的key,通过遍历key,来遍历value

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

map.put(1,“zhangsan”);

map.put(2,“lisi”);

map.put(3,“wangwu”);

map.put(4,“zhaoliu”);

①迭代器方式

//获取所有的key,所有的key是一个Set集合

Set keys = map.keySet();

//遍历key,通过key获取value

//迭代器可以

Iterator it = keys.iterator();

while(it.hasNext()){

Integer key = it.next();

String value = map.get(key);

System.out.println(key + “=” + value);

}

②foreach方式

//foreach也可以

for (Integer key : keys){

​ System.out.println(key + “=” + map.get(key));

}

//第二种方式:Set<Map.Entryr<K,V>> entrySet()

//以上这个方法是吧Map集合直接全部转换成Set集合

//Set集合中元素的类型是Map.Entry

Set<Map.Entry<Integer,String>> set = map.entrySet();

//遍历Set集合,每一次取出一个Node

//迭代器

Iterator<Map.Entry<Integer,String>> it2 = set.iterator();

while(it2.hasNext()){

Map.Entry<Integer,String> node = it2.next();

Integer key = node.getKey();

String value = node.getValue();

System.out.println(key + “=” + value);

}

//foreach

//这种方式效率较高,因为获取key和value都是直接从node对象中获取的属性值

for (Map.Entry<Integer,String> node : set){

​ System.out.println(node.getKey() + “—>” + node.getValue());

}

五、HashMap集合

1.HashMap集合底层是哈希表/散列表的数据结构

2.哈希表是一个怎样的数据结构?

哈希表是一个数组和单向链表的结合体

数组:在查询方面效率很高,随机增删方面效率很低

单向链表:在随机增删方面效率很高,在查询方面效率很低

哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点

3.HashMap集合底层的源代码

public class HashMap{

//HashMap底层实际上是一个数组(一维数组)

Node<K,V> table;

//静态的内部类HashMap.Node

static class Ndoe<K,V> implements Map.Entry<K,V>{

final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果,hash值通过哈希函数/算法,可以转换成数组的下标)

final K key;//存储到Map集合中的那个key

V value;//存储到Map集合中的那个value

Node<K,V> next;//下一个结点的内存地址

}

}

哈希表/散列表:是一个一维数组,这个数组中每个元素是一个单向链表(数组和链表的结合体)

4.最主要掌握的是

map.put(k,v)

v = map.get(k)

以上两个方法的实现原理,必须掌握

map.put(k,v)的实现原理:

①先将k,v封装到Node对象中

②底层会调用k的hashCode()方法得出hash值,然后通过哈希函数/哈希算法,将哈希值转换成数组下标,下标位置上如果没有任何元素,就把Node添加到这个位置;若下标对应位置上有链表,此时会用k和链表每一个结点中的k进行equals,若所有的equals方法返回false,那么这个新结点将被添加到链表的末尾,若其中有一个equals返回true,,那么这个节点的value就会被覆盖

v = map.get(k)的实现原理:

①先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,若这个位置上什么都没有,则返回null;若这个位置上有单向链表,那么会拿着参数k和单向链表上的每个结点中的k进行equals,若所有equals方法返回false,那么get方法返回null,只要其中有一个结点的k和参数k equals时,返回true,那么此时这个节点的value就是我们要找的value,get方法最终返回这个要找的value

为什么哈希表的堆积增删,以及查询效率都很高?

增删是在链表上完成的

查询也不需要全部扫描,只需要部分扫描

【重点】:通过讲解可以得出HashMap集合的key,辉县后调用两个方法,一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写

5.HashMap集合的key部分特点:

无序,不可重复

为什么无序?因为不一定挂到那个单向链表上

不可重复是怎么保证的?equals方法保证HashMap集合的key不可重复,若重复,value会覆盖

放在HashMap集合key部分的元素其实是放到HashSet集合中,所以HashSet集合中的元素也需要同时重写hashCode() + equals()方法

注意:同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的,但同一个链表上k和k的equals方法肯定返回的是false,都不相等

哈希表HashMap使用不当时无法发挥性能!结社将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成纯单向链表,这种情况称为:散列分布不均匀

什么是散列分布均匀?

假设有100个元素,10个单向链表,那么每个单向链表上有10个结点,这是最好的,是萨列分布均匀;

假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗?有什么问题?

不行,因为这样会导致底层哈希表就称为一维数组了,没有链表的概念了,也是散列分布不均匀

【重点】:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法

HashMap集合的默认初始化容量是16,默认加载因子是0.75,这个默认加载因子是当HashMap集合底层容量达到75%时,数组开始扩容

重点:记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率所必须的

向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后在调用equals方法,equals方法有可能调用,也有可能不调用

put(k,v)什么时候不需要调用equals方法?

​ 数组下标位置上如果是null,equals不需要执行

get(k)什么时候不需要调用equals方法?

​ 数组下标位置上如果是null,不需要调用equals方法

注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写,且equals方法返回如果是true,hashCode()方法的返回值必须一样

​ equals方法返回true,表示两个对象相同,在同一个单向链表上比较,那么对于同一个单向链表上的结点来说,他们的哈希值是相同的,所以hashCode()方法的返回值也应该相同(equals和hashCode可以用Idea生成,但必须同时生成)

终极结论:放在HashMap集合key部分的,以及方法HashSet集合中的元素,需要同时重写hashCode方法和equals方法

对于哈希数据结构来说,如果o1和o2的hash相同,一定是放到同一个单向链表上

如果o1和o2的hash值不同,但由于哈希算法执行结束后转换的数组下标可能相同,此时会发生“哈希碰撞”

6.HashMap和Hashtable的区别

①HashMap集合key部分允许null吗?允许,HashMap集合的key null值只能有一个

②Hashtable的key可以为null吗?

​ Hashtable的key和value都不能为null

​ HashMap集合的key和value都可以为null

③Hashtable方法都带有synchronize:线程安全的

​ 线程安全有其他方案,这个Hashtable对线程的处理导致效率较低,使用较少

④Hashtable和hashMap一样,底层都是哈希表数据结构

HashTable的初始化容量是11,默认加载因子是:0.75f

Hashtable扩容后:原容量*2+1

六、Properties属性类

掌握Properties属性类对象的相关方法

**Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型,**Properties被称为属性类对象,是线程安全的

//创建一个Properties对象
Properties pro = new Properties();
//需要掌握两个方法,一个存,一个取
pro.setProperty("username","root");
pro.setProperty("password","123");

//通过key获取value
String username = pro.getProperty("username");
String password = pro.getProperty("password");

System.out.println(username);
System.out.println(password);

七、TreeSet

1.TreeSet集合底层实际上是一个TreeMap,

​ TreeMap集合底层是一个二叉树,

​ 放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了,

​ TreeSet集合中的元素:无序不可重复,但可以按照元素的大小顺序自动排序,称为可排序集合

例子见TreeSetTest

八、自平衡二叉树数据结构

自平衡二叉树,遵循左小右大原则存放

遍历二叉树时有三种方式:

​ 前序遍历:根左右

​ 中序遍历:左根右

​ 后序遍历:左右根

​ 注意:前中后说的是“根”的位置

TreeSet集合/TreeMap集合采用的是:中序遍历方式

Iterator迭代器采用的是中序遍历方式,左根右
在这里插入图片描述
TreeSet集合中的元素可排序的第二种方式:使用比较器的方式

最终结论:

​ 放大TreeSet或TreeMap集合key部分的元素要想做到排序,包括两种方式:

​ ①放在集合中的元素实现java.lang.Comparable接口

​ ②在构造TreeSet或TreeMap集合时给它穿一个比较器对象

Comparable和Comparator怎么选择?

​ 当比较规则不发生改变时,或当比较规则只有1个时,建议使用Comparable接口

​ 若比较规则有多个,且需要多个比较规则之间频繁切换,建议使用Comparator接口

Comparator接口的设计符合OCP原则

九、Collections工具类

Comparable接口和Comparator接口

十、集合中需要掌握的内容

1.每个集合对象的创建(new)

2.向集合中添加元素

3.从集合中取出某个元素

4.遍历集合

5.主要的集合类:

​ ArrayList

​ LinkedList

​ HashSet(HashMap的key,存储在HashMap集合key的元素需要同时重写hashCode+equals)

​ TreeSet

​ HashMap

​ Properties

​ TreeMap

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值