集合框架

学习视频

集合的概述

image

image

public class Demo01 {
    public static void main(String[] args) {
        //简单确认一下Collection集合特点
        ArrayList<String> list = new ArrayList<>();
        list.add("java1");
        list.add("java2");
        list.add("java3");
        list.add("java1");
        System.out.println(list);
        System.out.println(list.get(1));

        HashSet<String> set = new HashSet<>();
        set.add("java1");
        set.add("java2");
        set.add("java3");
        set.add("java2");
        set.add("java3");
        System.out.println(set);
    }
}

image

Collection集合

为什么先学Collection集合呢?

Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。

常见方法

image

public class Demo02 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>(); //多态写法
        //1.public boolean add(E e):添加元素,添加成功返回true
        c.add("java1");
        c.add("java1");
        c.add("java2");
        c.add("java3");
        System.out.println(c.add("java2"));
        System.out.println(c);
        //2. public int size():获取集合的大小
        System.out.println(c.size());
        //3. public boolean contains(Object obj);判断集合中是否包含某个元素:
        System.out.println(c.contains("java1"));
        System.out.println(c.contains("Java1"));
        //4. public boolean remove(E e);删除某个元素:如果多个重复元素默认删除前面的第一个
        System.out.println(c.remove("java1"));
        System.out.println(c);
        //5. public Object[] toArray();把集合转换成数组
        //用Object[]接可以尽可能接住多种不同类型的元素
        Object[] arr = c.toArray();
        System.out.println(Arrays.toString(arr));
        //也可以指定类型,但是要确保与集合中元素数据类型一致
        String[] arr2 = c.toArray(new String[c.size()]);
        System.out.println(Arrays.toString(arr2));
        //6. public void clear();清空集合的元素
        c.clear();
        System.out.println(c);
        //7. public boolean isEmpty();判断集合是否为空,是空返回true,反转
        System.out.println(c.isEmpty());
        //把一个集合的全部数据倒入到另一个集合中去
        Collection<String> c1 = new ArrayList<>();
        c1.add("aaa1");
        c1.add("aaa2");
        Collection<String> c2 = new ArrayList<>();
        c2.add("bbb1");
        c2.add("bbb2");
        c1.addAll(c2);
        System.out.println(c1);
        //只是拷贝了一份,c2中的数据并没有消失
        System.out.println(c2);
    }
}

遍历方式

迭代器

image

public class Demo03 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        System.out.println(c);
        //使用迭代器遍历集合
        //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());//出现异常

        //2.使用循环结合迭代器遍历集合
        Iterator<String> it1 = c.iterator();
        while (it1.hasNext()) {
            String ele = it1.next();
            System.out.println(ele);
        }
    }
}
/*
输出结果
[aaa, bbb, ccc, ddd]
aaa
bbb
ccc
ddd
aaa
bbb
ccc
ddd*/
增强for循环遍历
public class Demo04 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        System.out.println(c);
        //使用增强for遍历结合或者数组
        for (String ele : c) {
            System.out.println(ele);
        }
    }
}
/*输出结果
[aaa, bbb, ccc, ddd]
aaa
bbb
ccc
ddd
 */
Lambda表达式

相关视频:原理应用

image

public class Demo05 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        System.out.println(c);
        //default void forEach(Consumer<? super T> action):结合Lambda表达式遍历集合
        //Consumer是个接口,可以用匿名类实现
//        c.forEach(new Consumer<String>() {
//            @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(System.out::println);
    }
}
/*
[aaa, bbb, ccc, ddd]
aaa
bbb
ccc
ddd
 */

集合存储对象的原理

注意:集合存储对象,存的不是对象本身,而是存的对象地址

image

List集合

常见方法

image

public class Demo07 {
    public static void main(String[] args) {
        //1 创建-个ArrayList集合对象(有序,可重复,有索引)
        List<String> list = new ArrayList<>();//一行经典代码
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        System.out.println(list);
        //2 public void add(int index, E element);在某个索引位置插入元素
        list.add(2,"eee");
        System.out.println(list);
        //3 public E remove(int index):根据索引删除元素,返回被删除元素
        System.out.println(list.remove(2));
        System.out.println(list);
        //4 public E get(int index):返回集合中指定位置的元素
        System.out.println(list.get(3));
        //5 public E set(int index, E element):修改索引值,并返回原来元素
        System.out.println(list.set(3,"fff"));
        System.out.println(list);
    }
}
/*
[aaa, bbb, ccc, ddd]
[aaa, bbb, eee, ccc, ddd]
eee
[aaa, bbb, ccc, ddd]
ddd
ddd
[aaa, bbb, ccc, fff]
 */
遍历方式

因为是支持索引,所有可以用for循环遍历。当然Collection的三种遍历方式也是支持的

public class Demo08 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();//一行经典代码
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        list.add("ddd");
        //快捷键:list.fori + 回车
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}
/*
aaa
bbb
ccc
ddd
 */
ArrayList集合的底层原理

底层采用的数据结构不同(存储、组织数据的方式),应用场景不同。

image

image

image

image

LinkedList集合的底层原理

image

应用场景

  • 用来实现队列
  • 用来实现栈
public class Demo11 {
    public static void main(String[] args) {
        //快捷键alt + 回车,自动添加对象引用
        LinkedList<String> queue = new LinkedList<>();
        //入队
        queue.addLast("1");
        queue.addLast("2");
        queue.addLast("3");
        queue.addLast("4");
        System.out.println(queue);
        //出队
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);
        System.out.println("-------------------");
        // 创建栈,其实push和pop就是用addFirst和removeFirst实现的
        LinkedList<String> stack = new LinkedList<>();
        stack.push("1");
        stack.push("2");
        stack.push("3");
        System.out.println(stack);
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack);
    }
}
/*
[1, 2, 3, 4]
1
2
3
[4]
-------------------
[3, 2, 1]
3
2
[1]
 */

Set集合

概述

Set系列集合特点:无序:添加数据的顺序和获取出的数据顺序不一致;不重复;无索引;

  • Hashset:无序、不重复、无索引。
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序、不重复、无索引。

注意:

Set要用到的常用方法,基本上就是Collection提供的!!
自己几乎没有额外新增一些常用功能!

public class Demo12 {
    public static void main(String[] args) {
//        Set<Integer> set = new HashSet<>();//经典代码
//        Set<Integer> set = new LinkedHashSet<>();
        Set<Integer> set = new TreeSet<>();//默认升序
        set.add(111);
        set.add(555);
        set.add(333);
        set.add(333);
        set.add(555);
        set.add(444);
        System.out.println(set);
    }
}
/*
[555, 444, 333, 111]
[111, 555, 333, 444]
[111, 333, 444, 555]
 */
HashSet集合的底层原理

哈希值

  • 就是一个int类型的数值,Java中每个对象都有一个哈希值。
  • Java中的所有对象,都可以调用0bejct类提供的hashCode方法,返回该对象自己的哈希值。
    public int hashCode():返回对象的哈希码值。

对象哈希值的特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
  • 不同的对象,它们的哈希值一般不相同,但但也有可能会相同(哈希碰撞)

Hashset集合的底层原理

  • 基于哈希表实现。
  • 哈希表是一种增删改查数据,性能都较好的数据结构。

哈希表

  • JDK8之前,哈希表=数组+链表
  • JDK8开始,哈希表=数组+链表+红黑树

image

当数据过多的时候可以使用考虑结合红黑树,注意这里树中的定位是依据哈希值而不是内容

image

HashSet集合去重复的机制

因为HashSet是依据哈希值构建的,如果要实现集合中依据内容实现去重复,就必须重写hashCode()和equals()方法,因为在构建HashSet集合中就是根据这两个方法分别确定哈希值和哈希值是否相等的。

//Student类
public class Student {
    private String name;
    private int age;
    private double height;

    public Student(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    //只有两个对象内容一样就返回同样的哈希值
    @Override
    public int hashCode() {
        return Objects.hash(this.name, this.age, this.height);
    }
    //只有两个对象内容一样就返回true
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return this.age == student.age && 0 == Double.compare(this.height, student.height) && this.name.equals(student.name);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Set<Student> students = new HashSet<>();
        Student student1 = new Student("李四", 18, 175);
        Student student2 = new Student("李四", 18, 175);
        Student student3 = new Student("王五", 81, 165);
        Student student4 = new Student("赵六", 30, 180);
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());
        students.add(student1);
        students.add(student2);
        students.add(student3);
        students.add(student4);
        System.out.println(students);
    }
}
/*
1889669274
1889669274
[Student{name='王五', age=81, height=165.0}, Student{name='李四', age=18, height=175.0}, Student{name='赵六', age=30, height=180.0}]
 */
LinkedHashSet底层原理
  • 依然是基于哈希表(数组、链表、红黑树)实现的。
  • 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。

image

TreeSet底层原理
  • 特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序)
  • 底层是基于红黑树实现的排序

注意:

  • 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序。
  • 对于字符串类型:默认按照首字符的编号升序排序。
  • 对于自定义类型如Student对象,Treeset默认是无法直接排序的,会报错

自定义的排序规则

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

两种方式中,关于返回值的规则:

假设要升序排序,如果要降序就互换一下

  • 如果认为第一个元素 > 第二个元素 返回正整数即可。
  • 如果认为第一个元素<第二个元素返回负整数即可。
  • 如果认为第一个元素=第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
  • 原理:当该方法返回值大于0的时候就将数组前一个数和后一个数交换
  • 目前规律
    • 升序:o2 - o1
    • 降序:o1 - o2

注意:

  • 如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。

  • 如果自定判断依据数值重复时会去重

//学生类
public class Student implements Comparable<Student> {
    public String name;
    public int age;
    public double height;

    @Override
    public int compareTo(Student o) {
        /*
        如果认为第一个元素 > 第二个元素 返回正整数即可。
        如果认为第一个元素<第二个元素返回负整数即可。
        如果认为第一个元素=第二个元素返回0即可
         */
        //需求:按照年龄升序排序
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }

    public Student(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        Set<Integer> s1 = new TreeSet<>();
        s1.add(3);
        s1.add(5);
        s1.add(2);
        s1.add(2);
        s1.add(8);
        System.out.println(s1);
        //如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
        Set<Student> s2 = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //需求:按照身高升序排序
                //注意,不能直接见,以为是double类型的,要用到Double.compare()方法
                return Double.compare(o1.height, o2.height);
            }
        });
        s2.add(new Student("张三", 18, 168));
        s2.add(new Student("李四", 20, 170));
        s2.add(new Student("王五", 20, 175));
        s2.add(new Student("赵六", 18, 170));
        System.out.println(s2);
    }
}
/*
[2, 3, 5, 8]
[Student{name='张三', age=18, height=168.0}, Student{name='李四', age=20, height=170.0}]
[Student{name='张三', age=18, height=168.0}, Student{name='李四', age=20, height=170.0}, Student{name='王五', age=20, height=175.0}]
 */

集合并发修改异常

  • 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
  • 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误

怎么保证遍历集合同时删除数据时不出bug?

  • 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可
  • 如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但除元素后做i --操作。
public class Demo19 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(111);
        list.add(222);
        list.add(333);
        list.add(444);
        list.add(555);
        System.out.println(list);

        //需求删除“333”
        Iterator<Integer> it = list.iterator();
//        while (it.hasNext()) {
//            int t = it.next().intValue();
//            if (t == 333) {
////                list.remove((Integer) t);//并发修改异常的错误
//                it.remove();//删除迭代器当前遍历到的数据,每删除一个数据后,相当于也在底层做了i--
//            }
//        }
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i) == 333) {
                list.remove(i);
                i--;
            }
        }
        //对于增强for循环和lambda表达式的循环,只是对迭代器遍历集合的简化写法,所以也会报错
        System.out.println(list);

    }
}
/*
[111, 222, 333, 444, 555]
[111, 222, 444, 555]
 */

Collections类

  • 是一个用来操作集合的工具类

Collections提供的常用静态方法

image

public class Demo02 {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        Collections.addAll(names, "张三", "李四", "王五");
        System.out.println(names);

        //打乱List集合中的元素顺序,应用“洗牌”
        Collections.shuffle(names);
        System.out.println(names);

        //如果是自定义对象,需要进行自定义排序规则,具体操作与TreeSet底层原理所讲一样
        Collections.sort(names);
        System.out.println(names);
    }
}
/*
[张三, 李四, 王五]
[王五, 李四, 张三]
[张三, 李四, 王五]
 */

image

综合案例

用斗地主的案例很好的体现了面向对象的思想,视频

Map集合

认识Map集合

  • Map集合称为双列集合,格式:{key1=value1, key2=value2, key3=value3, …},一次需要存一对数据做为一个元素

  • Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entny对象,Map集合也被叫做“键值对集合“

  • Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值

  • 应用场景:需要存储一一对应的数据时,就可以考虑使用Map集合来做

image

public class Demo05 {
    public static void main(String[] args) {
//        Map<Integer, String> map = new HashMap<>();
//        Map<Integer, String> map = new LinkedHashMap<>();
        Map<Integer, String> map = new TreeMap<>();
        //注意,这里用到是put
        map.put(3, "爪洼");
        map.put(5, "派森");
        map.put(5, "C嘎嘎");
        map.put(2, "里纽克斯");
        System.out.println(map);
    }
}
/*
{2=里纽克斯, 3=爪洼, 5=C嘎嘎}
{3=爪洼, 5=C嘎嘎, 2=里纽克斯}
{2=里纽克斯, 3=爪洼, 5=C嘎嘎}
 */

Map的常用方法

  • Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。

image

  • 拓展,同样有倒入方法,putAll();方法有很多,可查帮助文档

Map集合的遍历方式

  • 键找值:先获取Map集合全部的键,再通过遍历键来找值

    public class Demo07 {
        public static void main(String[] args) {
            Map<Integer, String> map = new HashMap<>();
            map.put(3, "爪洼");
            map.put(5, "派森");
            map.put(5, "C嘎嘎");
            map.put(2, "里纽克斯");
            System.out.println(map);
            //先获取Map集合全部的键
            Set<Integer> key = map.keySet();
            System.out.println(key);
            //再通过遍历键来找值
            for (Integer integer : key) {
                System.out.println(integer + "==>" + map.get(integer));
            }
        }
    }
    /*
    {2=里纽克斯, 3=爪洼, 5=C嘎嘎}
    [2, 3, 5]
    2==>里纽克斯
    3==>爪洼
    5==>C嘎嘎
     */
    
  • 键值对:把“键值对“看成一个整体进行遍历(难度较大)

    public class Demo08 {
        public static void main(String[] args) {
            Map<Integer, String> map = new HashMap<>();
            map.put(3, "爪洼");
            map.put(5, "派森");
            map.put(5, "C嘎嘎");
            map.put(2, "里纽克斯");
            System.out.println(map);
            //因为键值对整体不能看成一个单独的数据类型,Java提供了entrySet,进行转换
            //调用Map集合提供的entrySet方法,把Map集合转换成键值对类型的Set集合
            Set<Map.Entry<Integer, String>> entries = map.entrySet();
            System.out.println(entries);
            for (Map.Entry<Integer, String> entry : entries) {
                System.out.println(entry.getKey() + "==>" + entry.getValue());
            }
        }
    }
    /*
        {2=里纽克斯, 3=爪洼, 5=C嘎嘎}
    [2=里纽克斯, 3=爪洼, 5=C嘎嘎]
    2==>里纽克斯
    3==>爪洼
    5==>C嘎嘎
         */
    
  • Lambda表达式:JDK 1.8开始之后的新技术(非常的简单)

    public class Demo09 {
        public static void main(String[] args) {
            Map<Integer, String> map = new HashMap<>();
            map.put(3, "爪洼");
            map.put(5, "派森");
            map.put(5, "C嘎嘎");
            map.put(2, "里纽克斯");
            System.out.println(map);
    
            map.forEach((k, v) -> {
                System.out.println(k + "==>" + v);
            });
        }
    }
    /*
    {2=里纽克斯, 3=爪洼, 5=C嘎嘎}
    2==>里纽克斯
    3==>爪洼
    5==>C嘎嘎
     */
    

HashMap集合底层原理

  • HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的。

  • 实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。其实这里键和值是存在一起的Entry对象中

    image

  • HashMap集合是一种增删改查数据,性能都较好的集合

  • 但是它是无序,不能重复,没有索引支持的(由键决定特点)

  • HashMap的键依赖hashcode方法和equals方法保证键的唯

  • 如果键存储的是自定义类型的对象,可以通过重写hashcode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。

具体实现和HashSet实现大同小异

LinkedHashMap集合底层原理

  • 底层数据结构依然是基于哈希表实现的,只是每个键值对元索又额外的多了一个双链表的机制记录元索顺序(保证有序)。
  • 实际上:原来学习的LinkedHashset集合的底层原理就是LinkedHashMap。

TreeMap集合底层原理

  • 特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
  • 原理:TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序。

TreeMap集合同样也支持两种方式来指定排序规则

  • 让类实现Comparable接口,重写比较规则。
  • TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。

集合的嵌套

集合可以嵌套集合使用

public class Demo14 {
    public static void main(String[] args) {
        Map<String, List<String>> map = new HashMap<>();
        List<String> cities1 = new ArrayList<>();
        Collections.addAll(cities1, "南京市", "苏州市", "无锡市", "常州市");
        map.put("江苏省", cities1);

        List<String> cities2 = new ArrayList<>();
        Collections.addAll(cities2, "武汉市", "孝感市", "十堰市", "鄂州市");
        map.put("湖北省", cities2);

        List<String> cities3 = new ArrayList<>();
        Collections.addAll(cities3, "石家庄市", "唐山市", "邢台市", "保定市");
        map.put("河北省", cities3);

        System.out.println(map);

        List<String> cities = map.get("河北省");
        for (String city : cities) {
            System.out.println(city);
        }
    }
}
/*
{江苏省=[南京市, 苏州市, 无锡市, 常州市], 湖北省=[武汉市, 孝感市, 十堰市, 鄂州市], 河北省=[石家庄市, 唐山市, 邢台市, 保定市]}
石家庄市
唐山市
邢台市
保定市
 */
posted @ 2025-01-18 22:56  韩熙隐ario  阅读(19)  评论(0)    收藏  举报