一、集合概述

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

    二、Collection体系集合

    集合概述 - 图1

    2.1 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() //姜此集合转换成数组。
      1. /**
      2. * Collection接口的使用(一)
      3. * 1.添加元素
      4. * 2.删除元素
      5. * 3.遍历元素
      6. * 4.判断
      7. */
      8. public class Demo1{
      9. pubic static void main(String[] args){
      10. //创建集合
      11. Collection collection=new ArrayList();
      12. // * 1.添加元素
      13. Collection.add("苹果");
      14. Collection.add("西瓜");
      15. Collection.add("榴莲");
      16. System.out.println("元素个数:"+collection.size());
      17. System.out.println(collection);
      18. // * 2.删除元素
      19. collection.remove("榴莲");
      20. System.out.println("删除之后:"+collection.size());
      21. // * 3.遍历元素
      22. //3.1 使用增强for
      23. for(Object object : collection){
      24. System.out.println(object);
      25. }
      26. //3.2 使用迭代器(迭代器专门用来遍历集合的一种方式)
      27. //hasnext();判断是否有下一个元素
      28. //next();获取下一个元素
      29. //remove();删除当前元素
      30. Iterator iterator=collection.Itertor();
      31. while(iterator.hasnext()){
      32. String object=(String)iterator.next();
      33. System.out.println(s);
      34. //删除操作
      35. //collection.remove(s);引发错误:并发修改异常
      36. //iterator.remove();应使用迭代器的方法
      37. // * 4.判断
      38. System.out.println(collection.contains("西瓜"));//true
      39. System.out.println(collection.isEmpty());//false
      40. }
      41. }
      42. }
      1. /**
      2. * Collection接口的使用(二)
      3. * 1.添加元素
      4. * 2.删除元素
      5. * 3.遍历元素
      6. * 4.判断
      7. */
      8. public class Demo2 {
      9. public static void main(String[] args) {
      10. Collection collection=new ArrayList();
      11. Student s1=new Student("张三",18);
      12. Student s2=new Student("李四", 20);
      13. Student s3=new Student("王五", 19);
      14. //1.添加数据
      15. collection.add(s1);
      16. collection.add(s2);
      17. collection.add(s3);
      18. //collection.add(s3);可重复添加相同对象
      19. System.out.println("元素个数:"+collection.size());
      20. System.out.println(collection.toString());
      21. //2.删除数据
      22. collection.remove(s1);
      23. System.out.println("删除之后:"+collection.size());
      24. //3.遍历数据
      25. //3.1 增强for
      26. for(Object object:collection) {
      27. Student student=(Student) object;
      28. System.out.println(student.toString());
      29. }
      30. //3.2迭代器
      31. //迭代过程中不能使用collection的删除方法
      32. Iterator iterator=collection.iterator();
      33. while (iterator.hasNext()) {
      34. Student student=(Student) iterator.next();
      35. System.out.println(student.toString());
      36. }
      37. //4.判断和上一块代码类似。
      38. }
      39. }

      2.2 List 集合

      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之间的集合元素。
      1. /**
      2. * List子接口的使用(一)
      3. * 特点:1.有序有下标 2.可以重复
      4. *
      5. * 1.添加元素
      6. * 2.删除元素
      7. * 3.遍历元素
      8. * 4.判断
      9. * 5.获取位置
      10. */
      11. public class Demo3 {
      12. public static void main(String[] args) {
      13. List list=new ArrayList<>();
      14. //1.添加元素
      15. list.add("tang");
      16. list.add("he");
      17. list.add(0,"yu");//插入操作
      18. System.out.println("元素个数:"+list.size());
      19. System.out.println(list.toString());
      20. //2.删除元素
      21. list.remove(0);
      22. //list.remove("yu");结果同上
      23. System.out.println("删除之后:"+list.size());
      24. System.out.println(list.toString());
      25. //3.遍历元素
      26. //3.1 使用for遍历
      27. for(int i=0;i<list.size();++i) {
      28. System.out.println(list.get(i));
      29. }
      30. //3.2 使用增强for
      31. for(Object object:list) {
      32. System.out.println(object);
      33. }
      34. //3.3 使用迭代器
      35. Iterator iterator=list.iterator();
      36. while (iterator.hasNext()) {
      37. System.out.println(iterator.next());
      38. }
      39. //3.4使用列表迭代器,listIterator可以双向遍历,添加、删除及修改元素。
      40. ListIterator listIterator=list.listIterator();
      41. //从前往后
      42. while (listIterator.hasNext()) {
      43. System.out.println(listIterator.next());
      44. }
      45. //从后往前(此时“遍历指针”已经指向末尾)
      46. while(listIterator.hasPrevious()) {
      47. System.out.println(listIterator.previous());
      48. }
      49. //4.判断
      50. System.out.println(list.isEmpty());
      51. System.out.println(list.contains("tang"));
      52. //5.获取位置
      53. System.out.println(list.indexOf("tang"));
      54. }
      55. }
      1. /**
      2. * List子接口的使用(二)
      3. * 1.添加元素
      4. * 2.删除元素
      5. * 3.遍历元素
      6. * 4.判断
      7. * 5.获取位置
      8. */
      9. public class Demo4 {
      10. public static void main(String[] args) {
      11. List list=new ArrayList();
      12. //1.添加数字数据(自动装箱)
      13. list.add(20);
      14. list.add(30);
      15. list.add(40);
      16. list.add(50);
      17. System.out.println("元素个数:"+list.size());
      18. System.out.println(list.toString());
      19. //2.删除元素
      20. list.remove(0);
      21. //list.remove(20);很明显数组越界错误,改成如下
      22. //list.remove(Object(20));
      23. //list.remove(new Integer(20));
      24. System.out.println("元素个数:"+list.size());
      25. System.out.println(list.toString());
      26. //3-5不再演示,与之前类似
      27. //6.补充方法subList,返回子集合,含头不含尾
      28. List list2=list.subList(1, 3);
      29. System.out.println(list2.toString());
      30. }
      31. }

      List 集合实现类

      ArrayList【重点】
  • 数组结构实现,查询块、增删慢;

  • JDK1.2版本,运行效率快、线程不安全。

    1. /**
    2. * ArrayList的使用
    3. * 存储结构:数组;
    4. * 特点:查找遍历速度快,增删慢。
    5. * 1.添加元素
    6. * 2.删除元素
    7. * 3.遍历元素
    8. * 4.判断
    9. * 5.查找
    10. */
    11. public class Demo5 {
    12. public static void main(String[] args) {
    13. ArrayList arrayList=new ArrayList<>();
    14. //1.添加元素
    15. Student s1=new Student("唐", 21);
    16. Student s2=new Student("何", 22);
    17. Student s3=new Student("余", 21);
    18. arrayList.add(s1);
    19. arrayList.add(s2);
    20. arrayList.add(s3);
    21. System.out.println("元素个数:"+arrayList.size());
    22. System.out.println(arrayList.toString());
    23. //2.删除元素
    24. arrayList.remove(s1);
    25. //arrayList.remove(new Student("唐", 21));
    26. //注:这样可以删除吗(不可以)?显然这是两个不同的对象。
    27. //假如两个对象属性相同便认为其是同一对象,那么如何修改代码?
    28. //3.遍历元素
    29. //3.1使用迭代器
    30. Iterator iterator=arrayList.iterator();
    31. while(iterator.hasNext()) {
    32. System.out.println(iterator.next());
    33. }
    34. //3.2使用列表迭代器
    35. ListIterator listIterator=arrayList.listIterator();
    36. //从前往后遍历
    37. while(listIterator.hasNext()) {
    38. System.out.println(listIterator.next());
    39. }
    40. //从后往前遍历
    41. while(listIterator.hasPrevious()) {
    42. System.out.println(listIterator.previous());
    43. }
    44. //4.判断
    45. System.out.println(arrayList.isEmpty());
    46. //System.out.println(arrayList.contains(new Student("何", 22)));
    47. //注:与上文相同的问题。
    48. //5.查找
    49. System.out.println(arrayList.indexOf(s1));
    50. }
    51. }

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

    1. @Override
    2. public boolean equals(Object obj) {
    3. //1.是否为同一对象
    4. if (this==obj) {
    5. return true;
    6. }
    7. //2.判断是否为空
    8. if (obj==null) {
    9. return false;
    10. }
    11. //3.判断是否是Student类型
    12. if (obj instanceof Student) {
    13. Student student=(Student) obj;
    14. //4.比较属性
    15. if(this.name.equals(student.getName())&&this.age==student.age) {
    16. return true;
    17. }
    18. }
    19. //不满足,返回false
    20. return false;
    21. }

    ArrayList源码分析
  • 默认容量大小:private static final int DEFAULT_CAPACITY = 10;

  • 存放元素的数组:transient Object[] elementData;
  • 实际元素个数:private int size;
  • 创建对象时调用的无参构造函数:
    1. //这是一个空的数组
    2. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    3. public ArrayList() {
    4. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    5. }
    这段源码说明当你没有向集合中添加任何元素时,集合容量为0。那么默认的10个容量怎么来的呢?
    这就得看看add方法的源码了:
    1. public boolean add(E e) {
    2. ensureCapacityInternal(size + 1); // Increments modCount!!
    3. elementData[size++] = e;
    4. return true;
    5. }
    假设你new了一个数组,当前容量为0,size当然也为0。这时调用add方法进入到ensureCapacityInternal(size + 1);该方法源码如下:
    1. private void ensureCapacityInternal(int minCapacity) {
    2. ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    3. }
    该方法中的参数minCapacity传入的值为size+1也就是 1,接着我们再进入到calculateCapacity(elementData, minCapacity)里面:
    1. private static int calculateCapacity(Object[] elementData, int minCapacity) {
    2. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    3. return Math.max(DEFAULT_CAPACITY, minCapacity);
    4. }
    5. return minCapacity;
    6. }
    上文说过,elementData就是存放元素的数组,当前容量为0,if条件成立,返回默认容量DEFAULT_CAPACITY也就是10。这个值作为参数又传入ensureExplicitCapacity()方法中,进入该方法查看源码:
    1. private void ensureExplicitCapacity(int minCapacity) {
    2. modCount++;
    3. // overflow-conscious code
    4. if (minCapacity - elementData.length > 0)
    5. grow(minCapacity);
    6. }
    我们先不要管modCount这个变量。
    因为elementData数组长度为0,所以if条件成立,调用grow方法,重要的部分来了,我们再次进入到grow方法的源码中:
    1. private void grow(int minCapacity) {
    2. // overflow-conscious code
    3. int oldCapacity = elementData.length;
    4. int newCapacity = oldCapacity + (oldCapacity >> 1);
    5. if (newCapacity - minCapacity < 0)
    6. newCapacity = minCapacity;
    7. if (newCapacity - MAX_ARRAY_SIZE > 0)
    8. newCapacity = hugeCapacity(minCapacity);
    9. // minCapacity is usually close to size, so this is a win:
    10. elementData = Arrays.copyOf(elementData, newCapacity);
    11. }
    这个方法先声明了一个oldCapacity变量将数组长度赋给它,其值为0;又声明了一个newCapacity变量其值为oldCapacity+一个增量,可以发现这个增量是和原数组长度有关的量,当然在这里也为0。第一个if条件满足,newCapacity的值为10(这就是默认的容量,不理解的话再看看前面)。第二个if条件不成立,也可以不用注意,因为MAX_ARRAY_SIZE的定义如下:
    1. 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版本,运行效率慢、线程安全。

    1. /**
    2. * Vector的演示使用
    3. *
    4. *1.添加数据
    5. *2.删除数据
    6. *3.遍历
    7. *4.判断
    8. */
    9. public class Demo1 {
    10. public static void main(String[] args) {
    11. Vector vector=new Vector<>();
    12. //1.添加数据
    13. vector.add("tang");
    14. vector.add("he");
    15. vector.add("yu");
    16. System.out.println("元素个数:"+vector.size());
    17. //2.删除数据
    18. /*
    19. * vector.remove(0); vector.remove("tang");
    20. */
    21. //3.遍历
    22. //使用枚举器
    23. Enumeration enumeration=vector.elements();
    24. while (enumeration.hasMoreElements()) {
    25. String s = (String) enumeration.nextElement();
    26. System.out.println(s);
    27. }
    28. //4.判断
    29. System.out.println(vector.isEmpty());
    30. System.out.println(vector.contains("he"));
    31. //5. Vector其他方法
    32. //firstElement() lastElement() ElementAt();
    33. }
    34. }

    LinkedList
  • 链表结构实现,增删快,查询慢。

    1. /**
    2. * LinkedList的用法
    3. * 存储结构:双向链表
    4. * 1.添加元素
    5. * 2.删除元素
    6. * 3.遍历
    7. * 4.判断
    8. */
    9. public class Demo2 {
    10. public static void main(String[] args) {
    11. LinkedList linkedList=new LinkedList<>();
    12. Student s1=new Student("唐", 21);
    13. Student s2=new Student("何", 22);
    14. Student s3=new Student("余", 21);
    15. //1.添加元素
    16. linkedList.add(s1);
    17. linkedList.add(s2);
    18. linkedList.add(s3);
    19. linkedList.add(s3);
    20. System.out.println("元素个数:"+linkedList.size());
    21. System.out.println(linkedList.toString());
    22. //2.删除元素
    23. /*
    24. * linkedList.remove(new Student("唐", 21));
    25. * System.out.println(linkedList.toString());
    26. */
    27. //3.遍历
    28. //3.1 使用for
    29. for(int i=0;i<linkedList.size();++i) {
    30. System.out.println(linkedList.get(i));
    31. }
    32. //3.2 使用增强for
    33. for(Object object:linkedList) {
    34. Student student=(Student) object;
    35. System.out.println(student.toString());
    36. }
    37. //3.3 使用迭代器
    38. Iterator iterator =linkedList.iterator();
    39. while (iterator.hasNext()) {
    40. Student student = (Student) iterator.next();
    41. System.out.println(student.toString());
    42. }
    43. //3.4 使用列表迭代器(略)
    44. //4. 判断
    45. System.out.println(linkedList.contains(s1));
    46. System.out.println(linkedList.isEmpty());
    47. System.out.println(linkedList.indexOf(s3));
    48. }
    49. }

    LinkedList源码分析

    LinkedList首先有三个属性:

  • 链表大小:transient int size = 0;

  • (指向)第一个结点/头结点:transient Node first;
  • (指向)最后一个结点/尾结点:transient Node last;

关于Node类型我们再进入到类里看看:

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }

首先item存放的是实际数据;next指向下一个结点而prev指向上一个结点。
Node带参构造方法的三个参数分别是前一个结点、存储的数据、后一个结点,调用这个构造方法时将它们赋值给当前对象。
LinkedList是如何添加元素的呢?先看看add方法:

  1. public boolean add(E e) {
  2. linkLast(e);
  3. return true;
  4. }

进入到linkLast方法:

  1. void linkLast(E e) {
  2. final Node<E> l = last;
  3. final Node<E> newNode = new Node<>(l, e, null);
  4. last = newNode;
  5. if (l == null)
  6. first = newNode;
  7. else
  8. l.next = newNode;
  9. size++;
  10. modCount++;
  11. }

假设刚开始new了一个LinkedList对象,first和last属性都为空,调用add进入到linkLast方法。
首先创建一个Node变量 l 将last(此时为空)赋给它,然后new一个newNode变量存储数据,并且它的前驱指向l,后继指向null;再把last指向newNode。如下图所示:
集合概述 - 图2
如果满足if条件,说明这是添加的第一个结点,将first指向newNode:
集合概述 - 图3
至此,LinkedList对象的第一个数据添加完毕。假设需要再添加一个数据,我们可以再来走一遍,过程同上不再赘述,图示如下:
集合概述 - 图4


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

集合概述 - 图5

2.3 泛型

概述

  • Java泛型是JDK1.5中引入的一个新特性,其本质是参数化类型,把类型作为参数传递。
  • 常见形式有泛型类、泛型接口、泛型方法。
  • 语法:
    • T称为类型占位符,表示一种引用类型。
  • 好处:

    • 提高代码的重用性。
    • 防止类型转换异常,提高代码的安全性。

      泛型类

      1. /**
      2. * 泛型类
      3. * 语法:类名<T>
      4. * T是类型占位符,表示一种引用类型,编写多个使用逗号隔开
      5. *
      6. */
      7. public class myGeneric<T>{
      8. //1.创建泛型变量
      9. //不能使用new来创建,因为泛型是不确定的类型,也可能拥有私密的构造方法。
      10. T t;
      11. //2.泛型作为方法的参数
      12. public void show(T t) {
      13. System.out.println(t);
      14. }
      15. //泛型作为方法的返回值
      16. public T getT() {
      17. return t;
      18. }
      19. }
      1. /**
      2. * 注意:
      3. * 1.泛型只能使用引用类型
      4. * 2.不同泛型类型的对象不能相互赋值
      5. */
      6. public class testGeneric {
      7. public static void main(String[] args) {
      8. //使用泛型类创建对象
      9. myGeneric<String> myGeneric1=new myGeneric<String>();
      10. myGeneric1.t="tang";
      11. myGeneric1.show("he");
      12. myGeneric<Integer> myGeneric2=new myGeneric<Integer>();
      13. myGeneric2.t=10;
      14. myGeneric2.show(20);
      15. Integer integer=myGeneric2.getT();
      16. }
      17. }

      泛型接口

      1. /**
      2. * 泛型接口
      3. * 语法:接口名<T>
      4. * 注意:不能创建泛型静态常量
      5. */
      6. public interface MyInterface<T> {
      7. //创建常量
      8. String nameString="tang";
      9. T server(T t);
      10. }
      1. /**
      2. * 实现接口时确定泛型类
      3. */
      4. public class MyInterfaceImpl implements MyInterface<String>{
      5. @Override
      6. public String server(String t) {
      7. System.out.println(t);
      8. return t;
      9. }
      10. }
      1. //测试
      2. MyInterfaceImpl myInterfaceImpl=new MyInterfaceImpl();
      3. myInterfaceImpl.server("xxx");
      4. //xxx
      1. /**
      2. * 实现接口时不确定泛型类
      3. */
      4. public class MyInterfaceImpl2<T> implements MyInterface<T>{
      5. @Override
      6. public T server(T t) {
      7. System.out.println(t);
      8. return t;
      9. }
      10. }
      1. //测试
      2. MyInterfaceImpl2<Integer> myInterfaceImpl2=new MyInterfaceImpl2<Integer>();
      3. myInterfaceImpl2.server(2000);
      4. //2000

      泛型方法

      1. /**
      2. * 泛型方法
      3. * 语法:<T> 返回类型
      4. */
      5. public class MyGenericMethod {
      6. public <T> void show(T t) {
      7. System.out.println("泛型方法"+t);
      8. }
      9. }
      1. //测试
      2. MyGenericMethod myGenericMethod=new MyGenericMethod();
      3. myGenericMethod.show("tang");
      4. myGenericMethod.show(200);
      5. myGenericMethod.show(3.14);

      泛型集合

  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致。

  • 特点:

    • 编译时即可检查,而非运行时抛出异常。
    • 访问时,不必类型转换(拆箱)。
    • 不同泛型指尖引用不能相互赋值,泛型不存在多态。

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

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

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

      2.4 Set 集合

      Set 集合概述

  • 特点:无序、无下标、元素不可重复。

  • 方法:全部继承自Collection中的方法。

    1. /**
    2. * 测试Set接口的使用
    3. * 特点:1.无序,没有下标;2.重复
    4. * 1.添加数据
    5. * 2.删除数据
    6. * 3.遍历【重点】
    7. * 4.判断
    8. */
    9. public class Demo1 {
    10. public static void main(String[] args) {
    11. Set<String> set=new HashSet<String>();
    12. //1.添加数据
    13. set.add("tang");
    14. set.add("he");
    15. set.add("yu");
    16. System.out.println("数据个数:"+set.size());
    17. System.out.println(set.toString());//无序输出
    18. //2.删除数据
    19. /*
    20. * set.remove("tang"); System.out.println(set.toString());
    21. */
    22. //3.遍历【重点】
    23. //3.1 使用增强for
    24. for (String string : set) {
    25. System.out.println(string);
    26. }
    27. //3.2 使用迭代器
    28. Iterator<String> iterator=set.iterator();
    29. while (iterator.hasNext()) {
    30. System.out.println(iterator.next());
    31. }
    32. //4.判断
    33. System.out.println(set.contains("tang"));
    34. System.out.println(set.isEmpty());
    35. }
    36. }

    Set 集合实现类

    HashSet【重点】
  • 基于HashCode计算元素存放位置。

  • 当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入。
    1. /**
    2. * 人类
    3. */
    4. public class Person {
    5. private String name;
    6. private int age;
    7. public Person(String name,int age) {
    8. this.name = name;
    9. this.age = age;
    10. }
    11. public String getName() {
    12. return name;
    13. }
    14. public void setName(String name) {
    15. this.name = name;
    16. }
    17. public int getAge() {
    18. return age;
    19. }
    20. public void setAge(int age) {
    21. this.age = age;
    22. }
    23. @Override
    24. public String toString() {
    25. return "Peerson [name=" + name + ", age=" + age + "]";
    26. }
    27. }
    1. /**
    2. * HashSet集合的使用
    3. * 存储结构:哈希表(数组+链表+红黑树)
    4. * 1.添加元素
    5. * 2.删除元素
    6. * 3.遍历
    7. * 4.判断
    8. */
    9. public class Demo3 {
    10. public static void main(String[] args) {
    11. HashSet<Person> hashSet=new HashSet<>();
    12. Person p1=new Person("tang",21);
    13. Person p2=new Person("he", 22);
    14. Person p3=new Person("yu", 21);
    15. //1.添加元素
    16. hashSet.add(p1);
    17. hashSet.add(p2);
    18. hashSet.add(p3);
    19. //重复,添加失败
    20. hashSet.add(p3);
    21. //直接new一个相同属性的对象,依然会被添加,不难理解。
    22. //假如相同属性便认为是同一个对象,怎么修改?
    23. hashSet.add(new Person("yu", 21));
    24. System.out.println(hashSet.toString());
    25. //2.删除元素
    26. hashSet.remove(p2);
    27. //3.遍历
    28. //3.1 增强for
    29. for (Person person : hashSet) {
    30. System.out.println(person);
    31. }
    32. //3.2 迭代器
    33. Iterator<Person> iterator=hashSet.iterator();
    34. while (iterator.hasNext()) {
    35. System.out.println(iterator.next());
    36. }
    37. //4.判断
    38. System.out.println(hashSet.isEmpty());
    39. //直接new一个相同属性的对象结果输出是false,不难理解。
    40. //注:假如相同属性便认为是同一个对象,该怎么做?
    41. System.out.println(hashSet.contains(new Person("tang", 21)));
    42. }
    43. }
    注:hashSet存储过程:
  1. 根据hashCode计算保存的位置,如果位置为空,则直接保存,否则执行第二步。
  2. 执行equals方法,如果方法返回true,则认为是重复,拒绝存储,否则形成链表。

存储过程实际上就是重复依据,要实现“注”里的问题,可以重写hashCode和equals代码:

  1. @Override
  2. public int hashCode() {
  3. final int prime = 31;
  4. int result = 1;
  5. result = prime * result + age;
  6. result = prime * result + ((name == null) ? 0 : name.hashCode());
  7. return result;
  8. }
  9. @Override
  10. public boolean equals(Object obj) {
  11. if (this == obj)
  12. return true;
  13. if (obj == null)
  14. return false;
  15. if (getClass() != obj.getClass())
  16. return false;
  17. Person other = (Person) obj;
  18. if (age != other.age)
  19. return false;
  20. if (name == null) {
  21. if (other.name != null)
  22. return false;
  23. } else if (!name.equals(other.name))
  24. return false;
  25. return true;
  26. }

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

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

TreeSet
  • 基于排序顺序实现不重复。
  • 实现了SortedSet接口,对集合元素自动排序。
  • 元素对象的类型必须实现Comparable接口,指定排序规则。
  • 通过CompareTo方法确定是否为重复元素。
    1. /**
    2. * 使用TreeSet保存数据
    3. * 存储结构:红黑树
    4. * 要求:元素类必须实现Comparable接口,compareTo方法返回0,认为是重复元素
    5. */
    6. public class Demo4 {
    7. public static void main(String[] args) {
    8. TreeSet<Person> persons=new TreeSet<Person>();
    9. Person p1=new Person("tang",21);
    10. Person p2=new Person("he", 22);
    11. Person p3=new Person("yu", 21);
    12. //1.添加元素
    13. persons.add(p1);
    14. persons.add(p2);
    15. persons.add(p3);
    16. //注:直接添加会报类型转换错误,需要实现Comparable接口
    17. System.out.println(persons.toString());
    18. //2.删除元素
    19. persons.remove(p1);
    20. persons.remove(new Person("he", 22));
    21. System.out.println(persons.toString());
    22. //3.遍历(略)
    23. //4.判断
    24. System.out.println(persons.contains(new Person("yu", 21)));
    25. }
    26. }
    查看Comparable接口的源码,发现只有一个compareTo抽象方法,在人类中实现它:
    1. public class Person implements Comparable<Person>{
    2. @Override
    3. //1.先按姓名比
    4. //2.再按年龄比
    5. public int compareTo(Person o) {
    6. int n1=this.getName().compareTo(o.getName());
    7. int n2=this.age-o.getAge();
    8. return n1==0?n2:n1;
    9. }
    10. }
    除了实现Comparable接口里的比较方法,TreeSet也提供了一个带比较器Comparator的构造方法,使用匿名内部类来实现它:
    1. /**
    2. * TreeSet的使用
    3. * Comparator:实现定制比较(比较器)
    4. */
    5. public class Demo5 {
    6. public static void main(String[] args) {
    7. TreeSet<Person> persons=new TreeSet<Person>(new Comparator<Person>() {
    8. @Override
    9. public int compare(Person o1, Person o2) {
    10. // 先按年龄比较
    11. // 再按姓名比较
    12. int n1=o1.getAge()-o2.getAge();
    13. int n2=o1.getName().compareTo(o2.getName());
    14. return n1==0?n2:n1;
    15. }
    16. });
    17. Person p1=new Person("tang",21);
    18. Person p2=new Person("he", 22);
    19. Person p3=new Person("yu", 21);
    20. persons.add(p1);
    21. persons.add(p2);
    22. persons.add(p3);
    23. System.out.println(persons.toString());
    24. }
    25. }
    接下来我们来做一个小案例:
    1. /**
    2. * 要求:使用TreeSet集合实现字符串按照长度进行排序
    3. * helloworld tangrui hechengyang wangzixu yuguoming
    4. * Comparator接口实现定制比较
    5. */
    6. public class Demo6 {
    7. public static void main(String[] args) {
    8. TreeSet<String> treeSet=new TreeSet<String>(new Comparator<String>() {
    9. @Override
    10. //先比较字符串长度
    11. //再比较字符串
    12. public int compare(String o1, String o2) {
    13. int n1=o1.length()-o2.length();
    14. int n2=o1.compareTo(o2);
    15. return n1==0?n2:n1;
    16. }
    17. });
    18. treeSet.add("helloworld");
    19. treeSet.add("tangrui");
    20. treeSet.add("hechenyang");
    21. treeSet.add("yuguoming");
    22. treeSet.add("wangzixu");
    23. System.out.println(treeSet.toString());
    24. //输出[tangrui, wangzixu, yuguoming, hechenyang, helloworld]
    25. }
    26. }

    三、Map集合

    3.1 概述

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

集合概述 - 图6
方法

  • V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值。
  • Object get(Object key)//根据键获取相应的值。
  • Set//返回所有的key
  • Collection values()//返回包含所有值的Collection集合。
  • Set>//键值匹配的set集合

    1. /**
    2. * Map接口的使用
    3. * 特点:1.存储键值对 2.键不能重复,值可以重复 3.无序
    4. */
    5. public class Demo1 {
    6. public static void main(String[] args) {
    7. Map<String,Integer> map=new HashMap<String, Integer>();
    8. //1.添加元素
    9. map.put("tang", 21);
    10. map.put("he", 22);
    11. map.put("fan", 23);
    12. System.out.println(map.toString());
    13. //2.删除元素
    14. map.remove("he");
    15. System.out.println(map.toString());
    16. //3.遍历
    17. //3.1 使用keySet();
    18. for (String key : map.keySet()) {
    19. System.out.println(key+" "+map.get(key));
    20. }
    21. //3.2 使用entrySet();效率较高
    22. for (Map.Entry<String, Integer> entry : map.entrySet()) {
    23. System.out.println(entry.getKey()+" "+entry.getValue());
    24. }
    25. }
    26. }

    3.2 Map集合的实现类

    HashMap【重点】

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

    1. /**
    2. * HashMap的使用
    3. * 存储结构:哈希表(数组+链表+红黑树)
    4. */
    5. public class Demo2 {
    6. public static void main(String[] args) {
    7. HashMap<Student, String> hashMap=new HashMap<Student, String>();
    8. Student s1=new Student("tang", 36);
    9. Student s2=new Student("yu", 101);
    10. Student s3=new Student("he", 10);
    11. //1.添加元素
    12. hashMap.put(s1, "成都");
    13. hashMap.put(s2, "杭州");
    14. hashMap.put(s3, "郑州");
    15. //添加失败,但会更新值
    16. hashMap.put(s3,"上海");
    17. //添加成功,不过两个属性一模一样;
    18. //注:假如相同属性便认为是同一个对象,怎么修改?
    19. hashMap.put(new Student("he", 10),"上海");
    20. System.out.println(hashMap.toString());
    21. //2.删除元素
    22. hashMap.remove(s3);
    23. System.out.println(hashMap.toString());
    24. //3.遍历
    25. //3.1 使用keySet()遍历
    26. for (Student key : hashMap.keySet()) {
    27. System.out.println(key+" "+hashMap.get(key));
    28. }
    29. //3.2 使用entrySet()遍历
    30. for (Entry<Student, String> entry : hashMap.entrySet()) {
    31. System.out.println(entry.getKey()+" "+entry.getValue());
    32. }
    33. //4.判断
    34. //注:同上
    35. System.out.println(hashMap.containsKey(new Student("he", 10)));
    36. System.out.println(hashMap.containsValue("成都"));
    37. }
    38. }

    注:和之前说过的HashSet类似,重复依据是hashCode和equals方法,重写即可:

    1. @Override
    2. public int hashCode() {
    3. final int prime = 31;
    4. int result = 1;
    5. result = prime * result + id;
    6. result = prime * result + ((name == null) ? 0 : name.hashCode());
    7. return result;
    8. }
    9. @Override
    10. public boolean equals(Object obj) {
    11. if (this == obj)
    12. return true;
    13. if (obj == null)
    14. return false;
    15. if (getClass() != obj.getClass())
    16. return false;
    17. Student other = (Student) obj;
    18. if (id != other.id)
    19. return false;
    20. if (name == null) {
    21. if (other.name != null)
    22. return false;
    23. } else if (!name.equals(other.name))
    24. return false;
    25. return true;
    26. }

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

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

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final K key;
  3. V value;
  4. Node<K,V> next;
  5. }

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

  1. public HashMap() {
  2. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  3. }

发现没什么内容,只是赋值了一个默认加载因子;而在上文我们观察到源码中table和size都没有赋予初始值,说明刚创建的HashMap对象没有分配容量,并不拥有默认的16个空间大小,这样做的目的是为了节约空间,此时table为null,size为0。
当我们往对象里添加元素时调用put方法:

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }

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

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  2. boolean evict) {
  3. Node<K,V>[] tab; Node<K,V> p; int n, i;
  4. if ((tab = table) == null || (n = tab.length) == 0)
  5. n = (tab = resize()).length;
  6. if ((p = tab[i = (n - 1) & hash]) == null)
  7. tab[i] = newNode(hash, key, value, null);
  8. else{
  9. //略
  10. }
  11. }

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

  1. final Node<K,V>[] resize() {
  2. Node<K,V>[] oldTab = table;
  3. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  4. int oldThr = threshold;
  5. if (oldCap > 0);
  6. else if (oldThr > 0);
  7. else { // zero initial threshold signifies using defaults
  8. newCap = DEFAULT_INITIAL_CAPACITY;
  9. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  10. }
  11. @SuppressWarnings({"rawtypes","unchecked"})
  12. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  13. table = newTab;
  14. return newTab;
  15. }

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

  1. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict){
  2. if (++size > threshold)
  3. resize();
  4. }

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

  1. final Node<K,V>[] resize() {
  2. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  3. int newCap;
  4. if (oldCap > 0) {
  5. if (oldCap >= MAXIMUM_CAPACITY) {//略}
  6. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  7. oldCap >= DEFAULT_INITIAL_CAPACITY)
  8. }
  9. }

核心部分是else if里的移位操作,也就是说每次扩容都是原来大小的两倍。
注:额外说明的一点是在JDK1.8以前链表是头插入,JDK1.8以后链表是尾插入。


HashSet源码分析

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

  1. public class HashSet<E>
  2. extends AbstractSet<E>
  3. implements Set<E>, Cloneable, java.io.Serializable
  4. {
  5. private transient HashMap<E,Object> map;
  6. private static final Object PRESENT = new Object();
  7. public HashSet() {
  8. map = new HashMap<>();
  9. }
  10. }

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

  1. public boolean add(E e) {
  2. return map.put(e, PRESENT)==null;
  3. }

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

Hashtable

  • JDK1.0版本,线程安全,运行效率慢;不允许null作为key或是value。
  • 初始容量11,加载因子0.75。
    这个集合在开发过程中已经不用了,稍微了解即可。

    Properties

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

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

TreeMap

  • 实现了SortedMap接口(是Map的子接口),可以对key自动排序。

    1. /**
    2. * TreeMap的使用
    3. * 存储结构:红黑树
    4. */
    5. public class Demo3 {
    6. public static void main(String[] args) {
    7. TreeMap<Student, Integer> treeMap=new TreeMap<Student, Integer>();
    8. Student s1=new Student("tang", 36);
    9. Student s2=new Student("yu", 101);
    10. Student s3=new Student("he", 10);
    11. //1.添加元素
    12. treeMap.put(s1, 21);
    13. treeMap.put(s2, 22);
    14. treeMap.put(s3, 21);
    15. //不能直接打印,需要实现Comparable接口,因为红黑树需要比较大小
    16. System.out.println(treeMap.toString());
    17. //2.删除元素
    18. treeMap.remove(new Student("he", 10));
    19. System.out.println(treeMap.toString());
    20. //3.遍历
    21. //3.1 使用keySet()
    22. for (Student key : treeMap.keySet()) {
    23. System.out.println(key+" "+treeMap.get(key));
    24. }
    25. //3.2 使用entrySet()
    26. for (Entry<Student, Integer> entry : treeMap.entrySet()) {
    27. System.out.println(entry.getKey()+" "+entry.getValue());
    28. }
    29. //4.判断
    30. System.out.println(treeMap.containsKey(s1));
    31. System.out.println(treeMap.isEmpty());
    32. }
    33. }

    在学生类中实现Comparable接口:

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

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

    1. TreeMap<Student, Integer> treeMap2=new TreeMap<Student, Integer>(new Comparator<Student>() {
    2. @Override
    3. public int compare(Student o1, Student o2) {
    4. // 略
    5. return 0;
    6. }
    7. });

    TreeSet源码

    和HashSet类似,放在TreeMap之后讲便一目了然(部分):

    1. public class TreeSet<E> extends AbstractSet<E>
    2. implements NavigableSet<E>, Cloneable, java.io.Serializable
    3. {
    4. private transient NavigableMap<E,Object> m;
    5. private static final Object PRESENT = new Object();
    6. TreeSet(NavigableMap<E,Object> m) {
    7. this.m = m;
    8. }
    9. public TreeSet() {
    10. this(new TreeMap<E,Object>());
    11. }
    12. }

    TreeSet的存储结构实际上就是TreeMap,再来看其存储方式:

    1. public boolean add(E e) {
    2. return m.put(e, PRESENT)==null;
    3. }

    它的add方法调用的就是TreeMap的put方法,将元素作为key传入到存储结构中。

    四、Collections工具类

    概念:集合工具类,定义了除了存取以外的集合常用方法。
    方法

  • public static void reverse(List<?> list) 反转集合中元素的顺序

  • public static void shuffle(List<?> list) 随机重置集合元素的顺序
  • public static void sort(List<T> list) 升序排序(元素类型必须实现Comparable接口)

    1. /**
    2. * 演示Collections工具类的使用
    3. *
    4. */
    5. public class Demo4 {
    6. public static void main(String[] args) {
    7. List<Integer> list=new ArrayList<Integer>();
    8. list.add(20);
    9. list.add(10);
    10. list.add(30);
    11. list.add(90);
    12. list.add(70);
    13. //sort排序
    14. System.out.println(list.toString());
    15. Collections.sort(list);
    16. System.out.println(list.toString());
    17. System.out.println("---------");
    18. //binarySearch二分查找
    19. int i=Collections.binarySearch(list, 10);
    20. System.out.println(i);
    21. //copy复制
    22. List<Integer> list2=new ArrayList<Integer>();
    23. for(int i1=0;i1<5;++i1) {
    24. list2.add(0);
    25. }
    26. //该方法要求目标元素容量大于等于源目标
    27. Collections.copy(list2, list);
    28. System.out.println(list2.toString());
    29. //reserve反转
    30. Collections.reverse(list2);
    31. System.out.println(list2.toString());
    32. //shuffle 打乱
    33. Collections.shuffle(list2);
    34. System.out.println(list2.toString());
    35. //补充:list转成数组
    36. Integer[] arr=list.toArray(new Integer[0]);
    37. System.out.println(arr.length);
    38. //补充:数组转成集合
    39. String[] nameStrings= {"tang","he","yu"};
    40. //受限集合,不能添加和删除
    41. List<String> list3=Arrays.asList(nameStrings);
    42. System.out.println(list3);
    43. //注:基本类型转成集合时需要修改为包装类
    44. }
    45. }