Collection集合框架学习笔记

Collection集合框架学习笔记

笔记向


文章目录


前言

养成总结的好习惯


提示:以下是本篇文章正文内容,下面案例可供参考

在这里插入图片描述

collection

●概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。
●和数组区别:(1)数组长度固定,集合长度不固定.(2)数组可以存储基本类型和引用类型,集合只能存储引用类型
●位置: java. util. *;

collection 体系集合

在这里插入图片描述

因为接口不能new,只能实现,所以我们Collection c = new 的只能是实现类
这里collection的new的实现类是arrayList
方法:boolean add (0bject obj) //添加一一个对象。boolean addA1l (Collection c) //将一个集合中的所有对象添加到此集合中。.void clear() //清空此集合中的所有对象。boolean contains (Object o) //检查此集合中是否包含o对象boolean equals (Object o) //比较此集合是否与指定对象相等。boolean isEmpty() //判断此集合是否为空boolean remove (0bject o) //在此集合中移除o对象int size() //返回此集合中的元素个数。Object[] toArray() //将此集合转换成数组。
/**
* 1.添加 2.判断存在 3.删除 4.利用for迭代 
* 5.利用iterator迭代(hasNext,next,remove) 
* 6.isEmpty 7.toString
* 8.clear删除全部 (但是其实在内存中对象还存在)
*/
public class CollectionDemo {
    public static void main(String[] args) {
        Collection collection = new ArrayList();
        collection.add("a");
        collection.add("b");
        collection.add("c");
        System.out.println(collection.size());
        if (collection.contains("a")){
            System.out.println("存在a,删除!");
            boolean a = collection.remove("a");
            if (a) System.out.println("删除成功");
        }
        //利用for迭代
        for(Object o:collection)System.out.println(o);
        //利用iterator迭代器进行迭代
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
            System.out.println("读取出一个删掉一个");
            iterator.remove();
        }
        System.out.println(collection.isEmpty());
    }
}

collection 存Student对象

public class CollectionDemo2 {
    public static void main(String[] args) {
        Collection<Student> collection = new ArrayList<>();
        Student s1 = new Student("wang",12),
                s2 = new Student("zi",13),
                s3 = new Student("qi",14);
        collection.add(s1);
        collection.add(s2);
        collection.add(s3);
        System.out.println(collection.size());
        System.out.println(collection.toString());
        //下面这样再new一个判断是判断不到的,每new一个对象,就相当于再堆上创建一个新对象

        if (collection.contains(new Student("qi",14))){//只能用s1,s2,s3这些变量进行判断
            System.out.println("存在qi");
        }
        for (Student student: collection) {
            System.out.println(student);
        }
        //迭代器版本
        Iterator<Student> iterator = collection.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

看看collection的子接口

List

1.有序,有下标,元素可以重复
2.方法(因为list接口继承collection, 那么list中也会包含collection中的方法)
void add(int index,Object o) //在index的位置插入对象
boolean addAll(int index, Collection c)//将在一个集合中的元素添加到此集合中的index位置Object get(int index) //返回集合中指定位置的元素。
Object set(int index,Object o)//根据index下标设置值,返回的是原先的被覆盖的值List subList (int fromIndex, int toIndex) //返回from Index和toIndex之间的集合元
listIterator() 这是list的另外加的一个迭代器
listIterator(int index) 这是list的另外加的一个迭代器 从列表指定位置开始
subList(int fromList,int toIndex) 返回列表中指定的fromIndex(包括)toIndex(不包括)之间的部分list

/**
 * List的特点
 * 有下标,可以重复
 */
public class ListDemo1 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("a");
        list.add(1,"b");
        list.add(2,"c");
        System.out.println(list.toString());
        Object a1 = list.set(0, "a1");
        System.out.println(a1);
        System.out.println(list.size());
        System.out.println(list.toString());
        //删除
        list.remove(0);
        System.out.println(list.size());
        System.out.println(list.toString());
        //foreach遍历
        for (Object s:list) {
            System.out.println(s);
        }
        //for下标遍历
        //...

        //iterator迭代器遍历
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //ListIterator 这个是list特有的list迭代器
        //这个功能要比iterator强,支持任意方向迭代,且可以添加删除修改元素
        /**
         * 1.hasNext() 2.hasPrevious() 3.next() 4.previous()
         * 5.nextIndex(),6previousIndex() 7.remove()
         * 8.add() 9.set()
         */
        //正向
        ListIterator listIterator = list.listIterator();
        while (listIterator.hasNext()){
            System.out.println(listIterator.nextIndex()+""+listIterator.next());
        }
        //逆向
        while (listIterator.hasPrevious()){
            System.out.println(listIterator.previousIndex()+""+listIterator.previous());
        }
        //判断
        if (list.contains("a"))System.out.println("has a");
        System.out.println(list.indexOf("a"));
    }
}
List存基本类型会自动装箱
/**
 * 因为list也是一种collection集合
 * 集合是不存基本类型的,只存对象
 * 所以存入的int会被自动装箱转为Integer
 */
public class ListDemo2 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(10);//int会被自动装箱转为Integer
        list.add(20);
        list.add(30);
        list.add(40);
        list.remove(0);//如果remove参数为int  list的remove是根据下标,而不是根据值
        list.remove(new Integer(20));//传入一个对象;或者像下面
        list.remove((Object) 30);//转型为Obj格式也行
        System.out.println(list.toString());
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(40);
        List list1 = list.subList(0, 3);//是一个左闭又开的区间
        System.out.println(list1.toString());
    }
}
我们来看看List的实现类

ArrayList (重点!)

#使用的很多
    ArrayList其中是有个数组的,数组结构实现,查询快,增删速度慢(如果在中间插入,后面的元素需要移动..)
    JDK1.2版本增加,运行效率快,但是线程不安全
'对比另一个List的实现类Vector'
    数组结构实现,查询快,增删速度慢
    JDK1.0版本出现,运行效率慢,但是线程安全!
'对比LinkedList'
    链表结构实现,查询慢,增删快!
/**
 * arrayList的使用
 * 存储结构:数组 查找遍历速度快, 增删速度慢
 */
public class arrayListDemo1 {
    public static void main(String[] args) {
        ArrayList<Student> arrayList = new ArrayList<>();
        Student s1 = new Student("a",1),
                s2 = new Student("b",2),
                s3 = new Student("c",3);
        //1.添加
        arrayList.add(s1);
        arrayList.add(s2);
        arrayList.add(s3);
        System.out.println(arrayList.size());
        System.out.println(arrayList.toString());
        //2.删除
        arrayList.remove(0);//或者通过s1变量删除..new一个对象时删除不了的
        //3.遍历 与list相同
        //1.普通的前向iterator
        //2.双向的增强版listIterator
        //查找
        System.out.println("查找");
        System.out.println(arrayList.indexOf(s2));
        System.out.println(arrayList.get(0));
    }
}
我们来看看ArrayList的源码

在这里插入图片描述

我们看到确实ArrayList时继承List接口没错

在这里插入图片描述

我们注意array中几个重要的常量和变量!

private static final int DEFAULT_CAPACITY = 10;//默认的容量大小是10
transient Object[] elementData; // 是用来存放元素的数组 ArrayList的构造结构是数组
private int size;//实际的元素个数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//定义一个默认的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};//定义一个空数组

下面来看构造方法

//1.无参构造
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    //存放数据的数组等于 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,默认空数组(上面的final定义的)
    //注意!!如果没有向集合中添加任何元素,那么默认情况下容量为0,
    //那么默认容量10是怎么来的?..我们分析以下add方法👇
}

add()的源码

//1.我们知道ArrayList a = new ArrayList();这个空参构造出来,默认数组长度是0;是一个空数组!那么默认10的长度从何而来>??
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!,添加个数,添加要给元素那么size++
        elementData[size++] = e;
        return true;
    }
//2.我们再进入ensureCapacityInternal方法,这里用到了一个ensureExplicitCapacity和calculateCapacity
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
//calculateCapacity中,将一开始的size + 1也就是0+1=1和DEFAULT_CAPACITY=10进行对比取max返回
//	就是再这里默认设置为了10的空间
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
//ensureExplicitCapacity中 上面通过取max,传来的参数为10,那么modCount修改次数就0++
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//elementData一开始为空,那么这里就是10-0=10
        grow(minCapacity);//grow增长(10)??我进进去grow看看
    }

grow源码 (这既是数组进行扩容的实际代码)

//数组进行扩容
private void grow(int minCapacity) {//这个参数minCapacity==10
        // overflow-conscious code
        int oldCapacity = elementData.length;//长度为0
        int newCapacity = oldCapacity + (oldCapacity >> 1);//还是0
        if (newCapacity - minCapacity < 0)//0-10==-10<0 成立
            newCapacity = minCapacity;//那么就将10赋值给newCapacity
        if (newCapacity - MAX_ARRAY_SIZE > 0)//10-MAX_ARRAY_SIZE(这个值非常大..)>0不成立
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//!!将这个新的数组(长度为10),传给这个															 elementData
    }
#所以ArrayList没有添加元素之前是0,
'一旦添加任意一个元素,那么长度就是10!'
长度一旦超过10就开始扩容~~~~~~~
#假如我们已经放入10个元素了,假如说size==10,那么ensureCapacityInternal(10+1)继续进入下一层进行扩容
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
#进入后跳转到ensureExplicitCapacity(11)
private void ensureExplicitCapacity(int minCapacity) {#minCapacity的值为11
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0) #11-10=1>0成立进入grow(11)
            grow(minCapacity);
    }
#进入grow(11)
private void grow(int minCapacity) {#11
        // overflow-conscious code
        int oldCapacity = elementData.length;#10
        int newCapacity = oldCapacity + (oldCapacity >> 1);#右移一位相当于除以2,现在new=10+(10/2)=15
        if (newCapacity - minCapacity < 0)#15-11=4>0不成立
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)#15-很大的一个值 肯定是个负值所以也不成立
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);#那么扩容从10到15
    }
'所以当插入第11个元素的时候,arrayList就扩容到了15'

ArrayList 扩容规律

//从以下两条语句我们可以看出扩容规律!
int oldCapacity = elementData.length;#10
int newCapacity = oldCapacity + (oldCapacity >> 1);#右移一位相当于除以2,现在new=10+(10/2)=15
//因为右移一位相当于整除2
    //所以每次扩容的规模就相当于 x 1.5倍!!!
    10--->15--->22--->(int)(22*1.5)->>>

那问题又来了,我们添加的数据是怎么放进ArrayList的,

我们回头看看add方法

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 这句话就是将0+1扩容到了10的默认长度
        elementData[size++] = e;//这一句就是将元素一个个放到数组中的代码
        return true;
    }

下面我们来看看Vector

Vector在JDK1.0就已经出现
1.'数组结构实现的',所以查找快,但是增删慢
2.JDK1.0版本出现,运行效率'慢','线程安全'
//看看构造方法
Vector() 构造一个空向量,时期内部数据数组的大小为'10',其标准容量增量为0;
add()
addAll(index,Collection) 将collection中的指定位置的元素添加进vector
addAll(Collection)将collection中所有元素添加进vector
contains()
elements()返回此向量的组件的枚举,(里面有两个方法如下)
    hasMoreElements()
    nextElement()
/**
 * 存储结构:数组
 * jdk1.0,线程安全,运行效率低
 */
public class VectorDemo {
    public static void main(String[] args) {
        Vector vector = new Vector();
        vector.add("a");
        vector.add("b");
        vector.add("c");
        System.out.println(vector.size());
        //删除跟之前一样,remove(index或者根据值),clear是清空
        //遍历可以使用index,或者foreach,迭代器iterator
        //下面看看特有的方法枚举器enumerate
        Enumeration elements = vector.elements();
        while(elements.hasMoreElements()){
            System.out.println(elements.nextElement());
        }
        //判断
        if (vector.contains("a"))System.out.println("has a");
        if (vector.isEmpty())System.out.println("empty");
        //vector其他方法
        vector.get(0);
        vector.elementAt(0);
        vector.firstElement();
        vector.lastElement();
    }
}
LinkedList (重要)
#linkedList
其中数据结构是一个'双向链表'--->增删快,查询慢
/**
 * 数据结构:双向链表
 */
public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<Student> linkedList = new LinkedList<>();
        Student s1 = new Student("wan1",1);
        Student s2 = new Student("wan2",2);
        Student s3 = new Student("wan3",3);
        //1.添加数据
        linkedList.add(s1);
        linkedList.add(s2);
        linkedList.add(s3);
        System.out.println(linkedList.size());
        System.out.println(linkedList.toString());
        //2.删除
//        linkedList.remove(s1);
//        linkedList.remove(0);
        //3.遍历操作
        //1.foreach
        for (Student student : linkedList) {
            System.out.println(student);
        }
        //fori
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }
        //iterator
        Iterator<Student> iterator = linkedList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //注意这里还有个增强的迭代器 可以正向也可以反向,还可以添加元素
        ListIterator<Student> studentListIterator = linkedList.listIterator();
        while (studentListIterator.hasNext()){
            System.out.println(studentListIterator.next());
        }
        while (studentListIterator.hasPrevious()){
            System.out.println(studentListIterator.hasPrevious());
        }
        //判断 contains  isEmpty
        //获取 indexOf  get
    }
}
我们来看看LinkedList的源码

在这里插入图片描述

//先看常量和变量
    transient int size = 0; //初始大小0
    transient Node<E> first; //头节点
    transient Node<E> last;	//尾节点
	public LinkedList() {    } //构造方法为空

我们看看LinkedList的Add方法源码

    public boolean add(E e) {
        linkLast(e); //将添加的元素连接的Last 也就是最后的位置,进入该方法👇
        return true;
    }
'核心代码!非常关键!'   
void linkLast(E e) {
        final Node<E> l = last; //我们先看看这个Node有啥👇  这里先将没添加之前的last赋给L
        final Node<E> newNode = new Node<>(l, e, null); //然后这里将之前的L和插入的值传进Node构造方法👇
        last = newNode; // 将尾指针指向newNode
        if (l == null)
            first = newNode; //如果之前linkedList就是个空的,那么将头指针指向这个唯一的节点
        else
            l.next = newNode;// 如果不为空,把刚刚的尾节点(现在成了倒数第二个)的next指向尾节点
        size++;	//长度++
        modCount++; //修改次数++
    }
private static class Node<E> {
        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;
        }
    }

我们看看不同结构实现方式(ArrayList & LinkedList 的区别)

1.区别就是ArrayList 是一个数组;//数组的物理地址是连续的
         LinkedList 是一个双向链表;//链表的物理地址是分散的,用一个个指针相连

在这里插入图片描述

泛型
泛型是在JDK1.5引入的一种新特性,本质就是参数化的类型,把类型当作参数进行传递
常见形式有泛型类,泛型接口,泛型方法
语法:<T,...>
    T称为类型占位符,表示一种引用类型,所以我们用的时候就要把这个类型传过去 像是List<Student>
    E表示element元素
    k表示一个key键
    v就是value  其实就是一个字母,写什么都行,但是还是按规则来
好处:
	1.提高代码的重用性
	2.防止类型转换异常,提高代码的安全性
'注意::!!
    1.一定要注意泛型只能是引用类型,int基本类型不可以!
    2.不同的泛型对象不能相互赋值,比如一个泛型是String 一个是Integer,不可以相互赋值

我们自己定义一个泛型MyGeneric

/**
 * 泛型类 : 类名<T,..>
 *     T表示的就是一个数据类型占位符,需要传进来一个引用类型,可以写多个用逗号隔开
 */
public class MyGeneric<T> {
    //使用这个泛型T创建对象
    //1.创建变量
    //注意!!在这个泛型类中,我们只能创建变量,但是不能实例化new对象,因为这个T是什么类型,我们是不知道的,也不知道是否有无参构造,所以new不出来
    T t;
    //2.添加方法
    public void show(T t){
        System.out.println(t);
    }
    //3.使用泛型作为方法的返回值
    public T getT(){
        return t;
    }
}

使用这个泛型

public class TestGeneric {
    public static void main(String[] args) {
        //使用泛型类创建对象
        MyGeneric<Student> studentMyGeneric = new MyGeneric<>();
        studentMyGeneric.t = new Student("wang",1);
        Student t = studentMyGeneric.getT();
        System.out.println(t);
        studentMyGeneric.show(new Student("wang2",2));
        /******/
        MyGeneric<Integer> myGeneric = new MyGeneric<>();
        //一定要注意泛型只能是引用类型,int基本类型不可以!
        myGeneric.t = 1;
        System.out.println(myGeneric.getT());;
        myGeneric.show(2);
    }
}

我们再来看看泛型接口!

/**
 * 泛型接口 : 接口名<T> T类型
 * 注意!!不能使用泛型创建,静态常量,new 什么的都不行!
 */
public interface MyGenricInterface<T> {
    String name = "wang";
    T server(T t);
}

然后进行泛型接口的实现,有两种方法,

1.在实现类implements的时候就指明T的类型

public class MyGenricInterfaceImpl implements MyGenricInterface<String> {
    @Override
    public String server(String s) {
        return s;
    }

    public static void main(String[] args) {
        MyGenricInterfaceImpl myGenricInterface = new MyGenricInterfaceImpl();
        System.out.println(myGenricInterface.server("hello"));
    }
}

2.使用实现类的时候再指定T的类型

public class MyGenricInterfaceImp2<T> implements MyGenricInterface<T> {
    @Override
    public T server(T t) {
        return t;
    }
    public static void main(String[] args) {
        MyGenricInterfaceImp2<String> myGenricInterface = new MyGenricInterfaceImp2<>();
        System.out.println(myGenricInterface.server("hello"));
    }
}
我们再来看看泛型方法
泛型方法的语法是 在类返回值之前加<T>
/**
 * 泛型方法,语法: <T> 放在方法返回值类型前面
 */
public class MyGenericMethod {
    public void show(){
        System.out.println("普通方法");
    }
    //泛型方法
    public <T> void show(T t){
        System.out.println("泛型方法"+t);
    }
    //也可以将方法的返回值设置为T
    public <T> T showWhitReturn(T t){
        System.out.println("泛型方法"+t);
        return t;
    }
}

看看泛型方法的调用

        //测试泛型方法
        MyGenericMethod method = new MyGenericMethod();
        method.show("hello");//这里的泛型类型就不用传,以传递的数据类型决定
        System.out.println(method.showWhitReturn(1));//返回Integer
这样用下来我们就可以真正体会到泛型的类,接口,方法的好处
    1.提高了代码的重用性
    	确实,如果一个方法是 public <T> T get(T t){return t;}
		'如果不用泛型的话需要对各种类型的情况进行一次重写..降低工作效率'
    2.防止类型抓换异常,提高代码的安全性
    	在泛型集合里可以体现,下面会讲
泛型集合
默认的时候是Object,如果不指定很容易出现错误!
    像下面这个例子..又放String 又放Integer
    创建的时候就规定好是什么类型!!省的之后类型转换
    ArrayList<String> a = new ArrayList<>();

在这里插入图片描述

在这里插入图片描述

Link基础就这样讲完了…

下面我们讲讲collection的另一个继承子接口 set
1.特点:无序,无下标,元素不可以重复('List的特点完全相反')
2.方法完全继承自Collection的方法,set中并没有定义其他的抽象方法,都是继承来的
    '所以就是一个不包含重复元素的collection
3.实现类:
	1.HashSet['重点']:基于HashCode实现元素不重复
        			 当存入元素的哈希码相同时,会调用equals进行确认,如果时true,则拒绝后者存入
    2.TreeSet:基于排列顺序实现元素不重复

下面看看set的使用

/**
 * set接口的使用
 * 1.无顺序(添加顺序和遍历顺序不一致) 2.不能重复,3.无下标
 */
public class SetDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("b");
        set.add("a");
        set.add("b");//存入相同的值,是存不进去的
        set.add("c");
        System.out.println(set.size());
        System.out.println(set.toString());
        //删除
        //set.remove("a");
        //遍历 foreach
        for (String s : set) {
            System.out.println(s);
        }
        //迭代器
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext())System.out.println(iterator.next());
        //判断 isEmpty 和 contains
        if (set.contains("b"))System.out.println("yes b");
    }
}
我们看看set的实现类 HashSet
1.HashSet['重点']:基于HashCode实现元素不重复
当存入元素的哈希码相同时,会调用equals进行确认,如果时true,则拒绝后者存入
    '存储结构:哈希表(数组+链表+(在jdk1.8之后又加入了红黑树))!!'

hashSet的使用

/**
 * hashset 集合的使用
 * 存储结构:哈希表(数组+链表+(在jdk1.8之后又加入了红黑树))
 */
public class HashSetDemo {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("b");
        hashSet.add("c");
        hashSet.add("a");
        System.out.println(hashSet.toString());
        System.out.println(hashSet.size());
        //删除 hashSet("a")
        //foreach
        for (String s : hashSet) {
            System.out.println(s);
        }
        //iterator
        Iterator<String> iterator = hashSet.iterator();
        while (iterator.hasNext())System.out.println(iterator.next());
        //contains
        System.out.println(hashSet.contains("a"));
        System.out.println(hashSet.isEmpty());
    }
}
//我们再看一个HashSet的案例
public class HashSetDemo2 {
    public static void main(String[] args) {
        HashSet<Student> hashSet = new HashSet<>();
        Student s1 = new Student("w1",1);//已经添加一次这个对象了
        Student s2 = new Student("w2",2);
        Student s3 = new Student("w3",3);
        hashSet.add(s1);
        hashSet.add(s2);
        hashSet.add(s3);
        hashSet.add(new Student("w1",1));//这里又new了一个对象,问能不能添加进hashSet?
        System.out.println(hashSet.toString());
    }
}

答案是能!

因为这个new出来的新的没有名字的对象 跟前面的s1不是一个对象.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Um6yn08Q-1614316722267)(collection.assets/image-20210225122808322.png)]

怎么能组织这种情况的发生>? 我们先看看hashSet的存储过程
1.根据'hashCode'计算保存的位置(在数组中的位置)
    那么这个位置为空,那么就直接保存;
	如果不为空,那么就执行第二步
2.如果放的地方已经存在一个元素了,就再执行equals方法判断'已存在的对象''新加入的对象'是不是一个对象
        ,如果equals方法为ture,那么就是相同的对象
     如果equals方法为false 那么就形成数组后的链表

在这里插入图片描述

如果我们想要解决这个现象,我们
STEP1 要在实体类中重写hashcode方法,
//对姓名和年龄作为hashCode的依据进行相加返回
   @Override
   public int hashCode() {
       int n1 = this.name.hashCode();
       int n2 = this.age;
       return n1 + n2;
   }
//这样同一个姓名+年龄的Student就会是一样的HashCode,
'但是这样还是能存进来,因为使用equals方法进行对比,返回值为false,毕竟在堆上是两个不同的对象,只是属性相同'
使new 方法add的对象和变量方法add的对象,通过equals判断,重复,
从而阻止新的new 的对象添加进hashset,我们进再实体类中重写以下equals方法(如果不重写,默认是继承Object的)

在这里插入图片描述

//Object类中的源码   
public boolean equals(Object obj) {
        return (this == obj);
    }

所以我们再Student类中,应该对他进行重写

    @Override
    public boolean equals(Object obj) {
        if(this == obj){
            return true;//如果相等就直接返回true
        }
        if (obj == null){
            return false;//为null就直接false
        }
        if (obj instanceof Student){ //注意这个判断instanceof,判断obj是否为Student类的实例,返回布尔值
            //instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例
            Student student = (Student)obj;
            if (this.name == student.getName() && this.age == student.getAge()){
                return true;
            }
        }
        return false;
    }
}
//现在相同信息的对象,跟去hashcode计算,然后进行equals的对比,返回的是true,那么说明相同,于是就拒绝存入hashset
//这样的话不管是添加,删除,判断  我们都可以用new了个新对象堆已存在hashset中对象进行判断

还有一个问题就是我们看到hashCode()的源码

会经常看到一个数组 31

    /* 下面这个是hashCode的一个实现实现类的方法  */
    @Override public int hashCode() {
        int hash = 7;
        hash = 31 * (hash + name.hashCode());
        hash = 31 * (hash + styleClassSet.hashCode());
        hash = 31 * (hash + styleClassSet.hashCode());
        hash = (id != null) ? 31 * (hash + id.hashCode()) : 0;
        hash = 31 * (hash + pseudoClassState.hashCode());
        return hash;
    }
//为什么要使用31 呢???
原因
    1.因为31是素数,设置初始值为31 ,然后在31的基础上进行加值,能够减少哈希散列冲突
    2.31能够提高执行的效率 因为  31*i 等价于 (i<<5)-i; 比如i如果为2那么62 = (2<<5)-2 = 26次方-2
    	(1)位运算能够加快执行效率
    	(2)位运算<<左移动相当于*2,>>右移动相当于整除2

在这里插入图片描述

接着介绍另一个set继承子接口的实现类

TreeSet
1.数据结构:红黑树
2.基于排列顺序实现元素不重复
3.实现了SortedSet接口,堆集合元素自动排序
4.元素对象的类型必须实现Comparable接口,指定排序规则
5.通过CompareTo方法确定是否为重复元素
红黑树是一个二叉查找树(通过自旋实现实现分支平衡,提高查找的性能)
/**
 * TreeSet的使用
 * 存储结构:红黑树
 */
public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<String> treeSet = new TreeSet<>();
        treeSet.add("2aaa");
        treeSet.add("aaa");
        treeSet.add("3bbb");
        treeSet.add("bbb");
        treeSet.add("ccc");
        treeSet.add("1ccc");
        System.out.println(treeSet.size());
        //[1ccc, 2aaa, 3bbb, aaa, bbb, ccc]
        //set是无序的指的是不按照插入顺序,但是会自动排序
        System.out.println(treeSet.toString());
        for (String s : treeSet) {
            System.out.println(s);
        }
        System.out.println("----");
        Iterator<String> iterator = treeSet.iterator();
        while (iterator.hasNext())System.out.println(iterator.next());
        System.out.println(treeSet.contains("abc"));
    }
}

我们看看TreeSet插入普通的Student对象行不行?

/**
 * TreeSet的使用
 * 存储结构:红黑树
 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Student> treeSet = new TreeSet<>();
        Student s1 = new Student("wang1",1);
        Student s2 = new Student("wang2",2);
        Student s3 = new Student("wang3",3);
        treeSet.add(s1);
        treeSet.add(s2);
        treeSet.add(s3);
        System.out.println(treeSet.size());
    }
}

在这里插入图片描述

不可以,因为TreeSet 的数据结构是红黑树
所以TreeSet是自动实现排序的,但是我们add的是Student!!
TreeSet不知道拿Student的什么进行排序,所以就会 Student cannot be cast to java.lang.Comparable
//所以TreeSet的要求是元素必须实现Comparable接口!!!
public class Student implements Comparable<Student>{
	    @Override
    public int compareTo(Student o) {
        //先比姓名,如果姓名一样,再比年龄
        int n1 = this.getName().compareTo(o.getName());
        int n2 = this.getAge() - o.getAge();
        return n1 == 0?n2 : n1;//如果姓名一样则 返回n2,否则直接返回n1
    }
}
这样才能添加对象成功

在这里插入图片描述

上面提到了Comparable接口,这里补充一下接口使用
'使用匿名类实现comparable方法,创建集合的时候我们就将比较规则告诉treeSet'
/**
 * TreeSet
 * Comparable接口实现:定制比较(比较器)
 */
public class TreeSetDemo3 {
    public static void main(String[] args) {
        //创建集合的时候我们就将比较规则告诉treeSet 通过这个匿名的接口实现类
        TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                //先比较年龄再比较姓名
                int n1 = o1.getAge() - o2.getAge();
                int n2 = o1.getName().compareTo(o2.getName());
                return n1 == 0?n2:n1 ;
            }
        });
    }
}
//再写一个案例,加入要求使用TreeSet对一堆字符串实现,按照字符串长度进行排序
/**
 * TreeSet
 * Comparable接口实现:字符串按照长度进行排序
 */
public class TreeSetDemo4 {
    public static void main(String[] args) {
        //创建集合的时候我们就将比较规则告诉treeSet 通过这个匿名的接口实现类
        TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //先比较年龄再比较姓名
                int n1 = o1.length() - o2.length(); //定制按照长度进行排序!
                return n1 ;
            }
        });
        treeSet.add("a");
        treeSet.add("ab");
        treeSet.add("abb");
        treeSet.add("dabb");
        System.out.println(treeSet.toString());
    }
}

我们来看看map的体系结构

在这里插入图片描述

Map父接口

1.特点,存储一对键值对,无序,无下标,键不可以重复,值可以重复
2.方法
    1.put(k,v)
    2.get(k)
    3.keySet()'重要:返回所有的key
    4.values()返回包含所有值的collection集合
    5.set<Map.Entry<k,v>> 键值匹配set集合
    6.containsKey()
    7.containsValue()
    8.entrySet()'重要:返回此映射中包含映射关系的Set视图'
    9.size
    10.isEmpty
    11.clear
    ...
小案例
/**
 * Map接口的使用,
 * 特点:1.存储键值对,2.键不能重复,值可以重复 3.无序(插入顺序),但是自动根据key排序
 */
public class MapDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        //1.添加
        map.put("b","b1");
        map.put("c","c1");
        map.put("a","a1");
        map.put("b","b2");//注意添加重复的key的时候后面添加的value替换前面的value
        System.out.println(map.size());
        System.out.println(map.toString());
        //2.删除
        //map.remove("b");
        //3.遍历
        //使用keySet集合---key值转为set进行遍历(不包含value,所以不能用iterator进行遍历value,value只能map.get获取)
        Set<String> set = map.keySet();
        for (String s : set) {  //可以写成for(String s:map.keySet())
            System.out.println(s+map.get(s));
        }
        System.out.println("---------------");
        //使用entrySet()进行遍历(entries,就是键值对)[效率要高于keySet,一次性获取key和set,keySet()只是获取了key]
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry);//直接输出键值对
            System.out.println(entry.getKey()+"--"+entry.getValue());//也可以get键值对的key和value
        }
    }
}
MAP集合的实现类

HashMap

HashMap['重点']
1.	jdk1.2版本出现,线程不安全,运行效率快,允许用null作为key或value
2.	'默认初始容量是16!!默认加载因子是0.75(加载因子就是比如容量为100,如果元素满75个便开始扩容)'  
    (arrayList默认是10,扩容方案是满了之后1.5倍扩容)
3.	方法跟map相同
4.	'存储结构,哈希表(数组+链表+(JDK1.8之后加入红黑树))'
Hash案例
/**
 * 存储结构,哈希表(数组+链表+(JDK1.8之后加入红黑树))
 */
public class HashMapDemo {
    public static void main(String[] args) {
        HashMap<User,String> hashMap = new HashMap<>();
        //1.添加元素
        User u1 = new User("wang1",1);
        User u2 = new User("wang2",2);
        User u3 = new User("wang3",3);
        hashMap.put(u1,"1");
        hashMap.put(u2,"2");
        hashMap.put(u3,"3");
     //问题来了,通过new对象还是破坏了键的唯一性,在堆中是不同的对象,所以加进来了,我们要通过user信息判断唯一性
        //在hashset说过,hash表是通过hashCode和equals进行判断重复
        //我们还是要重写hashCode和equals(通过姓名和stuNo判断重复)
        hashMap.put(new User("wang3",3),"4");//重写之后这个4将会覆盖之前的3
        System.out.println(hashMap.size()+"个,"+hashMap.toString());
    }
}
tips:'通过Alt+ins快捷键可以很快的创建hashCode和equals关于name和stuNo的重写判断'
        //3.删除
//        hashMap.remove(u1);
        //遍历
        Set<User> users = hashMap.keySet();
        for (User user : users) {
            System.out.println(user+","+hashMap.get(user));
        }
        //entrySet.
        Set<Map.Entry<User, String>> entries = hashMap.entrySet();
        for (Map.Entry<User, String> entry : entries) {
            System.out.println(entry.getKey()+","+entry.getValue());
        }
        //判断
        if (hashMap.containsKey(new User("wang1",1) ))System.out.println("yes");
    }
hashMap源码分析

在这里插入图片描述

//重要常量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量大小 aka 16
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大的容量大小..2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 加载因子0.75 (容量如果100,当大于75个的时候,则开始扩容)
static final int TREEIFY_THRESHOLD = 8;//JDK1.8加入的,因为加入了红黑树,链表长度大于8,则红黑树替代链表
static final int MIN_TREEIFY_CAPACITY = 64;//JDK1.8因红黑树而加入,数组长度大于64启动红黑树替代链表
/*上面这个8的意思是:在hash表中,同过hashCode判断加入数组下标位置,通过equals判断是否相同元素,
	如果不相同则加入数组后的链表, 如果链表长度大于8,且数组长度大于64,则用红黑树替代链表,提高执行效率!*/
static final int UNTREEIFY_THRESHOLD = 6;//链表长度减少到小于6的时候,用链表替代回红黑树
transient Node<K,V>[] table;//哈希表数组
transient int size;//元素个数

static class Node<K,V> implements Map.Entry<K,V> {//看到我们存的键值对就是一个个Node,
        final int hash;
        final K key;
        V value;
        Node<K,V> next;//是个单向的链表,指向next
    ....}
下面我们来看看HashMap的构造方法
   public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // 无参构造就是设定了一个构造因子0.75
    }
//但是我们没有添加元素的时候,hashMap的table = null , size = 0 
//当添加第一个元素的时候才会将数组创建到size=16
//所以我们如果创建了很多集合,还没又到添加元素的时候,是可以节省空间的,因为没添加的时候创建出来的数组就是null和0
put方法
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);//hash(key)后面你在分析,是一个产生hash值的东西
    }
// 进入putVal
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)//因为一开始table=nul所以是成立的,并给tab赋值
            n = (tab = resize()).length;//执行这一句
    
//进入resize()
        final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;//将此时的table赋值给oldTab
        int oldCap = (oldTab == null) ? 0 : oldTab.length;//oldTab 的oldCap =0
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//不满足  直接取看else
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //这里注意!!!扩容方式是 X2
                     oldCap >= DEFAULT_INITIAL_CAPACITY)	//newCap = oldCap << 1就相当于x2
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // 第一次创建的oldTab的大小等于0走这里
          newCap = DEFAULT_INITIAL_CAPACITY;//newCap新的空间创建为16
          newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//扩容边界就是16*0.75取int=12
        }
        if (newThr == 0) {//不进去
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//关键!将默认16容量的数组传给newTab
        table = newTab;//newTab转给table..现在数组大小就是16了
            
//在返回上一层putVal方法里
      Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;//刚才在这里如果是第一次put,那么就将大小resize到16,扩容阈=12
        if ((p = tab[i = (n - 1) & hash]) == null)//但是现在table数组中还是没有值的,tab[i]所以这里为null
            tab[i] = newNode(hash, key, value, null);//在这个tab[i]创建一个新的Node节点
        else { ... }//不用看
        ++modCount;//操作数++
        if (++size > threshold)//size+1,如果大于扩容边界的时候,那么执行resize
            resize();      //扩容,扩容规则就是x2  每次都是下次的两倍
'            			16->32->64....(扩容与还是一样0.75这样判断)
'   					回顾:arrayList是10->15->22 每次扩容1.5
注意JDK1.8之前链表做的是头插入,JDK1.8之后是尾插入

在这里插入图片描述

HashSet和HashMap的关系

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;//我们看到...hashSet下面其实就是一个hashMap!
//所以说只是用set的方法..底层其实都是map的方法
    //1.无参构造
        public HashSet() {
        map = new HashMap<>();
    }
    //2.size
    public int size() {
        return map.size();
    }
    //3.add就是map的put,也就是说HashSet就用的是HashMap的Key保存数据!
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
看看Map的另一个实现类(了解)
HashTable
JDK1.0,线程安全,运行效率慢,不允许null作为key或者value
哈希表结构(数组+链表)
    现在已经不怎么用了!
但是'HashTable的子类Properties'还是经常用到
Properties 是一个HashTable的子类
'hashtable的子类,要求key和value都是String,通常用语配置文件的读取
跟流有关.
看看Map的另一个实现类
TreeMap
1.结构:红黑树 (TreeSet也是一个红黑树,记住只要是tree..就是红黑树)
2.实现了Sortedap接口,(Map的子接口),可以对key自动排序
#案例'如果直接讲一个没有实现comparable接口的user插入treeMap,那么就会报错!'
public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<User, String> treeMap = new TreeMap<>();
        User u1 = new User("wang1",1);
        User u2 = new User("wang2",2);
        User u3 = new User("wang3",3);
        treeMap.put(u1,"1");
        treeMap.put(u2,"2");
        treeMap.put(u3,"3");
        System.out.println(treeMap.size());
        System.out.println(treeMap.toString());
    }
}

在这里插入图片描述

原理同treeSet当时是一样的
因为数据结构是一个排序的二叉树(红黑树)如果我们将User作为Key的话,'程序不知道我们用什么依据进行排序
    那么就'无法创建树'
    所以我们要像之前一样
    1.让user类实现Comparable接口并重写compare对比方法
    public class User implements Comparable<User>{
        ...
        @Override
    	public int compareTo(User o) {
        	int n1 = this.getName().compareTo(o.getName());
        	int n2 = this.getStuNo()-o.getStuNo();
        	return n1 == 0 ? n2 : n1;
        }
        ...
    }
    2.或者直接在创建树的时候就告诉树,User的对比方法
    public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<User, String> treeMap = new TreeMap<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                int n1 = o1.getName().compareTo(o2.getName());
                int n2 = o1.getStuNo()-o2.getStuNo();
                return n1 == 0 ? n2 : n1;
            }
        });
        User u1 = new User("wang1",1);
        User u2 = new User("wang2",2);
        User u3 = new User("wang3",3);
        treeMap.put(u1,"1");
        treeMap.put(u2,"2");
        treeMap.put(u3,"3");
        System.out.println(treeMap.size());
        System.out.println(treeMap.toString());
    }
}
TreeSet 和 TreeMap有什么关系???
//我们看看TreeSet的源码.发现treeSet的源码其实就是TreeMap
public TreeSet() {
        this(new TreeMap<E,Object>());
    }
//add就是map中的put
//TreeSet就是将值存进的TreeMap的value (用键的位置)保证了set值的唯一不重复性
public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
Collection工具类的使用

在这里插入图片描述

下面讲讲Collections工具类的时候(并补充了Arrays工具类实现集合和数组之间的转换问题)
/**
 * Collection工具类的使用
 */
public class CollectionUtilDemo {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(20);
        arrayList.add(5);
        arrayList.add(10);
        arrayList.add(30);
        arrayList.add(6);
        //sort
        System.out.println("排序前");
        System.out.println(arrayList.toString());
        Collections.sort(arrayList);
        System.out.println("排序后");
        System.out.println(arrayList.toString());
        //binarySearch二分查找的前提是先排序
        int i = Collections.binarySearch(arrayList, 30);//返回的是下标
        System.out.println(i);
        //copy复制
        ArrayList<Integer> arrayListCopy = new ArrayList<>();
        //这个copy方法有瑕疵.必须要求两个数组一样大的前提才能copy
        for (Integer integer : arrayList) {
            arrayListCopy.add(0);
        }
        Collections.copy(arrayListCopy,arrayList);
        System.out.println(arrayListCopy.toString());
        //翻转reverse
        Collections.reverse(arrayList);
        System.out.println(arrayList);
        //shuffle打乱顺序
        Collections.shuffle(arrayList);
        System.out.println(arrayList);
        //补充
        //1.list集合转为数组
        Integer[] integers = arrayList.toArray(new Integer[0]);//给定长度小于list那么按list的size为准
        System.out.println(integers.length);
        System.out.println(Arrays.toString(integers));
        //2.数组转为集合(Arrays工具包)  记得基本类型不支持(int不行Integer才行!)
        // 但是这个集合是一个受限集合,不能add和remove,
        // 因为数组长度是固定的,转过来list还是不能添加
        List<Integer> integers1 = Arrays.asList(integers);
        System.out.println(integers.toString());
    }
}

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值