1.集合框架的使用

1.1目录

1.为什么使用集合?

2.集合架构有哪些?

3.List集合

4.ArrayList集合

5.LinkedList集合

6.Set集合

7.HashSet集合

8.TreeSet集合

1.2为什么使用集合?

1.我们原来学习过数组,但是数组有缺点(一旦声明之后,长度就不可变了)同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

2.我们是否可以定义一个长度改变的容器。---当然可以。

3.手撕可变长度的容器。

package com.qy151.test2;

import java.util.Arrays;

/**
 * @unthor : YSH
 * @date : 20:17 2022/4/15
 */
public class MyArray {
    private Object[] arr;//声明一个Object类型的数组
    private int size;//表示数组的下标 因为他们是类成员变量 在创建对象时都有默认值。

    public MyArray(){//无参构造函数
        this(3);//本类中其他的构造函数 this本类的对象。 如果在构造方法中this()表示调用本类的其他构造函数
    }

    public MyArray(int initSize){//有参构造函数--表示数组的长度
        if (initSize<0){//长度不合法
            throw new RuntimeException("抱歉,数组长度有误,请重新定义数组长度");
        }
        arr=new Object[initSize];
    }

    //把元素o放入数组arr
    public void addData(Object o){
         //判断你的数组是否已满
        if(size>=arr.length){
            //扩容--(1)容器的长度变长 (2)把原来容器中的元素复制到新的容器中
            Object[] newArr = Arrays.copyOf(arr, size * 2);
            arr=newArr;
        }
        arr[size]=o;
        size++;
    }

    //根据下标获取数组中的元素。
    public Object getData(int index){
        if(index>=size){
            throw new ArrayIndexOutOfBoundsException("下标越界");
        }
        Object o = arr[index];
        return o;
    }
}

我们自己可以手写一个可变的容器,那么别人也可以手写可变的容器。

java官网 基于数组 根据不同的数据结构 创建了多个类 而这些类统称 为集合框架。

以后 我们在说集合框架时 就表示多个类。

1.3集合的架构

1.4 List集合-ArrayList

1.4.0 ArrayList的特点

  • 实现了List接口
  • 可以动态扩容(我们只管存,长度不够,底层会自动的扩容)
  • 通过下标可以快速访问数据
  • 查找快,插入删除慢
  • ArrayList底层是数组,对数组做了封装
  • 可以存储任意类型的数据,包括null
  • 数据按照存储次序排列
  • 数据可以重复
  • 多线程访问时不安全

 1.4.1 创建集合对象

        //创建一个集合对象,如果没有指定集合容器的长度默认为10
        List list = new ArrayList();
        List list = new ArrayList(15);

1.4.2 添加的操作

        //添加操作
        list.add("java01");
        list.add("java02");
        list.add("java03");
        list.add(66.66);
        list.add(new Date());
        list.add("迪迦来了");
        //下标为2的位置添加集合元素,并把后面的元素进行移位
        list.add(2,"我是迪迦");
        //打印一个对象时默认使用的为toString()
        System.out.println(list);

1.4.3 删除的操作

        //删除操作
        list.remove(2);//移除下标为2的元素
        System.out.println(list);
        //list.clear();//清空集合中的元素.
        //System.out.println(list);

1.4.4修改的操作

        //修改操作
        //修改下标为1的集合元素的值
        list.set(1,"刘德华");
        System.out.println(list);

1.4.5查询的操作

        List list1 = new ArrayList();
        list1.add("java01");
        list1.add("java02");
        System.out.println(list1);

        //查询的方法
        //根据下标获取元素
        Object o = list1.get(1);
        System.out.println(o);

        //获取集合元素中的个数
        Object o1 = list1.size();
        System.out.println(o1);

        //判断元素是否在集合中
        boolean f = list1.contains("java05");
        System.out.println(f);

        //查询元素在集合中第一次出现的位置
        int index = list1.indexOf("java01");
        System.out.println(index);

        //遍历集合中的元素 for循环
        for (int i=0 ;i<list1.size();i++){
            Object ol = list1.get(i);
            System.out.println(ol);
        }
        System.out.println("====");
        //for each 遍历集合中的元素
        for (Object o2 : list1){
            System.out.println(o2);
        }
    }
}

1.4.6 ArrayList底层源码

从构造方法来入手。new ArrayList(22) 底层声明了一个Object类型的数组 名字elementData
  Object[] elementData

  public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) { //大于0
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) { //等于初始化为一个空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else { //抛出一个异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

==========add("java01")======E理解为Object类型================  
   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 扩容 
        elementData[size++] = e;  //把元素赋值给数组的相应位置
        return true;
    }
==========indexOf("java02") 判断元素在集合中第一次的位置=============
     public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i])) //和数组中的每个元素内容进行比对
                    return i;  //返回元素在集合中位置
        }
        return -1;
    }   

===========size() 请求数组的长度======================
 public int size() {
        return size;
    }   

============contain("java05")判断元素是否在集合中==============
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
===============get(1) 获取指定位置的元素========
   public E get(int index) {
        rangeCheck(index); //判断指定的位置是否合法 

        return elementData(index);
    }  

    E elementData(int index) {
        return (E) elementData[index];
    } 

============toString() 为什么不打印对象的引用地址 
    [java01, java02, java03, java02]因为重写了Object里面的toString方法。
    
 public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }   
    
    
通过对ArrayList方法的底层代码分析:底层就是对数组的操作。
    ArrayList的底层就是基于数组实现的。

1.5 LinkedList

它是一个链表结构。具有List的特征,底层以链表结构实现,可以进行头尾元素的添加删除。

1.5.1 添加操作

        //添加
        linkedList.add("喜羊羊");//追加到尾部
        linkedList.addFirst("美羊羊");//追加到头部
        linkedList.addLast("懒羊羊");//追加到尾部
        linkedList.add(2,6666);//在指定的位置添加元素
        System.out.println(linkedList);
        linkedList.addFirst("沸羊羊");//追加到头部
        linkedList.addLast("暖羊羊");//追加到尾部
        System.out.println(linkedList);

1.5.2 删除操作

        //删除操作
        linkedList.removeFirst();//移除头部元素
        System.out.println(linkedList);

        linkedList.remove(2);//移除指定位置的元素
        System.out.println(linkedList);


        linkedList.removeLast();//移除尾部元素
        System.out.println(linkedList);

        //linkedList.clear();//清空元素
        //System.out.println(linkedList);

1.5.3 修改操作

        //修改操作
        linkedList.set(1,"灰太狼");//修改指定位置的元素
        System.out.println(linkedList);

1.5.4查询操作

        //查询操作
        int size = linkedList.size();//求长度

        boolean empty = linkedList.isEmpty();//查询是否为空

        boolean b = linkedList.contains("java01");//判断元素是否在集合中

        Object o = linkedList.get(1);//根据下标获取指定位置的元素

        Object first = linkedList.getFirst();//获取第一个元素
        System.out.println(first);

        Object last = linkedList.getLast();//获取最后一个元素
        System.out.println(last);

1.5.5 LInkedList的底层源码

1.凡是查询源码,我们都是从类对的构造方法入手:

     /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

该类的构造方法内是空的,没有任何的代码。但是该类中有三个属性。

          transient int size = 0; //索引
           
          transient Node<E> first ; //第一个元素对象

          transient Node<E> last ; //最后一个元素对象

================ add的源码=====E:理解为Object类型==========================。

   public boolean add(E e) {
        linkLast(e);
        return true;
    }
   void linkLast(E e) {
        final Node<E> l = last;
        //上一个节点   数据  下一个节点
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

 ==================Node的源码 内部类======================================= 

 private static class Node<E> { //<E>泛型--object
        E item; //数据
        Node<E> next; //下一个节点
        Node<E> prev; //上一个节点

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

 

 ==================== get(1)-----获取元素========================

 public E get(int index) {
        checkElementIndex(index); //检查index下标是否正确。
        return node(index).item;  //李四Node对象
    }
 ========================node(index)=============================
 Node<E> node(int index) {
        //>> 位运算二进制运算 ----- size >> 1 一半的意思size/2
        if (index < (size >> 1)) { //前半部分
            Node<E> x = first; 
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {  //后半部分
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    

 1.6Set集合

1.6.1Set集合(接口)的特点

  • Set接口是无序的
  • Set接口中的数据不允许重复
  • Set接口无法通过下标访问数据
  • 查找慢,插入删除快(底层数据结构是哈希表和红黑树)
  • Set集合使用equals()和hashCode()方法实现元素去重

1.7 HashSet集合

1.7.1HashSet的特点

  • HashSet是Set接口的实现类 
  • 线程不安全

1.7.2创建HashSet对象

public class Test {
    public static void main(String[] args) {
        //如果没有指定容器的大小 默认为16 负载因子为0.75
        HashSet hashSet = new HashSet(16);//初始容器的大小
        
        
        //loadFactor:-->0.7f 表示负载因子 当前空间使用70%时 要求扩容
        HashSet hashSet1 = new HashSet(16,0.7f);
    }
}

1.7.3添加操作

        //添加操作
        hashSet.add("周杰伦");
        hashSet.add("刘德华");
        hashSet.add("陈奕迅");
        hashSet.add("许嵩");
        hashSet.add("周杰伦");
        System.out.println(hashSet);
        
        
        HashSet set2=new HashSet();
        set2.add("刘德华");
        set2.add("张学友");
        set2.add("黎明");

        hashSet.addAll(set2); //把set2中的每个元素添加到hashset中
        System.out.println(hashSet); //元素不能重复 而且无序

运行结果:

1.7.4删除操作

        //删除操作
        hashSet.remove("黎明");//删除元素"黎明"
        //hashSet.clear();//清空容器集合
        System.out.println(hashSet);

运行结果:

1.7.5查询操作

        //查询操作
        boolean empty = hashSet.isEmpty();//判断是否为空
        System.out.println(empty);

        boolean b = hashSet.contains("刘德华");//判断元素是否在集合中
        System.out.println(b);

运行结果:

1.7.6遍历

foreach遍历:

        //foreach遍历
        for (Object o : hashSet){
            System.out.println(o);
        }

迭代器遍历:

迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。

HashSet类中没有提供根据集合索引获取索引对应的值的⽅法,

因此遍历HashSet时需要使⽤Iterator迭代器。Iterator的主要⽅法如下

返回类型

方法

描述

boolean

hasNext()

如果有元素可迭代

Object

next()

返回迭代的下⼀个元素

        //迭代器遍历
        Iterator iterator = hashSet.iterator();//创建迭代器对象 有序:有下标
        while (iterator.hasNext()) {//判断是否指针能够移动
            Object next = iterator.next();//指针移动并获取当前的元素
            System.out.println(next);

 运行结果:

1.7.7HashSet的源码

    从构造函数说起:
    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

在创建一个HashSet的对象时,底层创建的是HashMap。我们说hashset的底层原理时,我们就在后HashMap的原理就行。 讲HashMap时给大家说原理。 

1.8TreeSet集合

1.8.1TreeSet的特点

TreeSet中的方法和HashSet中的方法一模一样 只是他们的实现不一样。
TreeSet 基于TreeMap 实现。TreeSet可以实现有序集合,但是有序性需要通过比较器实现。

  • 有序
  • 不重复
  • 添加、删除、判断元素存在性效率比较高
  • 线程不安全

1.8.2TreeSet对元素进行排序的方式:

1) 如果是基本数据类型和String类型,无需其它操作,可以直接进行排序。

2) 对象类型元素排序,需要实现Comparable接口,并覆盖其compareTo方法。

3) 自己定义实现了Comparator接口的排序类,并将其传给TreeSet,实现自定义的排序规则。

 1.8.2存储String类型

TreeSet treeSet=new TreeSet();
        treeSet.add("java05");
        treeSet.add("java03");
        treeSet.add("java04");
        treeSet.add("java01");
        treeSet.add("java02");
        treeSet.add("java04");

        System.out.println(treeSet);

1.8.3存储一个对象类型

public class Test1 {
    public static void main(String[] args) {
        TreeSet treeSet=new TreeSet(); //TreeSet不允许重复元素
        treeSet.add(new Student("喜羊羊",10));
        treeSet.add(new Student("美羊羊",11));
        treeSet.add(new Student("慢羊羊",66));
        treeSet.add(new Student("灰太狼",25));

        System.out.println(treeSet);
    }
}

通过运行我们发现出现如下的错误:

发现: TreeSet中的元素必须实现Comparable接口 方可放入TreeSet

解决办法有两个:

第一个: 让你的类实现Comparable接口

package com.qy151.test3;

import java.util.TreeSet;

/**
 * @unthor : YSH
 * @date : 17:09 2022/4/17
 */
public class Test1 {
    public static void main(String[] args) {
        TreeSet treeSet=new TreeSet(); //TreeSet不允许重复元素
        treeSet.add(new Student("喜羊羊",10));
        treeSet.add(new Student("美羊羊",11));
        treeSet.add(new Student("慢羊羊",66));
        treeSet.add(new Student("灰太狼",25));

        System.out.println(treeSet);
    }
}
class Student implements Comparable{
    private String name;
    private Integer age;

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

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    //排序:---返回如果大于0 表示当前元素比o大  如果返回-1 当前添加的元素比o小  返回0表示相同元素。
    @Override
    public int compareTo(Object o) {
        Student student= (Student) o;
        System.out.println(this+"===================>"+o);

        if(this.age>student.age){
            return 1;
        }
        if(this.age<student.age){
            return -1;
        }

        return 0;
    }
}

第二种: 在创建TreeSet时指定排序的对象。

我们之前 创建过TreeSet对象。
  TreeSet treeSet=new TreeSet(); 但是在创建对象时 并没有为其指定排序得规则,那么就要求该集合得元素有排序规则。 如果元素得类已经创建完成,不能修改该类得源码,这时我们又想把该类得对象放入得TreeSet容器中。 这时就需要你在创建TreeSet时指定排序得规则。 

public class Test {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet(new MyComparator());//为TreeSet指定了排序规则
        treeSet.add(new Student("张三",18));
        treeSet.add(new Student("李四",17));
        treeSet.add(new Student("王五",20));
        treeSet.add(new Student("赵六",19));
        System.out.println(treeSet);
    }
}



public class MyComparator implements Comparator {//需要重写接口中的抽象方法
    @Override
    public int compare(Object o1, Object o2) {
        System.out.println("=====调用了compare方法=====");
        //先判断两个要比较的数据是否是相同类型的
        if ((o1 instanceof Student) && (o2 instanceof Student)) ;
        {
            Student s1 = (Student) o1;
            Student s2 = (Student) o2;
            System.out.println(o1 + "======" + o2);
            if (s1.getAge() > s2.getAge()) {
                return 1;
            } else if (s1.getAge() < s2.getAge()) {
                return -1;
            } else {
                return 0;
            }
        }
    }
}

总结:

 TreeSet : 1.保证数据逻辑有序 2.不重复

 实现有序的两种方式

 a. 自定义类型实现Comparable接口,实现里面的compareTo方法

 b. 自定义一个比较器对象实现Comparator接口,实现里面的commpare方法

1.9Map接口及其实现类

1.9.1Map接口特点

  • 以键值对方式存储数据(Collection是单值集合)
  • 键不能重复,键重复时,后面的数据会覆盖前面的数据
  • 可以存储null
  • 键值对数据无序

map中得每个元素属于键值对模式。 如果往map中添加元素时 需要添加key 和 value. 它也属于一个接口,该接口常见得实现类有: HashMap.

1.9.2如何创建Map对象

         //默认初始化大小为16 负载因子为0.75
         Map map=new HashMap();
         //初始化大小
         Map map2=new HashMap(16);
         //初始化大小  负载因子
         Map map3=new HashMap(16,0.78f);

1.9.3HashMap实现类

HashMap实现了Map接口,拥有Map接口的基本特点。HashMap线程不安全,效率高。HashMap的底层是由哈希表、链表加红黑树构成的。

 1.9.4添加操作

public class MapTest {
    public static void main(String[] args) {
        //默认初始化大小为16 负载因子为0.75
        Map map = new HashMap();
        //添加操作 key:name/age 相当于属性   value:相当于属性值
        map.put("name","张三");
        map.put("age",18);
        map.put("sex","男");
        map.put("name","李四");//因为key不能重复,所以后者会把前者覆盖

        Map m1 = new HashMap();
        m1.put("k1","v1");
        m1.put("k2","v2");
        map.putAll(m1);//把m1中的每个元素添加到map中

        map.putIfAbsent("age1",20);//如果指定得到的key存在,则不放入map中,如果不存在则放入map中

        System.out.println(map);
    }
}

运行结果:

1.9.5删除操作

        //删除操作
        map.remove("age1");//根据指定的key移除元素
        map.remove("sex","男");//根据指定的key,value移除元素
        //map.clear();//清空map容器
        System.out.println(map);

1.9.6修改操作

        //修改操作
        map.replace("age",20);//修改元素的值
        System.out.println(map);

运行结果:

1.9.7查询操作

public class MapTest1 {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("k1","v1");
        map.put("k4","v4");
        map.put("k2","v2");
        map.put("k3","v3");
        //查询操作
        boolean k2 = map.containsKey("k2");//判断map是否存在指定的key
        System.out.println(k2);

        Object k3 = map.get("k3");//根据指定的key获取相应的value值
        System.out.println(k3);

        Set keys = map.keySet();//返回该map中所有得key
        System.out.println(keys);

        //遍历map.
        for(Object k:keys){
            Object value= map.get(k);//
            System.out.println(k+"================>"+value);
        }
    }
}

运行结果:

1.9.8HashMap的底层原理

JDK1.7 和 JDK1.8他们是有区别得。
   JDK1.7使用得数据结构: 数组+链表  而且链表插入模式为头部插入(造成死循环)。
   jdk1.8使用得数据结构: 数组+链表+红黑树 而且链表得插入模式为尾部插入。
   
从构造函数入口:
  /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
   public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

总结:

HashMap的put()和get()的实现

1) map.put(key,value)实现原理

第一步:首先将k,v封装到Node对象当中(节点)。

第二步:它的底层会调用K的hashCode()方法得出hash值。

第三步:通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

2) map.get(key) 实现原理

第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。

第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

JDK1.8 HashMap原理
Hashmap得原理,存储元素使用得put(key,value),根据key得hash计算出相应得哈希值,根据相应得算法求出该元素在数组中得位置, 如果求出得哈希值相同,则称为哈希冲突,会根据equals来判断元素是否一致,如果equals不同,则存入单向链表上, 如果哈希碰撞得个数超过8个,则把链表转换为红黑二叉树。


JDK1.7和JDK1.8 HashMap得区别。
   JDK1.7使用得数据结构: 数组+链表  而且链表插入模式为头部插入(造成死循环)
   jdk1.8使用得数据结构: 数组+链表+红黑树 而且链表得插入模式为尾部插入。

 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                 //如果key得hash值相同,判断key得equals是否相同,替换原来得元素
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        // 判断链表得长度是否超过8个
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 把链表转换为红黑树结构
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

集合到此结束,谢谢大家浏览!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值