集合进阶1

集合进阶

概述:

集合是一种容器,用来装数据的,类似于数组,但集合的大小可变,开发中非常常用。

为了满足不同的业务场景需求,Java还提供了很多不同特点的集合。

体系

  1. Collection 单列集合 ,每个元素只含一个值。

  2. Map 双列集合,每个元素含两个值(键值对)。


Collection

List系列集合:添加的元素是有序的、可重复、有索引 ArrayList, LinkedList 有序、可重复、有索引

Set系列集合:添加的元素是无序、不重复、无索引。 HashSet 无序、不重复、无索引; TreeSet按照大小默认升序排序、不重复、无索引

HashSet: LinkedHashSet有序、不重复、无索引


Collection常见方法

所有单列集合都可以使用

// 1.public boolean add(E e):添加元素,添加成功返回true。
// 2.public void clear():清空集合的元素。
// 3.public boolean isEmpty():判断集合是否为空 是空返回true,反之。
// 4.public int size():获取集合的大小。
// 5.public boolean contains(Object obj):判断集合中是否包含某个元素。
// 6.public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个!
// 7.public Object[] toArray():把集合转换成数组

  Collection<String> c = new ArrayList<>(); // 多态写法  Collection是接口,不能直接new对象
        // 1.public boolean add(E e):添加元素,添加成功返回true。
        c.add("java1");
        c.add("java1");
        c.add("java1");
        c.add("java2");
        System.out.println(c);

      //  c.clear();
        System.out.println(c);
// 3
        System.out.println(c.isEmpty());
// 4.
        System.out.println(c.size());
// 5.public boolean contains(Object obj):判断集合中是否包含某个元素。
        System.out.println(c.contains("java1"));
// 6.public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个!
        System.out.println(c.remove("java1"));
        System.out.println(c);
// 7.public Object[] toArray():把集合转换成数组
        Object[] arr = c.toArray();
        System.out.println(Arrays.toString(arr));

        String[] arr2 = c.toArray(new String[c.size()]);
        System.out.println(Arrays.toString(arr2));

拓展

boolean addAll(Collection c);把一个集合的全部数据倒入另一个集合。


        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java1");
        c1.add("java1");
        Collection<String> c2 = new ArrayList<>();
        c2.add("java2");
        c2.add("java2");
        c2.add("java2");
        c2.addAll(c1);  // 就是把c1集合的全部数据全部倒进c2
        System.out.println(c2);
        

Collection遍历方式

x先定义一个Collection集合

  Collection<String> c = new ArrayList<>();
        c.add("孙悟空");
        c.add("唐三藏");
        c.add("猪悟能");
        c.add("沙悟净");
        System.out.println(c);
        //  c = [孙悟空, 唐三藏, 猪悟能, 沙悟净]
1,迭代器

概述:迭代器是用来遍历集合的专用方式(数组没有迭代器),在java中迭代器的代表是iterator

获取方法:

Iterator iterator(); 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素

迭代器常用方法

boolean hasNext() 询问当前位置是否有元素存在,存在返回true,不存在返回false

E next() 获取当前位置的元素,并同时将迭代器对象指向下一个元素处。

 // 使用迭代器遍历元素
        // 1,先从集合对象中获取迭代器对象
        Iterator<String> it = c.iterator();
//        System.out.println(it.next()); // 取出当前元素并移至下一位
//        System.out.println(it.next());
//        System.out.println(it.next());
//        System.out.println(it.next());  // 人工遍历,若有一千元素,非常麻烦
       // System.out.println(it.next()); 越界 异常

        // 使用循环结合迭代器
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele); // 一定要问一次取一次 ,不然易出bug
        }
2,增强for循环

格式:for(元素的数据类型 变量名 :数组或集合){}

  • 增强for可以用来遍历集合或数组

  • 增强for遍历集合,本质就是迭代器遍历集合的简化写法。

     // 用增强for循环遍历集合或数组
            for (String ele : c) {
                System.out.println(ele);
            }
            String[] names = {"古力娜扎","迪丽热巴","西起哈哈","估计旮旯"};
            for (String ele: names){
                System.out.println(ele);
            }
    
3,Lambda表达式遍历集合

概述:得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。

要求:需要使用Collection提供的方法

default void forEach(Consumer action) 结合lambda遍历集合

 // `default void forEach(Consumer action) `  结合lambda遍历集合
// ----------------------------------------------
//        c.forEach(new Consumer() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        });
-------------------------------------------
//        c.forEach((String s) ->{
//                System.out.println(s);
//        });
//--------------------------------------------------
//        c.forEach( s ->{
//            System.out.println(s);
//        });
//------------------------------------------
//        c.forEach( s -> System.out.println(s));
----------------------------------------------------
        c.forEach(  System.out::println);

List集合

List集合的特有方法和遍历方式 其家族都可以使用

特点,特有方法

有序,可重复,索引

List集合因为支持索引,所以多了很多与索引相关的方法。

遍历方式
  1. for循环 因为List集合有索引

  2. 迭代器

  3. 增强for循环

  4. Lambda表达式

    学习集合的3点;1) 基本功能 2)底层原理 (采用的数据结构,即存储,组织数据的方式,其本质就是增删改查的性能的差异以及特点) 3)基于这些,他的应用场景是什么

ArrayList集合的底层原理

基于数组实现的

数组是内存中一块连续的的区域存储

  • 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗费时间相同
  • 删除效率低:可能需要把后面很多的数据进行前移
  • 添加效率极低:可能需要把后面很多数据后移,再添加元素;或要对数组进行扩容
  1. 利用无参构造器创建的集合,会在底层创建一个默认长度是的0的数组
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组
  3. 存满时,会扩容1.5倍
  4. 如果一次添加多个元素,1.5倍还装不下,则新创建的数组长度以实际为准

应用场景:根据索引查询数据,但不频繁增删操作(对数据量不做要求),亦或数据量不是很大(要增删改查操作)

LinkedList集合的底层原理

基于双链表实现的

链表是由一个一个结点组成的,分散存储的

链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址(单向链表)。

特点:链表的特点1:查询慢,无论查询哪个数据都要从头开始找。
链表的特点2:链表增删相对快

双向链表:

结点会记住前后结点的地址 头结点 ,尾结点。

特点:查找数据会先对索引进行比较,看靠前还是靠后,较于单向链表更快。较于ArrayList查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。

因此新增了很多首尾操作的特有方法

方法名称

public void addFirst(E e) // 在该列表开头插入指定的元素
public void addLast(E e) //  将指定的元素追加到此列表的末尾
public E getFirst() // 返回此列表中的第一个元素
public E getLast() // 返回此列表中的最后一个元素
public E removeFirst() // 从此列表中删除并返回第一个元素
public E removeLast() //  从此列表中删除并返回最后一个元

应用场景:可以设计队列 先进先出,后进后出
    // 创建一个队列
        LinkedList<String> queue = new LinkedList<>();
        // 入队
        queue.addLast("第一号");
        queue.addLast("第二号");
        queue.addLast("第三号");
        queue.addLast("第四号");
        System.out.println(queue); // [第一号, 第二号, 第三号, 第四号]
        // 出队
        System.out.println(queue.removeFirst());// 第一号
        System.out.println(queue.removeFirst()); // 第二号
        System.out.println(queue.removeFirst()); //  第三号
        System.out.println(queue); //  [第四号]
可以设计栈:后进先出,先进后出

push压栈 pop出栈 stack 栈 专用API 栈名.push . pop
集合进阶1_第1张图片

Set集合(接口)

特点

无序(只会无序一次):添加数据的顺序和获取出的数据顺序不一致;不重复;无索引

  • HashSet:无序、不重复、无索引 (用的较多)
  • LinkedHashSet:有序、不重复、无索引
  • TreeSet:排序(帮你大小排序)、不重复、无索引

注意:Set要用到的常用方法,基本上都是Collection提供的,自己没有额外新增的

HashSet集合的底层原理
哈希值
  • 就是一个int类型的数值,Java中每个对象都有一个哈希值
  • Java中的所有对象,都可以调用Object类提供的hashCode方法,返回自己的哈希值 public int hashCode();
对象哈希值的特点
  • 同一对象多次调用hashCode方法返回的哈希值是相同的
  • 不同的对象,他们的哈希值一般不同,但也有可能相同(哈希碰撞)
  • int(-21亿多~21亿多)
  1. HashSet集合是基于哈希表实现
  2. 哈希表是一种增删改查数据,性能都较好的数据结构

哈希表

JDK8之前,哈希表=数组+链表

JDK8之后,哈希表=数组+链表+红黑树

哈希表

哈希表=数组+链表

1 创建一个默认长度16的数组,默认加载因子为0.75(扩容指标),数组名table
2 使用元素的哈希值对数组的长度求余计算出应存入的位置
3 判断当前位置是否为null,如果是null直接存入
4 如果不为null,表示有元素,则调用equals方法比较
相等,则不存;不相等,则存入数组
JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
JDK 8开始之后,新元素直接挂在老元素下面

哈希表=数组+链表+红黑树

JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树

HashSet去重复机制

HashSet集合默认不能对内容一样的两个不同对象去重复!
比如内容一样的两个学生对象存入到HashSet集合中去,HashSet集合是不能去重复的! 哈希值不重复

结论:如果希望Set集合认为2个内容一样的对象是重复的,
必须重写对象的hashCode()和equals()方法

  @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height);
    }
  Set<Student> students = new HashSet<>();
        Student s1 = new Student("张三", 23, 165.3);
        Student s2 = new Student("王麻子", 22, 167.3);
        Student s3 = new Student("王麻子", 22, 167.3);
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        Student s4 = new Student("李四", 27, 175.3);
            students.add(s1);
            students.add(s2);
            students.add(s3);
            students.add(s4);
        System.out.println(students);
//结果
2142373836
2142373836
[Student{name='张三', age=23, height=165.3}, Student{name='李四', age=27, height=175.3}, Student{name='王麻子', age=22, height=167.3}]

LinkedHashSet集合的底层原理

● 依然是基于哈希表(数组、链表、红黑树)实现的。
● 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。

缺点: 每个元素记得信息多,更占内存。

TreeSet集合

底层是基于红黑树实现排序的

注意:

● 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序
● 对于字符串类型:默认按照首字符的编号升序排序。
● 对于自定义类型如Student对象,TreeSe默认是无法直接排序的,TreeSet不知道你自定义的排序规则是什么,会报错。

自定义排序规则

TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。
方式一
● 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
方式二
● 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。
public TreeSet(Comparator comparator)

// 方法1
public class Student implements Comparable<Student>{
        private String name;
        private int age;
        private double height;

    @Override
    public int compareTo(Student o) {
        return this.age-o.age;
    }
    
// 方法2
     Set<Student> students = new TreeSet<>(( o1,  o2) ->  Double.compare(o1.getHeight(),o2.getHeight()));

两种方式中,关于返回值的规则:
● 如果认为第一个元素>第二个元素 返回正整数即可。
● 如果认为第一个元素<第二个元素返回负整数即可。
● 如果认为第一个元素=第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。

注意事项:集合的并发修改异常问题

● 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
● 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
Lambda表达式的底层是增强for,也会出bug
怎么保证遍历集合同时删除数据时不出bug?
● 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。

 List<String> list = new ArrayList<>();
        list.add("王麻子");
        list.add("小李子");
        list.add("李海峰");
        list.add("翠花");
        list.add("李爱花");
        list.add("王铁蛋");
        System.out.println(list); // [王麻子, 小李子,李海峰, 翠花, 李爱花, 王铁蛋]

 Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String name = it.next();
            if (name.contains("李")){
//                list.remove(name); // 并发修改异常
                it.remove(); // 会在底层做i--
            }
        }

● 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i – 操作。

  // 使用for循环遍历并删除
        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i);
            if (name.contains("李")){
                list.remove(name);
                i--; // 解决方法
            }
        }

Collection其他相关知识

前置知识:可变参数

● 就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型 … 参数名称;
可变参数的特点和好处
● 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
● 好处:常常用来灵活的接收数据。
可变参数的注意事项:
● 可变参数在方法内部就是一个数组。
● 一个形参列表中可变参数只能有一个
● 可变参数必须放在形参列表的最后面

Collections

● 是一个用来操作集合的工具类
Collections提供的常用静态方法

方法名称

public static boolean addAll(Collection c, T ... elements)
给集合批量添加元素
public static void shuffle(List list)
打乱List集合中的元素顺序

Collections只能支持对List集合进行排序

排序1public static void sort(List list)
对List集合中的元素进行升序排序

注意:本方法可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,制定了比较规则才行。

排序2public static void sort(List list, Comparator c)
对List集合中元素,按照比较器对象指定的规则进行排序

你可能感兴趣的:(java,开发语言,学习)