WELCOME

任何一个伟大的目标,都有一个微不足道的开始。

Java集合框架

JAVA集合框架详解

集合概述


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

Collection体系集合


Collection父接口


  • 特点:代表一组任意类型的对象,无序、无下标、不能重复。
  • 方法
    • boolean add(Object obj) //添加一个对象。
    • boolean addAll(Collection c) //讲一个集合中的所有对象添加到此集合中。
    • void clear() //清空此集合中的所有对象。
    • boolean contains(Object o) //检查此集合中是否包含o对象。
    • boolean equals(Object o) //比较此集合是否与指定对象相等。
    • boolean isEmpty() //判断此集合是否为空。
    • boolean remove(Object o) //在此集合中移除o对象。
    • int size() //返回此集合中的元素个数。
    • Object[] toArray() //姜此集合转换成数组。

第一种类型

package com.java_frame.demo;

/**
 * Collections接口的使用
 * (1)添加元素
 * (2)删除元素
 * (3)遍历元素
 * (4)判断
 * author gyj
 */

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

public class Demo01 {
    public static void main(String[] args) {
        //创建一个集合
        Collection collection = new ArrayList();
        //1.添加元素
        collection.add("苹果");
        collection.add("西瓜");
        collection.add("榴莲");
        System.out.println("元素个数:"+collection.size());
        System.out.println(collection);
        //2.删除元素
//        collection.remove("榴莲");
        System.out.println("删除之后元素个数:"+collection.size());
        //3.遍历【重点】
       //3.1使用增强for
        System.out.println("=====3.1使用增强for=======");
        for (Object object:collection) {
            System.out.println(object);
        }
        //3.2使用迭代器(专门用来遍历集合的一种方式)	***迭代器下面有讲***
        System.out.println("=====3.2使用迭代器=======");
        Iterator it = collection.iterator();
        while (it.hasNext()){
            String s = (String) it.next();
            System.out.println(s);
        }
        //4.判断
        System.out.println(collection.contains("西瓜"));
    }
}

第二种类型

package com.java_frame.demo;

/**
 * Collection的使用:保存学生信息
 * @author gyj
 */

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

public class Demo02 {
    public static void main(String[] args) {
        //新建Collection对象
        Collection collection = new ArrayList();
        Student yujian = new Student("yujian", 19);
        Student gao = new Student("gao", 20);
        Student tiantain = new Student("tiantain", 18);

        //1.添加数据
        collection.add(yujian);
        collection.add(gao);
        collection.add(tiantain);
        System.out.println("元素个数:"+collection.size());
        System.out.println(collection.toString());

        //2.删除
//        collection.remove(tiantain);
//        System.out.println("删除之后"+collection.toString());
        //3.遍历
        //3.1增强for
        System.out.println("======增强for遍历======");
        for (Object object:collection) {
            Student s = (Student)object;
            System.out.println(s.toString());
        }
        //3.2 迭代器遍历:hasNext(), Next(), remove()
        System.out.println("======迭代器遍历======");
        Iterator it = collection.iterator();
        while(it.hasNext()){
            Student s = (Student) it.next();
            System.out.println(s);
        }

        //4.判断
        System.out.println(collection.contains(gao));
        System.out.println(collection.isEmpty());
    }
}

package com.java_frame.demo;

/**
 * 学生类
 * @author gyj
 */
public class Student {
    private String name;
    private int age;

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

    public Student() {
    }

    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;
    }

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

获取一个迭代器

集合想获取一个迭代器可以使用 iterator() 方法:

 // 获取迭代器
Iterator it = collection.iterator();

Collection子接口


List集合

  • 特点:有序、有下标、元素可以重复。
  • 方法
    • void add(int index,Object o) //在index位置插入对象o。
    • boolean addAll(index,Collection c) //将一个集合中的元素添加到此集合中的index位置。
    • Object get(int index) //返回集合中指定位置的元素。
    • List subList(int fromIndex,int toIndex) //返回fromIndex和toIndex之间的集合元素。
package com.java_frame.demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * List子接口的使用
 * 特点:1.有序有下标 2.可以重复
 * @author gyj
 */
public class Demo03 {
    public static void main(String[] args) {
        //先创建集合对象
        List list = new ArrayList<>();
        //1.添加元素
        list.add("苹果");
        list.add("小米");
        list.add(0,"华为");
        System.out.println("元素个数:"+list.size());
        System.out.println(list);
        //2.删除元素
        /*
        list.remove("苹果");//删除值为苹果
        list.remove(0);//删除索引为0的值
        System.out.println("删除之后:"+list.size());
        System.out.println(list);
         */
        //3.遍历
        //3.1使用for遍历
        System.out.println("==========for遍历===========");
        for (int i=0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        //3.2增强for
        System.out.println("==========增强for遍历===========");
        for (Object object:list){
            System.out.println(object);
        }
        //3.3使用迭代器遍历
        System.out.println("=========迭代器遍历============");
        Iterator it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
        //3.4使用列表迭代器,和Iterator的区别,ListIterator可以向前或向后遍历,添加,删除,修改元素
        ListIterator lit= list.listIterator();
        System.out.println("=============使用列表迭代器从前往后遍历================");
        while (lit.hasNext()){
            System.out.println(lit.nextIndex()+":"+lit.next());
        }
        System.out.println("=============使用列表迭代器从后往前遍历================");
        while (lit.hasPrevious()){
            System.out.println(lit.previousIndex()+":"+lit.previous());
        }
        //4.判断
        System.out.println(list.contains("苹果"));
        System.out.println(list.isEmpty());

        //5.获取位置
        System.out.println(list.indexOf("华为"));
    }
}
package com.java_frame.demo;

import java.util.ArrayList;
import java.util.List;

/**
 * List的使用
 * @author gyj
 */
public class Demo04 {
    public static void main(String[] args) {
        //创建集合
        List list = new ArrayList();
        //1.添加数字数据(自动装箱)
        list.add(20);
        list.add(30);
        list.add(40);
        list.add(50);
        list.add(60);
        System.out.println("元素个数:"+list.size()); //元素个数:5
        System.out.println(list);// [20, 30, 40, 50, 60]
        //删除数字20
        //第一种做法:通过索引来定位
//        list.remove(0);
        //第二种做法:
//        list.remove((Object)20);
        //第三种做法:
        list.remove(new Integer(20));
        System.out.println(list);   //[30, 40, 50, 60]

        //补充:subList,返回子集合(含左不含右)
        List list1 = list.subList(1, 4);
        System.out.println(list1);  //[40, 50, 60]

    }
}


List实现类

ArrayList【重点】
  • 数组结构实现,查询块、增删慢;
  • JDK1.2版本,运行效率快、线程不安全。
package com.java_frame.demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

/**
 *ArrayList的使用
 * 存储结构:数组,查找遍历速度快,增删慢
 * @author gyj
 */
public class Demo05 {
    public static void main(String[] args) {
        //创建集合
        ArrayList arrayList = new ArrayList<>();

        Student s1 = new Student("yujian",20);
        Student s2 = new Student("gao",19);
        Student s3 = new Student("man",20);
        //1.添加元素
        arrayList.add(s1);
        arrayList.add(s2);
        arrayList.add(s3);
        System.out.println("元素个数:"+arrayList.size());
        System.out.println(arrayList.toString());
        //2.删除元素
//        arrayList.remove(s1);
//        arrayList.remove(new Student("gao",19));//需要重写equals方法
//        System.out.println("删除之后:"+arrayList.size());
        //3.遍历元素[重点]
        //1.使用迭代器遍历
        System.out.println("===========使用迭代器===========");
        Iterator it = arrayList.iterator();
        while (it.hasNext()){
            Student s = (Student)it.next();
            System.out.println(s.toString());
        }
        System.out.println("==============使用列表迭代器==================");
        ListIterator lst = arrayList.listIterator();
        while (lst.hasNext()){
            Student s = (Student)lst.next();
            System.out.println(s);
        }
        //4.判断
        System.out.println(arrayList.contains(new Student("yujian",20)));//需要重写equals方法
        System.out.println(arrayList.contains(s1));
        //5.查找
        System.out.println(arrayList.indexOf(s1));
    }
}

:Object里的equals(this==obj)用地址和当前对象比较,如果想实现代码中的问题,可以在学生类中重写equals方法:

@Override
    public boolean equals(Object obj) {
        //1.判断是否为同一个对象
        if (this == obj){
            return true;
        }
        //2.判断是否为空
        if (obj ==null){
            return false;
        }
        //3.判断是否为Student类型
        if(obj instanceof Student){
            Student s = (Student) obj;//从写equals方法,形参中obj是Object类,所以需要强制转换为Student类
            //4.比较属性
            if (this.name.equals(s.getName())&&this.age==s.getAge()){
                return true;
            }
        }

        return false;
    }
ArrayList源码分析
  • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;
    • 注:如果没有向集合当中添加任何元素,则其容量为0,添加一个任意元素之后,容量变成10
  • 存放元素的数组:transient Object[] elementData;
  • 实际元素个数:private int size;
  • 创建对象时调用的无参构造函数:
//这是一个空的数组private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};public ArrayList() {    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

这段源码说明当你没有向集合中添加任何元素时,集合容量为0。那么默认的10个容量怎么来的呢?

这就得看看add方法的源码了:

public boolean add(E e) {    ensureCapacityInternal(size + 1);  // Increments modCount!!    elementData[size++] = e;    return true;}

假设你new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);该方法源码如下:

private void ensureCapacityInternal(int minCapacity) {        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);        }        ensureExplicitCapacity(minCapacity);    }

上文说过,elementData就是存放元素的数组,当前容量为0,if条件成立,返回默认容量DEFAULT_CAPACITY也就是10。这个值作为参数又传入ensureExplicitCapacity()方法中,进入该方法查看源码:

private void ensureExplicitCapacity(int minCapacity) {    modCount++;    // overflow-conscious code    if (minCapacity - elementData.length > 0)        grow(minCapacity);}

我们先不要管modCount这个变量。

因为elementData数组长度为0,所以if条件成立,调用grow方法,重要的部分来了,我们再次进入到grow方法的源码中:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;又声明了一个newCapacity变量其值为oldCapacity+一个增量,可以发现这个增量是和原数组长度有关的量,当然在这里也为0。第一个if条件满足,newCapacity的值为10(这就是默认的容量,不理解的话再看看前面)。第二个if条件不成立,也可以不用注意,因为MAX_ARRAY_SIZE的定义如下:

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这个值太大了以至于第二个if条件没有了解的必要。

最后一句话就是为elementData数组赋予了新的长度,Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象不会改变,该拷贝不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值。

这时候再回到add的方法中,接着就向下执行elementData[size++] = e;到这里为止关于ArrayList就讲解得差不多了,当数组长度为10的时候你们可以试着过一下源码,查一下每次的增量是多少(答案是每次扩容为原来的1.5倍)。


Vector(现在开发过程中,用到的不是很多,但在面试的时候有可能会被问到)
  • 数组结构实现,查询快、增删慢;
  • JDK1.0版本,运行效率慢、线程安全。
package com.java_frame.demo_Collection_frame_02;

import java.util.Enumeration;
import java.util.Vector;

/**
 * 演示Vector集合的使用
 * 存储结构:数组
 * @author gyj
 */
public class Demo01 {
    public static void main(String[] args) {
        //创建集合
        Vector vector = new Vector<>();
        //添加元素
        vector.add("草莓");
        vector.add("香蕉");
        vector.add("西瓜");
        System.out.println("元素个数:"+vector.size());

        //删除元素
//        vector.remove(0);
//        vector.remove("西瓜");
        //遍历元素
        //使用枚举器
        Enumeration elements = vector.elements();
        while (elements.hasMoreElements()){
//            String s = (String) elements.nextElement();
            System.out.println(elements.nextElement());
        }
        //判断
        System.out.println(vector.contains("西瓜"));
        
    }
}


LinkedList
  • 链表结构实现,增删快,查询慢。
package com.java_frame.demo_Collection_frame_02;

import com.java_frame.demo_Collection_frame_01.Student;

import java.util.Iterator;
import java.util.LinkedList;

/**
 * LinkList 的使用
 * 存储结构:双向链表
 * @author gyj
 */
public class Demo02 {
    public static void main(String[] args) {
        //创建集合
        LinkedList linkedList = new LinkedList<>();

        Student s1 = new Student("yujian",19);
        Student s2 = new Student("gao",20);
        Student s3 = new Student("man",22);
        //添加元素
        linkedList.add(s1);
        linkedList.add(s2);
        linkedList.add(s3);
        System.out.println("元素个数:"+linkedList.size());
        System.out.println(linkedList);

        //删除
//        linkedList.remove(s1);

        //遍历
        System.out.println("==========for循环遍历===========");
        for (int i=0;i<linkedList.size();i++){
            System.out.println(linkedList.get(i));
        }
        System.out.println("==========增强for循环遍历===========");
        for (Object object:linkedList){
            Student s = (Student)object;
            System.out.println(s);
        }
        System.out.println("==========迭代器循环遍历===========");
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

        //判断
        System.out.println(linkedList.contains(s1));

        //获取
        System.out.println(linkedList.indexOf(s2));

    }
}


LinkedList源码分析

LinkedList首先有三个属性:

  • 链表大小:transient int size = 0;
  • (指向)第一个结点/头结点: transient Node<E> first;
  • (指向)最后一个结点/尾结点:transient Node<E> last;

LinkedList是如何添加元素的呢?先看看add方法:

public boolean add(E e) {
    linkLast(e);
    return true;
}

进入到linkLast方法:

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 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;
    }
}

首先item存放的是实际数据;next指向下一个结点而prev指向上一个结点。

Node带参构造方法的三个参数分别是前一个结点、存储的数据、后一个结点,调用这个构造方法时将它们赋值给当前对象.


ArrayList和LinkedList区别
  • ArrayList:必须开辟连续空间,查询快,增删慢。
  • LinkedList:无需开辟连续空间,查询慢,增删快。

image


泛型概述

  • Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
  • 常见形式有泛型类、泛型接口、泛型方法。
  • 语法:
    • <T,…> T称为类型占位符,表示一种引用类型。
  • 好处:
    • 提高代码的重用性。
    • 防止类型转换异常,提高代码的安全性。
泛型类
package com.java_frame.demo_Collection_frame_02;

/**
 * 泛型类
 * 语法:类名<T,E,K,..>,T表示类型占位符,表示一种引用类型,如果编写多个,用逗号(,)隔开
 * @author gyj
 */
public class MygGeneric<T> {
    //使用泛型T
    //1.创建一个变量
    T t;

    //2.泛型作为方法的参数
    public void show(T t){
        /*
            注:泛型可以创建变量,但是不可以创建对象
            T t1 = new T();(这是错误的)
         */
        System.out.println(t);
    }

    //3.泛型作为方法的返回值
    public T getT(){
        return t;
    }
}

测试类

package com.java_frame.demo_Collection_frame_02;

public class TestGeneric {
    public static void main(String[] args) {
        //1.使用泛型类创建对象
        //注意:1.泛型只能使用引用类型,2,不同泛型对象之间不能相互复制
        MygGeneric<String> mygGeneric = new MygGeneric<String>();//第二个<>中的内容可写可不写
        /*
            T为String类型,
            MygGeneric<T>中有变量t,则可以调用
            MygGeneric<T>中有方法show(),则可以调用
            MygGeneric<T>中有方法,getT(),则可以调用
         */
        mygGeneric.t = "hello";
        mygGeneric.show("大家好");
        System.out.println(mygGeneric.getT());

        MygGeneric<Integer> mygGeneric1 = new MygGeneric<>();
        mygGeneric1.t = 100;
        mygGeneric1.show(200);
        System.out.println(mygGeneric1.getT());
    }
}
泛型接口
package com.java_frame.demo_Collection_frame_02;

/**
 * 泛型接口
 * 语法:接口名<T>
 * @author gyj
 */
public interface MyInterface<T> {
    String name = "yujian";

    T server(T t);
}

1.MyInterfacempl.java

package com.java_frame.demo_Collection_frame_02;

public class MyInterfacempl implements MyInterface<String>{
/*
    因为你在<>当中传入了String,使得在Myinterfa.java中的T变成了String,则有
    String server(String s)
 */
    @Override
    public String server(String s) {
        System.out.println(s);
        return s;
    }
}

test1

MyInterfacempl myInterfacempl = new MyInterfacempl();
myInterfacempl.server("gao_yu_jian");

2.MyInterfacempl2.java

/**
 * 实现接口时不确定泛型类
 */
package com.java_frame.demo_Collection_frame_02;

public class MyInterfacempl2<T> implements MyInterface<T>{
    @Override
    public T server(T t) {
        System.out.println(t);
        return null;
    }
}

test2

MyInterfacempl2<Integer> myInterfacempl2 = new MyInterfacempl2<>();
myInterfacempl2.server(100);

3.MyIterfacempl3.java

package com.java_frame.demo_Collection_frame_02;

public class MyIterfacempl3 implements MyInterface<Object>{
    @Override
    public Object server(Object o) {
        System.out.println(o);
        return o;
    }
}

test3

MyIterfacempl3 myIterfacempl3 = new MyIterfacempl3();
myIterfacempl3.server(100);
myIterfacempl3.server(1.01);
myIterfacempl3.server("你好");
泛型方法

MyGenericMethod.java

package com.java_frame.demo_Collection_frame_02;

/**
 * 泛型方法
 * 语法:方法返回值的前面  <T> 方法返回值 如:public <T> void show(){...}
 * @author gyj
 */
public class MyGenericMethod {

    //泛型方法
    public <T> void show(T t){
        T t1;
        System.out.println("泛型方法"+" "+t);

    }

    public <T> T shout(T t){
        T t2;
        System.out.println("泛型方法"+" "+t);
        return t;
    }
}

Test

//T的类型根据你给的参数自行判断
MyGenericMethod myGenericMethod = new MyGenericMethod();
myGenericMethod.show(10);
myGenericMethod.shout("yujian");
泛型集合
  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。
  • 特点:
    • 编译时即可检查,而非运行时抛出异常。
    • 访问时,不必类型转换(拆箱)。
    • 不同泛型指尖引用不能相互赋值,泛型不存在多态。

之前我们在创建LinkedList类型对象的时候并没有使用泛型,但是进到它的源码中会发现:

COPYpublic class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{//略}

它是一个泛型类,而我之前使用的时候并没有传递,说明java语法是允许的,这个时候传递的类型是Object类,虽然它是所有类的父类,可以存储任意的类型,但是在遍历、获取元素时需要原来的类型就要进行强制转换。这个时候就会出现一些问题,假如往链表里存储了许多不同类型的数据,在强转的时候就要判断每一个原来的类型,这样就很容易出现错误。

package com.java_frame.demo_Collection_frame_02.demo;

import com.java_frame.demo_Collection_frame_01.Student;

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

public class Demo01 {
   public static void main(String[] args) {
       ArrayList<Integer> arrayList = new ArrayList<>();
       arrayList.add(100);
       arrayList.add(200);
       arrayList.add(300);
       for (Integer integer:arrayList) {
           System.out.println(integer);
       }
       ArrayList<Student> students = new ArrayList<>();
       Student s1 = new Student("yujian",19);
       Student s2 = new Student("gao",20);
       Student s3 = new Student("man",22);

       students.add(s1);
       students.add(s2);
       students.add(s3);

       for (Student student : students){
           System.out.println(student);
       }

       Iterator<Student> iterator = students.iterator();
       while (iterator.hasNext()){
           System.out.println(iterator.next());
       }
   }
}


Set集合概述

Set子接口
  • 特点:无序、无下标、元素不可重复。
  • 方法:全部继承自Collection中的方法。
package com.java_frame.demo_Collection_frame_03;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 测试set接口的使用
 * @author gyj
 */
public class Demo01 {
    public static void main(String[] args) {
        //1.创建集合
        Set<String> strings = new HashSet<>();
        //2.添加数据
        strings.add("芒果");
        strings.add("香蕉");
        strings.add("苹果");
        System.out.println("数据个数:"+strings.size());
        System.out.println(strings);
        //3.删除数据
//        strings.remove("苹果");
        //遍历[重点]
        //3.1增强for
        System.out.println("=======增强for遍历======");
        for (String s:strings){
            System.out.println(s);
        }
        //3.2使用迭代器
        System.out.println("=======迭代器遍历======");
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //4.判断
        System.out.println(strings.contains("苹果"));
    }
}

Set实现类

HashSet【重点】
  • 基于HashCode计算元素存放位置。
  • 当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。

存储String对象

package com.java_frame.demo_Collection_frame_03;

import java.util.HashSet;
import java.util.Iterator;

/**
 * HashSet 集合的使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * @author gyj
 */
public class Demo02 {
    public static void main(String[] args) {
        //创建集合
        HashSet<String> strings = new HashSet<>();
        //1.添加元素
        strings.add("yujian");
        strings.add("girl");
        strings.add("gao");
        strings.add("man");

        System.out.println("元素个数:"+strings.size());
        System.out.println(strings);
        //2.删除数据
//        strings.remove("gao");
//        System.out.println("元素个数:"+strings.size());//元素个数:3
        //3.遍历
        //3.1使用增强for遍历
        System.out.println("======使用增强for遍历======");
        for (String s:strings){
            System.out.println(s);
        }
        //3.2使用迭代器遍历
        System.out.println("======使用迭代器遍历======");
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //4.判断
        System.out.println(strings.contains("gao"));
    }
}

存储Person对象

package com.java_frame.demo_Collection_frame_03;

import java.util.HashSet;
import java.util.Iterator;

/**
 * HashSet 使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * 存储过程
 * (1)根据hashCode计算保存位置,如果此位置为空,则直接保存,如果不为空,则执行第二步
 * (2)在执行equal方法,如果equal方法为TRUE,则认为重复,形成链表
 * @author gyj
 */
public class Demo03 {
    public static void main(String[] args) {
        //创建集合
        HashSet<Person> people = new HashSet<>();
        //1.添加数据
        Person p1 = new Person("gao",19);
        Person p2 = new Person("girl",18);
        Person p3 = new Person("man",29);
        people.add(p1);
        people.add(p2);
        people.add(p3);
        people.add(new Person("man",29));
        System.out.println("元素个数:"+people.size());
        System.out.println(people);
        //2.删除数据
//        people.remove(p1);
        //3.遍历
        System.out.println("======增强for遍历=======");
        for (Person p:people){
            System.out.println(p);
        }
        System.out.println("======迭代器遍历=======");
        Iterator<Person> iterator = people.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //4.判断
        System.out.println(people.contains(p1));//true
        System.out.println(people.contains(new Person("gao",19)));//true
    }
}

package com.java_frame.demo_Collection_frame_03;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

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

    @Override
    public int hashCode() {
        int n1 = this.name.hashCode();
        int n2 = this.age;

        return n1+n2;
    }

    @Override
    public boolean equals(Object obj) {
        if (this==obj){
            return true;
        }
        if (obj==null){
            return false;
        }
        if (obj instanceof Person){
            Person p = (Person)obj;
            if (this.name.equals(p.getName())&&this.age==p.getAge()){
                return true;
            }
        }

        return false;
    }

}

hashCode方法里为什么要使用31这个数字大概有两个原因:

  1. 31是一个质数,这样的数字在计算时可以尽量减少散列冲突。
  2. 可以提高执行效率,因为31*i=(i<<5)-i,31乘以一个数可以转换成移位操作,这样能快一点;但是也有网上一些人对这两点提出质疑。

TreeSet
  • 基于排序顺序实现不重复。
  • 实现了SortedSet接口,对集合元素自动排序。
  • 元素对象的类型必须实现Comparable接口,指定排序规则。
  • 通过CompareTo方法确定是否为重复元素。
package com.java_frame.demo_Collection_frame_03;

import java.util.Iterator;
import java.util.TreeSet;

/**
 * 使用treeSet保存数据
 * 存储结构:红黑树
 * 要求:元素要实现Comparable接口,要实现compareTo()方法返回值为0,则认为是重复元素
 * @author gyj
 */
public class Demo05 {
    public static void main(String[] args) {
        //创建集合
        TreeSet<Person> people = new TreeSet<>();
        //添加元素
        Person p1 = new Person("gao",19);
        Person p2 = new Person("girl",18);
        Person p3 = new Person("man",29);

        people.add(p1);
        people.add(p2);
        people.add(p3);
        System.out.println("元素个数"+people.size());
        System.out.println(people);

        //遍历
        System.out.println("==========增强for遍历===========");
        for (Person p:people){
            System.out.println(p);
        }
        System.out.println("==========迭代器遍历===========");
        Iterator<Person> iterator = people.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

实现compareTo()方法:

	 @Override
	//1.先按姓名比
	//2.再按年龄比
	public int compareTo(Person o) {
		int n1=this.getName().compareTo(o.getName());
		int n2=this.age-o.getAge();
		return n1==0?n2:n1;//如果姓名一样,则比较年龄,否则就直接比较姓名
	}

除了实现Comparable接口里的比较方法,TreeSet也提供了一个带比较器Comparator的构造方法,使用匿名内部类来实现它:

package com.java_frame.demo_Collection_frame_03;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * TreeSet 接口使用
 * Comparator:实现定制比较(比较器)
 * Comparable:可比较的
 * @author gyj
 */
public class Demo06 {
    public static void main(String[] args) {
        //创建一个集合,并指定比较规则
        TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                int n1 = o1.getAge()-o2.getAge();
                int n2 = o1.getName().compareTo(o2.getName());
                return n1==0?n2:n1;
            }
        });
        Person p1 = new Person("gao",19);
        Person p2 = new Person("girl",18);
        Person p3 = new Person("man",29);

        people.add(p1);
        people.add(p2);
        people.add(p3);
        System.out.println(people);
    }
}

接下来我们来做一个小案例:

  • 要求:使用TreeSet集合实现字符串按照长度进行排序
  • helloworld zhang list wangwu shanghai gao xian
  • Comparator接口实现定制比较
package com.java_frame.demo_Collection_frame_03;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * 要求:使用TreeSet集合实现字符串按照长度进行排序
 * helloworld zhang list wangwu shanghai gao xian
 * Comparator接口实现定制比较
 * @author gyj
 */
public class Demo07 {
    public static void main(String[] args) {
        //创建集合,并指定规则
        TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int n1 = o1.length()-o2.length();
                int n2 = o1.compareTo(o2);
                return n1==0?n2:n1;
            }
        });
        //添加数据
        treeSet.add("helloworld");
        treeSet.add("zhang");
        treeSet.add("list");
        treeSet.add("gao");
        treeSet.add("cat");
        treeSet.add("beijing");
        treeSet.add("xian");
        System.out.println(treeSet.toString());
    }
}

Map体系集合

  • Map接口的特点:

    1. 用于存储任意键值对(Key-Value)。
    2. 键:无序、无下标、不允许重复(唯一)。
    3. 值:无序、无下标、允许重复。
      image

Map父接口

  • 特点:存储一对数据(Key-Value),无序、无下标,键不可重复。
  • 方法
    • V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。
  • Object get(Object key)//根据键获取相应的值。
    • keySet<K>//返回所有的key
    • Collection<V> values()//返回包含所有值的Collection集合。
    • entrySet<Map.Entry<K,V>>//键值匹配的set集合
package com.java_frame.demo_Collection_frame_04;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;

/**
 * Map接口的使用
 * 特点:(1)存储的是键值对,(2)键不能重复,值可以重复,(3)无序
 */
public class Demo01 {
    public static void main(String[] args) {
        //创建Map集合
        Map<String, String> map = new HashMap<>();
        //1.添加元素
        map.put("cn","中国");
        map.put("uk","英国");
        map.put("usa","美国");
        System.out.println("元素个数:"+map.size());
        System.out.println(map);

        //2.删除
//        map.remove("usa");
//        System.out.println(map.size());

        //3.遍历
        //3.1使用keySet()
        System.out.println("------keySet()------");
        Set<String> keySet = map.keySet();
        for (String key:keySet){
            System.out.println(key+"----"+map.get(key));
        }
        //3.2使用entrySet()方法
        System.out.println("------entrySet()------");
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        for (Map.Entry<String, String> entry:entrySet){
            System.out.println(entry.getKey()+"---"+entry.getValue());
        }
        
    }
}

entrySet效率高于keySet


Map集合的实现类

HashMap【重点】

  • JDK1.2版本,线程不安全,运行效率快;允许用null作为key或是value。
/**
   * 学生类
   */
package com.java_frame.demo_Collection_frame_04;

import java.util.Objects;

public class Student {
    private String name;
    private int stuNo;

    public Student() {
    }

    public Student(String name, int stuNo) {
        this.name = name;
        this.stuNo = stuNo;
    }

    public String getName() {
        return name;
    }

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

    public int getStuNo() {
        return stuNo;
    }

    public void setStuNo(int stuNo) {
        this.stuNo = stuNo;
    }

    @Override
    public String toString() {
        return "Student [name=" + name +",stuNo=" + stuNo + "]";
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(name, stuNo);
    }
}

package com.java_frame.demo_Collection_frame_04;

import java.util.HashMap;
import java.util.Map;

/**
 * HashMap集合的使用
 * 存储结构:哈希表(数组+链表+红黑树)
 * 使用key的hashcode和equal作为重复依据
 * @author gyj
 */
public class Demo02 {
    public static void main(String[] args) {
        //创建集合
        HashMap<Student, String> students = new HashMap<>();

        Student s1 = new Student("yujian", 001);
        Student s2 = new Student("gao", 002);
        Student s3 = new Student("man", 003);
        //添加元素
        students.put(s1,"北京");
        students.put(s2,"广州");
        students.put(s3,"西安");
        students.put(new Student("man", 003),"北京");
        System.out.println("元素个数:"+students.size());
        System.out.println(students);
        //2.删除
//        students.remove(s1);

        //3.遍历
        //使用keySet遍历
        System.out.println("======使用keySet遍历======");
        for (Student key:students.keySet()){
            System.out.println(key+":"+students.get(key));
        }
        //使用entrySet
        System.out.println("======使用entrySet======");
        for (Map.Entry<Student,String> entry:students.entrySet()){
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

        //判断
        System.out.println(students.containsKey(s1));
        System.out.println(students.containsValue("北京"));
    }
}

  • HashMap源码分析

  • 默认初始化容量:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    • 数组最大容量:static final int MAXIMUM_CAPACITY = 1 << 30;
  • 默认加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;

  • 链表调整为红黑树的链表长度阈值(JDK1.8):static final int TREEIFY_THRESHOLD = 8;

  • 红黑树调整为链表的链表长度阈值(JDK1.8):static final int UNTREEIFY_THRESHOLD = 6;

  • 链表调整为红黑树的数组最小阈值(JDK1.8):static final int MIN_TREEIFY_CAPACITY = 64;

  • HashMap存储的数组:transient Node<K,V>[] table;

  • HashMap存储的元素个数:transient int size;

    • 默认加载因子是什么?
      • 就是判断数组是否扩容的一个因子。假如数组容量为100,如果HashMap的存储元素个数超过了100*0.75=75,那么就会进行扩容。
    • 链表调整为红黑树的链表长度阈值是什么?
      • 假设在数组中下标为3的位置已经存储了数据,当新增数据时通过哈希码得到的存储位置又是3,那么就会在该位置形成一个链表,当链表过长时就会转换成红黑树以提高执行效率,这个阈值就是链表转换成红黑树的最短链表长度;
    • 红黑树调整为链表的链表长度阈值是什么?
      • 当红黑树的元素个数小于该阈值时就会转换成链表。
    • 链表调整为红黑树的数组最小阈值是什么?
      • 并不是只要链表长度大于8就可以转换成红黑树,在前者条件成立的情况下,数组的容量必须大于等于64才会进行转换。

    HashMap的数组table存储的就是一个个的Node<K,V>类型,很清晰地看到有一对键值,还有一个指向next的指针(以下只截取了部分源码):

static class Node<K,V> implements Map.Entry<K,V> {
      final K key;
      V value;
      Node<K,V> next;
  }

之前的代码中在new对象时调用的是HashMap的无参构造方法,进入到该构造方法的源码查看一下:

public HashMap() {
      this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  }
  • 发现没什么内容,只是赋值了一个默认加载因子;而在上文我们观察到源码中table和size都没有赋予初始值,说明刚创建的HashMap对象没有分配容量,并不拥有默认的16个空间大小,这样做的目的是为了节约空间,此时table为null,size为0。

    当我们往对象里添加元素时调用put方法:

    COPYpublic V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
      }
    

    put方法把key和value传给了putVal,同时还传入了一个hash(Key)所返回的值,这是一个产生哈希值的方法,再进入到putVal方法(部分源码):

    COPYfinal 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{
              //略
          }
      }
    

    这里面创建了一个tab数组和一个Node变量p,第一个if实际是判断table是否为空,而我们现在只关注刚创建HashMap对象时的状态,此时tab和table都为空,满足条件,执行内部代码,这条代码其实就是把resize()所返回的结果赋给tab,n就是tab的长度,resize顾名思义就是重新调整大小。查看resize()源码(部分):

    COPYfinal Node<K,V>[] resize() {
          Node<K,V>[] oldTab = table;
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int oldThr = threshold;
          if (oldCap > 0);
          else if (oldThr > 0);
          else {               // zero initial threshold signifies using defaults
              newCap = DEFAULT_INITIAL_CAPACITY;
              newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
          } 
          @SuppressWarnings({"rawtypes","unchecked"})
          Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
          table = newTab;
          return newTab;
      }
    

    该方法首先把table及其长度赋值给oldTab和oldCap;threshold是阈值的意思,此时为0,所以前两个if先不管,最后else里newCap的值为默认初始化容量16;往下创建了一个newCap大小的数组并将其赋给了table,刚创建的HashMap对象就在这里获得了初始容量。然后我们再回到putVal方法,第二个if就是根据哈希码得到的tab中的一个位置是否为空,为空便直接添加元素,此时数组中无元素所以直接添加。至此HashMap对象就完成了第一个元素的添加。当添加的元素超过16*0.75=12时,就会进行扩容:

    COPYfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
          if (++size > threshold)
              resize();
      }
    

    扩容的代码如下(部分):

    COPYfinal Node<K,V>[] resize() {
          int oldCap = (oldTab == null) ? 0 : oldTab.length;
          int newCap;
          if (oldCap > 0) {
              if (oldCap >= MAXIMUM_CAPACITY) {//略}
              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                       oldCap >= DEFAULT_INITIAL_CAPACITY)
          }
      }
    

    核心部分是else if里的移位操作,也就是说每次扩容都是原来大小的两倍

  • *注**:额外说明的一点是在JDK1.8以前链表是头插入,JDK1.8以后链表是尾插入。


    HashSet源码分析

    了解完HashMap之后,再回过头来看之前的HashSet源码,为什么放在后面写你们看一下源码就知道了(部分):

    COPYpublic class HashSet<E>
          extends AbstractSet<E>
          implements Set<E>, Cloneable, java.io.Serializable
      {
          private transient HashMap<E,Object> map;
          private static final Object PRESENT = new Object();
          public HashSet() {
              map = new HashMap<>();
          }
      }
    

    可以看见HashSet的存储结构就是HashMap,那它的存储方式是怎样的呢?可以看一下add方法:

    COPYpublic boolean add(E e) {
          return map.put(e, PRESENT)==null;
      }
    

    很明了地发现它的add方法调用的就是map的put方法,把元素作为map的key传进去的。。

Hashtable

  • JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。

  • 初始容量11,加载因子0.75。

    这个集合在开发过程中已经不用了,稍微了解即可。

Properties

  • Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。

它继承了Hashtable的方法,与流关系密切,此处不详解。

TreeMap

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
package com.java_frame.demo_Collection_frame_04;

import java.util.Map;
import java.util.TreeMap;

/**
 * TreeMap的使用
 * 存储结构:红黑树
 * @author gyj
 */
public class Demo03 {
    public static void main(String[] args) {
        //新建集合
        TreeMap<Student, String> treeMap = new TreeMap<>();

        Student s1 = new Student("yujian", 001);
        Student s2 = new Student("gao", 002);
        Student s3 = new Student("man", 003);

        treeMap.put(s1,"北京");
        treeMap.put(s2,"深圳");
        treeMap.put(s3,"上海");
        System.out.println("元素个数:"+treeMap.size());
        System.out.println(treeMap);

        //2.删除
//        treeMap.remove(s1,"北京");
        //遍历
        //3.1使用keySet遍历
        System.out.println("======3.1使用keySet遍历======");
        for (Student key:treeMap.keySet()){
            System.out.println(key+":"+treeMap.get(key));
        }
        System.out.println("======3.2使用entrySet()遍历======");
        for (Map.Entry<Student,String> entry:treeMap.entrySet()){
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

    }
}

在学生类中实现Comparable接口:

public class Student implements Comparable<Student>{
    @Override
    public int compareTo(Student o) {
        int n1=this.id-o.id;
        return n1;
}

除此之外还可以使用比较器来定制比较:

TreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        // 略
        return 0;
    }			
});

Collections工具类

  • 概念:集合工具类,定义了除了存取以外的集合常用方法。
  • 方法
    • public static void reverse(List<?> list)//反转集合中元素的顺序
    • public static void shuffle(List<?> list)//随机重置集合元素的顺序
    • public static void sort(List<T> list)//升序排序(元素类型必须实现Comparable接口)
package com.java_frame.demo_Collection_frame_04;

import java.util.*;

/**
 * 演示Collections工具类的使用
 * @author gyj
 */
public class Demo04 {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(20);
        arrayList.add(23);
        arrayList.add(15);
        arrayList.add(30);
        arrayList.add(6);
        //sotr排序
        System.out.println("排序前:"+arrayList);
        Collections.sort(arrayList);
        System.out.println("排序后:"+arrayList);

        //binarySearch 二分查找
        /*
            binarySearch(第一个参数:一个有序的数组,第二个参数:查找的数字)
            假如查找成功(找到了),返回一个正数,假如没有查找成功,则返回一个负数
         */
        int i = Collections.binarySearch(arrayList,15);
        System.out.println(i);

        //copy复制
        /*
            copy(第一个参数:新的数组,第二个参数:旧的数组(被拷贝的数组))
            有一个弊端,复制过去时,2个数组的长度必须一样
         */
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        for (int k=0;k<arrayList.size();k++){
            arrayList1.add(0);//使arratlist1的长度与arrayList长度相等,并且全部赋值为0
        }
        Collections.copy(arrayList1,arrayList);
        System.out.println(arrayList1);

        //reverse 反转
        Collections.reverse(arrayList1);
        System.out.println("反转之后:"+arrayList1);

        //shuffle 打乱
        Collections.shuffle(arrayList1);
        System.out.println("打乱之后:"+arrayList1);

        //补充:arrayList(集合)转成数组
        System.out.println("======arrayList(集合)转成数组======");
        Integer[] array = arrayList.toArray(new Integer[0]);
        System.out.println(Arrays.toString(array));
        //数组转成集合
        System.out.println("======数组转成集合======");
        String[] arr1 = {"xxx","yyy","zzz","aaa","bbb"};
        //集合是一个受限集合,不可以添加与删除
        List<String> list = Arrays.asList(arr1);
        System.out.println(list);
    }
}

完结撒花❀

posted @ 2021-08-19 17:19  GD1_1DG  阅读(40)  评论(0编辑  收藏  举报
Language: HTML