JavaSe进阶501-516 几种常见的集合 Collection/Contains

开始时间2020-11-14

集合

集合实质也是一个容器,一次可容纳多个对象
集合存的是引用
不能存储基本数据类型,要存基本数据类型会自动装箱int->Integer
有点点像Python的列表(list)和集合(Set)
参考:Python中的列表
Python中的集合
所有的集合类和集合接口都在Java.util.*下

Java集合分为两大类
1.单个方式存储元素,超级父接口 Java.Util.Collection
2.以键值对存储(字典吗?)超级父接口 Java.Util.Map
Map代表映射
迭代可以近似理解为遍历

集合的继承结构图

在这里插入图片描述

List集合存储特点:有序可重复,存储元素有下标
Set集合存储特点:无序不能重复,存储元素无下标
List类似于Python中的列表List
Set类似于Java中的集合Set

在这里插入图片描述
ArrayList底层是数组,非线程安全
LinkedList底层是双向链表
Vector底层用的也是数组,线程安全。所有方法都有synchronized,但是这样效率较低 。使用较少
可以想到StringBuffer是线程安全的,StringBuilder不是
参考StringBuilder和StringBuffer

HashSet,底层是HashMap集合,向HashSet中存储元素,实际是存HashMap里面,HashMap是Hash表数据结构
TreeSet集合,底层是一个TreeMap,TreeMap是一个二叉树
在这里插入图片描述

SortedSet中的元素可以自动按大小排序,但是仍然是无序,不可重复的,没有下标
在这里插入图片描述
列出的只是常用的几种

Map接口

Map集合和Collection集合没有关系
Map由键值对组成
Key和Value存对象的内存地址
有点点字典的感觉,参考Python中的字典
所有集合中的key不可重复,类似于Set
往Set里面加元素,实际是把元素加到了Map函数的Key部
TreeMap底层是一个二叉树

HashMap

HashMap底层是数组+链表\红黑树

Q1:为什么用红黑树

  • 红黑树用来避免DoS攻击,防止链表超长时性能下降,树化应当是偶然情况
    hash表的查找,更新的时间复杂度是0(1),而红黑树的查找,更新的时间复杂度是0(log2 n),TreeNode占用空间也比普通Node的大,如非必要,尽量还是使用链表。
    hash值如果足够随机,则在 hash表内按泊松分布,在负载因子0.75的情况下,长度超过8的链表出现概率是0.00000006,选择8就是为了让树化几率足够小
    树化两个条件:链表长度超过树化阈值;数组容量>=64
  • 退化情况1:在扩容时如果拆分树时,树元素个数<=6则会退化链表,
  • 退化情况2: remove树节点时,若root、root.left、root.right、root.left.left有一个为null,也会退化为链表

Q2:为什么用红黑树而不用其他的树
因为插入删除会锁结构,就会导致性能下降,而红黑树由于其原理上,在插入和删除数据之后,旋转上色的过程比较少,所以锁结构的时间短,性能更佳。
这也是Redis的底层结构,JDK里面的各种Map大多都是采取的红黑树就是这个原理。
平衡二叉树的选择过程是递归的,旋转一次会导致后续的又不平衡了,所以在删除和插入节点时,为了使得该树再次平衡而带来的旋转操作就比较多。
查询性能红黑树略弱于二叉平衡树,但是综合性能还是好一些

Q3:为什么负载因子是0.75而不是0.5或者0.9
因为0.5的话空间利用率过低,扩容会很频繁,而0.9的话涉及到更多的hash冲突,空间利用率高,但是链表比较长,时间效率就降低了。
Q4:索引如何计算。有hashcode为什么还要hash()方法
计算对象的hashCode(),再进行调用HashMap的 hash()方法进行二次哈希,最后&(capacity -1)得到索引,二次hash()是为了综合高位数据,让哈希分布更为均匀
Q5:数组容量为什么是2的n次幂
计算索引时,如果是2的n次幂可以使用位与运算代替取模,效率更高;扩容时hash & oldCap
==0的元素留在原来位置,否则新位置=旧位置+oldCap
但①、②、③都是为了配合容量为2的n次幂时的优化手段,例如Hashtable的容量就不是2的n次幂,并不能说哪种设计更优,应该是设计者综合了各种因素,最终选择了使用2的n次幂作为容量
97%16=1
97^&(16-1)=1
要满足上述,必须让n为2的整数次幂

HashMap的put方法

首次使用创建数组
计算索引【桶下标】
如果桶下标还没被人占用,创建Node占位并返回
如果有人占用:当前如果是TreeNode就走红黑树的添加或更新操作
如果是普通Node,走链表的添加或更新逻辑,如果链表长度超过阈值,走树化逻辑
返回前检查容量是否超过阈值,一旦超过进行扩容【1.8无论带放元素是否有空位都扩容】
【1.7是头插法,1.8是尾插法】

总结 所有实现类

  • Arraylist:底层是数组。
  • LinkedList:底层是双向链表。
  • Vector:底层是数组,线程安全的,效率较低,使用较少。
  • HashSet:底层是HashMap,放到HashSet,集合中的元素等同于放到HashMap集合key部分了。
  • TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到 TreeMap集合 key部分了。
  • HashMap:底层是哈希表。
  • Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少。
  • Properties:是线程安全的,并且 key和value只能存储字符串stringoTreeMap:底层是二叉树。
  • TreeMap集合的 key可以自动按照大小顺序排序

Collection常用接口

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;

/*
collection中
没有使用泛型之前,能存Object所有子类型
使用之后,只能用某一种
还是要记住,集合中不能存基本数据类型,也不能直接存Java对象,只是存对象的内存地址

 常用方法:
 boolean add(Object e) 向集合中添加元素
 int size() 获取集合中元素个数
 void clear() 清空集合
 boolean contains(Object o)判断当前集合是否含有元素o
 boolean remove(Object e)删除某一个元素
 boolean isEmpty()判断集合是否为空
 Object[] toArray() 转换为数组
 */
public class CollectionTest01 {
    public static void main(String[] args) {
        //接口没办法直接new出来的
        Collection c = new ArrayList();
        //测试Collection中常用方法
        //自动装箱了,存的integer地址
        c.add(1200);
        c.add(3.14);
        c.add(true);
        System.out.println(c);
        //获取元素个数
        System.out.println("集合中元素个数" + c.size());
        //判断当前元素是否还有某元素
        System.out.println(c.contains(1200));
        //删除某一个元素
        c.remove(3.14);
        System.out.println(c);
        //转为数组
        Object obj[] = c.toArray();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i]);
        }
        //清空集合
        c.clear();
        System.out.println(c.size());
        //判断集合是否为空
        System.out.println(c.isEmpty());
    }
}

Collection迭代

迭代器在Python中也有类似的地方,参考:Python中的迭代器

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionTest02 {
    public static void main(String[] args) {
     /*
        以下遍历/迭代方式,在map集合中不能用,在所有collection以及其子类中可用
     */
        Collection c = new ArrayList();
        c.add("abc");
        c.add("232");
        c.add(1002);
        //第一步:获取集合对象的迭代器对象iterator
        Iterator iterator = c.iterator();
        //开始迭代,遍历集合
        /*
        boolean hasNext() 如果还有可迭代的对象,返回true
        Object next() 返回迭代的下一个元素
         */

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

在这里插入图片描述
迭代器有点像C语言的指针
上图中集合对象里存的是对象地址,这里仅仅是为了示意方便,迭代器也是一个对象
hasNext判断是否还能迭代,Next拿到当前的对象

再看一个例子

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class CollectionTest03 {
    public static void main(String[] args) {
        Collection c1 = new HashSet();
        c1.add(1);
        c1.add(2);
        c1.add(5);
        //无序不可重复的
        c1.add(1);
        Iterator it = c1.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

输出结果可能按顺序也可能不按顺序

1
2
5
package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class CollectionTest03 {
    public static void main(String[] args) {
        Collection c1 = new ArrayList();
        c1.add(1);
        c1.add(2);
        c1.add(5);
        //有序可重复的
        c1.add(1);
        Iterator it = c1.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

1
2
5
1

contains

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;

/*
contains方法,包含某个对象返回true,否则返回false
 */
public class CollectionTest04 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        String s1 = new String("ac");
        c.add(s1);
        String s2 = new String("ef");
        c.add(s2);
        String x = new String("ac");

        System.out.println(c.contains(x));
        System.out.println(x.equals(s1));
        //这里的结果都是true,因为contains底层调用了equals
        //equals比较的是内容而不是内存地址
    }
}

放在集合中的元素,需要重写equals方法

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

public class CollectionTest05 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        User u1 = new User("Jack");
        User u2 = new User("Jack");
        c.add(u1);
        System.out.println(c.contains(u2));
        //没有重写equals方法之前,结果为false
        //对象之间用equals比较,比的是地址
        //重写后比较的就是name,比的是内容
    }
}

class User {
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    //重写equals,
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof User)) return false;
        User user = (User) o;
        //比较名字了,名字一样就ok
        return Objects.equals(name, user.name);
    }

}

像这种用remove,底层都重写了equals 方法的

package BUPT20201114;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest06 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        String s1 = new String("ac");
        c.add(s1);
        String x = new String("ac");
        c.remove(x);
        System.out.println(c.isEmpty());
    }
}

把x和s1视为同一个对象
同理,上一个题目定义的User对象,只要重写了equals,那么删除u2,u1也没了

结束时间 2020-11-14

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值