集合框架

一、框架

在程序中,将一些功能进行封装,提供给其他人使用的功能叫做框架。其作用是用来简化开发过程以及代码。

二、集合

实现了类似于数组的功能,将多个对象进行操作的方法进行了定义和封装,在开发时只需要调用即可,简化了数组的使用过程。

与数组的区别:

  • 数组长度固定,集合长度不固定。
  • 数组可以存储基本数据类型和引用数据类型,而集合只能存储引用数据类型。

注意:如果在集合中存储基本数据类型,会自动转换成包装类。

三、集合框架的体系

集合框架常用的主要有两个体系:Collection和Map

3.1 Collection体系

Collection作为整个体系的父接口,能够存储多种类型的对象,特点是无序、无下标、可以迭代(循环)每个元素。其有两个常用的子接口。

  • List,该接口增加下标功能,特点是有序,有下标,且元素可以重复。常用实现类如下:

    • ArrayList,底层使用数组实现
    • LinkedList,底层使用链表实现
  • Set,该接口特点是无序,无下标,且元素不能重复。

    • HashSet,特点是使用HashMap的key来实现。
    • TreeSet,特点是使用tree实现,所以可以排序。

3.2 collection体系常用方法

add:添加对象

addAll:将另一个集合中的所有对象添加到当前集合中

clear:清空集合中所有的对象

contains:判断集合中是否包含某个对象

isEmpty:判断集合是否为空

remove:删除一个对象

size:得到集合中元素的个数

toArray:将集合转换成数组

四、List接口相关

4.1 基本特点

List接口继承自Collection接口,特点是:有序、有下标、元素可以重复,相对于Collection接口,添加一些与下标相关的方法。

add(index, obj):向指定下标添加元素。

get(index):获得指定下标的元素。

4.2 有序、无序、排序

有序是指会按照元素添加时的顺序存放。

无序是指添加元素的顺序与最终存放的顺序无关,顺序与用户的理解也无关。

排序是指可以根据程序员要求的特点来进行排序。

4.3 常用实现类

ArrayList(Vector)、LinkedList

Vector与ArrayList具备有几乎相同的结构以及完全相同的用法,区别在于ArrayList不安全,Vector是线程安全的,但是性能非常低下,几乎不会被使用,后面会有其他的性能相对较高的安全的集合来代替。

4.4 数组和链表

  • 数组的特点:连续空间、长度固定。增删慢,遍历速度快。
  • 链表的特点:随机空间、长度不固定。增删快,遍历较慢。

    • 链表如果操作首尾,比操作中间更快
    • 链表有单向和双向之分,单向表示只能向一个方向遍历,双向可以向两边遍历。

4.5 ArrayList的用法

  1. public class TestMain1 {
  2. // ArrayList用法
  3. // 使用数组原理实现的集合
  4. public static void main(String[] args) {
  5. // 创建一个集合
  6. ArrayList list = new ArrayList();
  7. // 添加元素
  8. list.add("a");
  9. list.add("b");
  10. list.add("c");
  11. list.add("d");
  12. list.add("e");
  13. // 通过下标获取元素
  14. System.out.println(list.get(2));
  15. // 通过下标遍历
  16. // size方法获得集合中元素的个数
  17. for (int i = 0; i < list.size(); i++) {
  18. System.out.println(list.get(i));
  19. }
  20. // 通过foreach遍历
  21. for (Object object : list) {
  22. String temp = (String)object;
  23. System.out.println(temp);
  24. }
  25. // 使用迭代器循环
  26. Iterator it = list.iterator(); // 得到集合中的迭代器
  27. while(it.hasNext()) { // 如果有下一个
  28. String str = (String)it.next(); // 获取下一个
  29. System.out.println(str);
  30. }
  31. // 删除
  32. list.remove(1);// 通过下标删除
  33. list.remove("e"); // 直接删除元素
  34. Object [] arr = list.toArray(); // 将集合转换成数组
  35. }
  36. }

注意:Vector与ArrayList用法完全相同。

4.6 LinkedList的基本用法

  1. public class TestMain2 {
  2. // LinkedList用法
  3. // 使用数组原理实现的集合
  4. public static void main(String[] args) {
  5. // 创建一个集合
  6. LinkedList list = new LinkedList();
  7. // 添加元素
  8. list.add("a");
  9. list.add("b");
  10. list.add("c");
  11. list.add("d");
  12. list.add("e");
  13. // 通过下标获取元素
  14. System.out.println(list.get(2));
  15. // 通过下标遍历
  16. // size方法获得集合中元素的个数
  17. for (int i = 0; i < list.size(); i++) {
  18. System.out.println(list.get(i));
  19. }
  20. // 通过foreach遍历
  21. for (Object object : list) {
  22. String temp = (String)object;
  23. System.out.println(temp);
  24. }
  25. // 使用迭代器循环
  26. Iterator it = list.iterator(); // 得到集合中的迭代器
  27. while(it.hasNext()) { // 如果有下一个
  28. String str = (String)it.next(); // 获取下一个
  29. System.out.println(str);
  30. }
  31. // 删除
  32. list.remove(1);// 通过下标删除
  33. list.remove("e"); // 直接删除元素
  34. Object [] arr = list.toArray(); // 将集合转换成数组
  35. // 除了上面的通用用法以外,还有一些链表特有的用法
  36. list.addFirst("1"); // 在链表最前面添加
  37. list.addLast("2"); // 在链表最后面添加
  38. list.removeFirst(); // 删除最前面的元素
  39. list.removeLast(); // 删除最后面的元素
  40. }
  41. }

4.7 ArrayList底层实现

1、当创建一个无参的对象时,默认数据为一个空数组。长度为0

2、当创建的无参对象(空数组)第一次添加元素时,会将长度指定为10,添加元素时,会进行一次扩容。

3、如果元素的个数没有超过数组的空间时,不会扩容。如果添加的下一个元素时超过了数组空间大小时,会进行扩容。注:说明扩容的临界点是当数组元素已满时进行。

4、当进行扩容时,会扩容为原来大小的1.5倍。如果扩容1.5倍时超出了范围,扩容1个空间,保障至少能添加元素。

5、当新的空间大小超出MAX_ARRAY_SIZE时,指定为Integer.MAX_VALUE,如果超过了Integer.MAX_VALUE时,则报错。

  1. // 默认大小
  2. private static final int DEFAULT_CAPACITY = 10;
  3. // 存储数据的
  4. Object[] elementData;
  5. // 当前数组中元素的数量
  6. private int size;
  7. // 无参构造方法
  8. public ArrayList() {
  9. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  10. }
  11. // 通过指定长度来创建集合
  12. public ArrayList(int initialCapacity) {
  13. if (initialCapacity > 0) {
  14. // 通过指定长度来创建数组
  15. this.elementData = new Object[initialCapacity];
  16. // 如果长度等于0,就直接赋值为一个空数组
  17. } else if (initialCapacity == 0) {
  18. this.elementData = EMPTY_ELEMENTDATA;
  19. } else {
  20. // 如果是负数就报错
  21. throw new IllegalArgumentException("Illegal Capacity: "+
  22. initialCapacity);
  23. }
  24. }
  25. // 添加元素
  26. public boolean add(E e) {
  27. ensureCapacityInternal(size + 1); // Increments modCount!!
  28. //将新添加的元素赋值到数组中,并size加1
  29. elementData[size++] = e;
  30. return true;
  31. }
  32. // 判断是否空数组,根据数组情况来设置最小需要的长度
  33. private void ensureCapacityInternal(int minCapacity) {
  34. // 当通过无参构造方法创建对象时,默认为空数组时
  35. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  36. // 将数组大小设置为10
  37. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  38. }
  39. ensureExplicitCapacity(minCapacity);
  40. }
  41. private void ensureExplicitCapacity(int minCapacity) {
  42. modCount++;
  43. // overflow-conscious code
  44. // 当数组元素已经达到数组所容纳的元素上限时
  45. if (minCapacity - elementData.length > 0)
  46. // 扩容
  47. grow(minCapacity);
  48. }
  49. // 扩容
  50. private void grow(int minCapacity) {
  51. // overflow-conscious code
  52. // 旧的空间大小,原来数组的长度
  53. int oldCapacity = elementData.length;
  54. // 新的空间大小,原来数组大小的1.5倍
  55. int newCapacity = oldCapacity + (oldCapacity >> 1);
  56. // 当新的空间-需要的最小空间(size+1)小于0时,新空间直接赋值为最小空间(size+1)
  57. if (newCapacity - minCapacity < 0)
  58. newCapacity = minCapacity;
  59. // 当新的空间如果大于2147483639时
  60. if (newCapacity - MAX_ARRAY_SIZE > 0)
  61. // 指定新的空间为巨大的空间
  62. newCapacity = hugeCapacity(minCapacity);
  63. // minCapacity is usually close to size, so this is a win:
  64. // 创建一个新的大小的数组,并将原来的数据复制到新数组中
  65. elementData = Arrays.copyOf(elementData, newCapacity);
  66. }
  67. private static int hugeCapacity(int minCapacity) {
  68. // 保障的最小空间都超出了范围,此时直接报错
  69. if (minCapacity < 0) // overflow
  70. throw new OutOfMemoryError();
  71. // 如果需要保障的最小空间超过2147483639,直接指定大小为最大值(2147483647),否则指定为最大数组大小
  72. return (minCapacity > MAX_ARRAY_SIZE) ?
  73. Integer.MAX_VALUE :
  74. MAX_ARRAY_SIZE;
  75. }

4.8LinkedList的原理

1、LinkedList是一个双向链表。

2、内置一个Node节点类,来进行链接。

3、会保存链表的首尾节点。

4、当添加元素时,会将元素保存为一个节点(Node),并设置节点相应的前后节点,以及根据情况修改LinkedList首尾节点。

  1. // LinkedList的内部类,用来保存一个节点
  2. private static class Node<E> {
  3. E item; // 元素
  4. Node<E> next; // 下一个节点
  5. Node<E> prev; // 上一个节点
  6. // 有参构造方法,每次创建对象时必须指定上一个节点,当前元素,下一个节点
  7. Node(Node<E> prev, E element, Node<E> next) {
  8. this.item = element;
  9. this.next = next;
  10. this.prev = prev;
  11. }
  12. }
  13. transient Node<E> first; // 第一个节点
  14. transient Node<E> last; // 最后一个节点
  15. // 无参构造方法
  16. public LinkedList() {
  17. }
  18. // 添加,默认在尾部添加
  19. public boolean add(E e) {
  20. linkLast(e);
  21. return true;
  22. }
  23. // 在尾部添加
  24. public void addLast(E e) {
  25. linkLast(e);
  26. }
  27. //
  28. void linkLast(E e) {
  29. // 记住最后一个节点
  30. final Node<E> l = last;
  31. // 创建一个新的节点,将上一个节点指定为原来的最后一个节点,下一个节点指定为null
  32. final Node<E> newNode = new Node<>(l, e, null);
  33. // 将自己指定为当前LinkedList的尾部(最后一个节点)
  34. last = newNode;
  35. // 如果上一个节点为空,则将当前节点也设为第一个节点(首部)
  36. if (l == null)
  37. first = newNode;
  38. else
  39. // 如果上一个节点不为空,则将原来的最后一个节点的下一个节点指定为当前节点
  40. l.next = newNode;
  41. size++;
  42. modCount++;
  43. }

五、泛型以及集合

5.1 泛型

泛型是对类型的一种约束。作用是在使用时会自动转成相应的类型,还会对不是该类型的地方进行检查,会有相应的编译错误提示。

5.2 泛型集合

在集合创建时使用泛型(推荐使用),会在向集合中添加元素时对元素进行检查, 如果类型不一致会出现编译错误的提示,在访问时(获取)会自动转型,避免强制转换的麻烦。

5.3 泛型的语法

泛型分为类泛型和方法泛型。

  • 类泛型

    • 在类定义时使用泛型(可以使用多个),类中的任意位置都可以使用该定义的泛型
  1. // T是一个可变化的类型,用户在使用时传入的是什么类型,就会变化成什么类型
  2. // 如果用户没有传入,则就是Object类型
  3. // 在当前类中,只能当为Object使用,因为不确定用户会传入什么类型
  4. // T也可以使用任意字母代替
  5. // 多个类型之间可以使用逗号隔开
  6. public class MyClass <T, P>{
  7. public T test(T obj) {
  8. T t;
  9. return null;
  10. }
  11. public T test1(T obj1, P obj2) {
  12. return null;
  13. }
  14. }
  15. public class TestMain1 {
  16. public static void main(String[] args) {
  17. MyClass<String, Integer> c = new MyClass<String, Integer>();
  18. String str = c.test("2");
  19. String str1 = c.test1("2", 3);
  20. }
  21. }
  • 方法泛型

    • 是指在类上面不定义泛型,但是在方法中定义并使用泛型
  1. public class MyClass1 {
  2. // 在方法上定义并使用泛型
  3. public <T> T test(T obj) {
  4. T t;
  5. return null;
  6. }
  7. // 在方法中定义的泛型,在其他方法中不能使用
  8. // public T test1() {
  9. //
  10. // }
  11. }
  12. public class TestMain1 {
  13. public static void main(String[] args) {
  14. MyClass1 c1 = new MyClass1();
  15. // 由于在方法中定义了泛型,且要求传入参数与返回值类型一致,所以也可以不需要强制转换,会自动检查类型
  16. Date str2 = c1.test(new Date());
  17. }
  18. }

经典面试题:

为什么说Java中的泛型是伪(假)泛型?

1、Java中的泛型仅对传入的参数进行编译检查,不会真的对参数进行处理。

2、Java中的泛型仅对对象使用时进行强制转换,不会真的对对象进行转型处理。

3、在对象真正使用的过程中,仍旧是Object类型。

5.4 泛型中的super和extends

当作为参数时,传入的参数根据extends和super的不同,可以传入更宽泛的类型。

? extends B 表示可以传入B类或者B类的子类 ,称为上界限定符

? super B 表示可以传入B类或者B类的父类,称为下界限定符

在使用时,上界不存,下界不取

使用上界时,该集合不能存储数据,可以取值

使用下界时,集合不能取值,但是可以存储数据

  1. public class TestMain2 {
  2. public static void main(String[] args) {
  3. ArrayList<B> list = new ArrayList<B>();
  4. addAll(list);
  5. }
  6. // ? extends B 表示B类或者B类的子类 上界限定符
  7. // ? super B 表示B类或者B类的父类 下界限定符
  8. // 相对于直接写B类,类型会宽泛一些
  9. // 上界不存,下界不取
  10. // 使用上界时,集合不能存储数据,可以取值
  11. // 使用下界时,集合不能取值,但是可以存储数据
  12. public static void addAll(ArrayList<? super B> c) {
  13. // 添加元素(存)
  14. // c.add(new B());
  15. // 循环(取)
  16. // for (B b : c) {
  17. // System.out.println(b);
  18. // }
  19. }
  20. }

六、Collections工具类

Collections针对于集合的帮助类,相当于Arrays是针对于数组的帮助类。

主要是对集合的排序、打乱、截取等操作。

  1. public class TestMain3 {
  2. public static void main(String[] args) {
  3. // sort
  4. ArrayList<Integer> list = new ArrayList<>();
  5. list.add(23);
  6. list.add(35);
  7. list.add(12);
  8. list.add(8);
  9. list.add(10);
  10. list.add(45);
  11. list.add(14);
  12. Collections.sort(list); // 对集合进行排序
  13. System.out.println(list);
  14. Collections.reverse(list); // 对集合进行反转
  15. System.out.println(list);
  16. Collections.shuffle(list);// 随机打乱一个集合
  17. System.out.println(list);
  18. // 如果已知元素,可以使用asList方法将其转换成集合,但是不能强转成ArrayList,因为它是List接口
  19. // 一般用作循环使用
  20. List<Integer> list1 = Arrays.asList(23, 35, 12, 8, 10, 45, 14);
  21. System.out.println(list1);
  22. Collections.sort(list1); // 对集合进行排序
  23. System.out.println(list1);
  24. Collections.reverse(list1); // 对集合进行反转
  25. System.out.println(list1);
  26. Collections.shuffle(list1);// 随机打乱一个集合
  27. System.out.println(list1);
  28. }
  29. }

面试题:

Collection与Collections的区别:

Collection是List和Set的父接口,无序,无下标。

Collections是一个集合的帮助类。提供一系列的静态方法对集合进行操作。

七、Set集合

无序、无下标,元素不能重复。

7.1 HashSet

通过hashCode实现元素不可重复。

当存入的元素hashCode相同时,会使用equals方法来判断是否相同元素,如果不相同,则可以存入。

注意:

HashSet判断不同对象的步骤是:

1、先判断hashCode是否相同,如果不同,直接认定是不同对象,如果相同,进行第二步。

2、判断equals是否相同,如果不同,才认定是不同对象,如果相同,认定为相同对象,会直接放弃添加。

所以,在使用Hashset时,如果要存入实体类的对象,一定要对该实体类进行重写equals和hashCode方法。

注意:HashSet底层使用的HashMap,创建HashSet时会创建一个HashMap,每次添加元素时,就是向HashMap的key上面进行添加。

  1. public class TestMain {
  2. public static void main(String[] args) {
  3. ArrayList<String> list = new ArrayList<>();
  4. // 六个都会存入
  5. list.add("c");
  6. list.add("b");
  7. list.add("a");
  8. list.add("a");
  9. list.add("b");
  10. list.add("c");
  11. System.out.println(list);
  12. HashSet<String> set = new HashSet<String>();
  13. // 只存入了A、B、C
  14. set.add("B");
  15. set.add("C");
  16. set.add("A");
  17. set.add("B");
  18. set.add("C");
  19. set.add("A");
  20. System.out.println(set);
  21. }
  22. }
  23. public class Student {
  24. private String name;
  25. public Student(String name) {
  26. super();
  27. this.name = name;
  28. }
  29. @Override
  30. public String toString() {
  31. return "Student [name=" + name + "]";
  32. }
  33. // 如果hashCode和equals都相同,无论是否同一个对象,都会认定为同一个,不会存入
  34. @Override
  35. public int hashCode() {
  36. return 1;
  37. }
  38. @Override
  39. public boolean equals(Object obj) {
  40. return true;
  41. }
  42. }
  43. public class TestMain1 {
  44. public static void main(String[] args) {
  45. HashSet<Student> set = new HashSet<Student>();
  46. // 当hashCode和equals都认定相同时,只会存入张三
  47. set.add(new Student("张三"));
  48. set.add(new Student("李四"));
  49. System.out.println(set);
  50. }
  51. }

7.2 LinkedHashSet

LinkedHashSet继承自HashSet。

HashSet一般情况下使用HashMap作为底层,而LinkedHashSet使用的LinkedHashMap作为底层,在HashMap的基础上添加了链表的结构,来保存了添加的顺序。

  1. public class TestMain2 {
  2. public static void main(String[] args) {
  3. LinkedHashSet<String> set = new LinkedHashSet<>();
  4. set.add("B");
  5. set.add("C");
  6. set.add("A");
  7. // 输出结果为:B-->C-->A
  8. for (String string : set) {
  9. System.out.println(string);
  10. }
  11. }
  12. }

7.3 TreeSet

实现了SortedSet接口,自动排序,根据自定的方式(基于Comparable接口)排序。

注意:使用TreeSet来存储元素时一定要指定排序的方式,否则会报错。

二叉树:

一个节点,最多只能有两个直接的子节点。

TreeSet进行排序的规则要求实现Comparable接口,如果是自定义的类,要将对象放入到TreeSet中,必须实现该接口,否则会出现ClassCastException。

  1. public class Student implements Comparable<Student>{
  2. private String name;
  3. private int age;
  4. public Student(String name) {
  5. super();
  6. this.name = name;
  7. }
  8. public Student(String name, int age) {
  9. super();
  10. this.name = name;
  11. this.age = age;
  12. }
  13. @Override
  14. public String toString() {
  15. return "Student [name=" + name + ", age=" + age + "]";
  16. }
  17. // 如果hashCode和equals都相同,无论是否同一个对象,都会认定为同一个,不会存入
  18. @Override
  19. public int hashCode() {
  20. return 1;
  21. }
  22. @Override
  23. public boolean equals(Object obj) {
  24. return true;
  25. }
  26. @Override
  27. public int compareTo(Student o) {
  28. // if(this.age - o.age < 0) {
  29. // return -1;
  30. // }else if(this.age - o.age > 0) {
  31. // return 1;
  32. // }
  33. // return 0;
  34. return this.age - o.age;
  35. }
  36. }
  1. public class TestMain3 {
  2. public static void main(String[] args) {
  3. TreeSet<Integer> set = new TreeSet<Integer>();
  4. set.add(20);
  5. set.add(15);
  6. set.add(30);
  7. set.add(18);
  8. set.add(12);
  9. set.add(14);
  10. set.add(10);
  11. set.add(30); // 与前面添加的30相等,放弃添加,保证元素不重复
  12. System.out.println(set);
  13. // 要使用TreeSet来添加自定义类的对象,需要实现比较接口
  14. TreeSet<Student> set1 = new TreeSet<Student>();
  15. set1.add(new Student("张三", 20));
  16. set1.add(new Student("李四", 18));
  17. set1.add(new Student("王五", 19));
  18. set1.add(new Student("赵六", 22));
  19. System.out.println(set1);
  20. }
  21. }

上面使用的Collections.sort方法进行排序时,如果是自定义对象,也需要实现Comparable接口。

  1. public class TestMain3 {
  2. public static void main(String[] args) {
  3. ArrayList<Student> list = new ArrayList<Student>();
  4. list.add(new Student("张三", 20));
  5. list.add(new Student("李四", 18));
  6. list.add(new Student("王五", 19));
  7. list.add(new Student("赵六", 22));
  8. Collections.sort(list);
  9. System.out.println(list);
  10. }
  11. }

注意:Comparable接口中的compareTo方法,返回负数会放到树的左边,正数放到树的右边,返回0表示元素相等,会放弃添加。

八、Map体系

8.1 Map结构

Map是一个顶层接口,常用的实现类有HashMap、TreeMap等。

Map结构特点是:使用键值(key,value)对来存放数据。key键要求不能重复,value值可以重复。

  1. // 常用方法:
  2. put(Object key, Object value); // 添加
  3. Object get(Object key); // 获取元素
  4. Set keySet(); // 返回所有的键集
  5. Collection values(); // 返回所有的值

注意:map存取元素时,需要使用key来访问,key可以是任意类型,但是推荐使用字符串。当使用相同的key存放时,会覆盖原来的内容。

8.2 HashMap基本使用

  1. public class TestMain {
  2. // HashMap的用法
  3. public static void main(String[] args) {
  4. // 创建对象
  5. HashMap<String, Student> map = new HashMap<String, Student>();
  6. // 添加元素
  7. map.put("a", new Student("1", "张三", 20));
  8. map.put("b", new Student("2", "李四", 21));
  9. map.put("c", new Student("3", "王五", 22));
  10. // 获取元素
  11. Student stu = map.get("b");
  12. System.out.println(stu);
  13. // 遍历元素
  14. // 1、借助遍历key来遍历值
  15. // 得到keys键集
  16. Set<String> set = map.keySet();
  17. for (String key : set) {
  18. System.out.println("key为:" + key + ",对应的值为:" + map.get(key));
  19. }
  20. // 2、直接获取值集合
  21. Collection<Student> colls = map.values();
  22. for (Student student : colls) {
  23. System.out.println("值为:" + student);
  24. }
  25. // 3、在遍历时获得键值
  26. Set<Map.Entry<String, Student>> entrySet = map.entrySet();
  27. for (Map.Entry<String, Student> entry : entrySet) {
  28. System.out.println("key为:" + entry.getKey() + ",对应的值为:" + entry.getValue());
  29. }
  30. // 删除元素
  31. map.remove("a");
  32. int size = map.size(); // 得到元素个数
  33. System.out.println(size);
  34. }
  35. }

经典面试题:

HashMap与Hashtable的区别:(相同点:使用方法基本一样)

1、HashMap性能较高,但是线程不安全,Hashtable线程安全,性能极低,基本不使用。

2、HashMap键和值都可以为null,但是键不能重复,会覆盖。Hashtable键和值都不能为null。

8.3 HashMap的底层原理

既有比较快的查找性能,又有较好的增删性能。

采用数组+链表+红黑树(JDK1.8后)的结构。

1、创建HashMap时,指定了扩容因子为0.75

2、在第一次添加元素时,才会创建数组,大小为16,设置临界点12

3、添加元素时,如果key相同,会覆盖值,会根据key的hash计算,并与数组的长度求模,得到该元素存放的下标,如果该下标上有元素,就直接放到链表最后一个元素,如果此时链表数量大于等于7,变化为红黑树。

注意:红黑树的变化条件有两个:1、元素总数量大于64,2、链表上的元素大于等于7

4、当元素添加到临界点时,再添加元素时,会进行扩容,扩容为原来的2倍,临界点重新设置为数组大小*0.75

5、当扩容时,数组大小大于2^30,则指定int的最大值。

  1. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始大小
  2. static final int MAXIMUM_CAPACITY = 1 << 30; // 最大空间2^30
  3. static final float DEFAULT_LOAD_FACTOR = 0.75f; // 扩容因子
  4. static final int TREEIFY_THRESHOLD = 8; // 变成树的临界值
  5. static final int UNTREEIFY_THRESHOLD = 6; // 变成链表的临界值
  6. static final int MIN_TREEIFY_CAPACITY = 64; // 变成的最小大小
  7. // map中的链表结构(单向链表)
  8. static class Node<K,V> implements Map.Entry<K,V> {
  9. final int hash; // 哈希值
  10. final K key;
  11. V value;
  12. Node<K,V> next;
  13. Node(int hash, K key, V value, Node<K,V> next) {
  14. this.hash = hash;
  15. this.key = key;
  16. this.value = value;
  17. this.next = next;
  18. }
  19. public final K getKey() { return key; }
  20. public final V getValue() { return value; }
  21. public final String toString() { return key + "=" + value; }
  22. public final int hashCode() {
  23. return Objects.hashCode(key) ^ Objects.hashCode(value);
  24. }
  25. public final V setValue(V newValue) {
  26. V oldValue = value;
  27. value = newValue;
  28. return oldValue;
  29. }
  30. public final boolean equals(Object o) {
  31. if (o == this)
  32. return true;
  33. if (o instanceof Map.Entry) {
  34. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  35. if (Objects.equals(key, e.getKey()) &&
  36. Objects.equals(value, e.getValue()))
  37. return true;
  38. }
  39. return false;
  40. }
  41. }
  42. transient Node<K,V>[] table; // map中存放数据的数组,每个元素都是一个链表
  43. // hash计算
  44. static final int hash(Object key) {
  45. int h;
  46. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  47. }
  48. // 无参构造
  49. public HashMap() {
  50. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  51. }
  52. public V put(K key, V value) {
  53. return putVal(hash(key), key, value, false, true);
  54. }
  55. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  56. boolean evict) {
  57. Node<K,V>[] tab; Node<K,V> p; int n, i;
  58. // 如果是第一次添加元素(table为null),或者table中的元素数量为0
  59. if ((tab = table) == null || (n = tab.length) == 0)
  60. // 设置n为16
  61. n = (tab = resize()).length;
  62. // hash值求模运算,如果原来的数组的首元素为空,直接赋值
  63. if ((p = tab[i = (n - 1) & hash]) == null)
  64. tab[i] = newNode(hash, key, value, null);
  65. else {
  66. Node<K,V> e; K k;
  67. // 如果首元素不为空,则判断key的hash与equals是否相同
  68. if (p.hash == hash &&
  69. ((k = p.key) == key || (key != null && key.equals(k))))
  70. // 相同则覆盖元素
  71. e = p;
  72. // 如果不同,并且为树形结构
  73. else if (p instanceof TreeNode)
  74. // 向树型结构中添加元素
  75. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  76. else {
  77. // 向链表中添加元素
  78. for (int binCount = 0; ; ++binCount) {
  79. if ((e = p.next) == null) {
  80. p.next = newNode(hash, key, value, null);
  81. // 如果当前桶上元素数量大于等于7,转成红黑树
  82. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  83. treeifyBin(tab, hash);
  84. break;
  85. }
  86. // key相同则停止循环
  87. if (e.hash == hash &&
  88. ((k = e.key) == key || (key != null && key.equals(k))))
  89. break;
  90. p = e;
  91. }
  92. }
  93. //
  94. if (e != null) { // existing mapping for key
  95. V oldValue = e.value;
  96. if (!onlyIfAbsent || oldValue == null)
  97. e.value = value;
  98. afterNodeAccess(e);
  99. return oldValue;
  100. }
  101. }
  102. ++modCount;
  103. // 如果size大于临界点
  104. if (++size > threshold)
  105. resize(); // 扩容
  106. afterNodeInsertion(evict);
  107. return null;
  108. }
  109. final Node<K,V>[] resize() {
  110. // 记住原来的数组
  111. Node<K,V>[] oldTab = table;
  112. // 如果原来的数组为空,长度即为0,否则为原来的长度
  113. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  114. // 记住原来的临界点空间大小
  115. int oldThr = threshold;
  116. // 新的大小暂为0
  117. int newCap, newThr = 0;
  118. // 如果原来的大小大于0
  119. if (oldCap > 0) {
  120. // 如果原来的大小大于最大大小,直接赋值为最大值
  121. if (oldCap >= MAXIMUM_CAPACITY) {
  122. threshold = Integer.MAX_VALUE;
  123. return oldTab;
  124. }
  125. // 如果原来的大小扩容1倍后小于最大大小并且原来的大小大于等于16
  126. // 大小扩容1倍
  127. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  128. oldCap >= DEFAULT_INITIAL_CAPACITY)
  129. // 临界点扩容1倍
  130. newThr = oldThr << 1; // double threshold
  131. }
  132. // 原来的临界点大于0
  133. else if (oldThr > 0) // initial capacity was placed in threshold
  134. newCap = oldThr; // 新的空间等于老的临界点
  135. else { // zero initial threshold signifies using defaults
  136. newCap = DEFAULT_INITIAL_CAPACITY; // 新的空间大小设置为16
  137. // 新的临界点为12
  138. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  139. }
  140. // 处理极限值
  141. if (newThr == 0) {
  142. float ft = (float)newCap * loadFactor;
  143. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  144. (int)ft : Integer.MAX_VALUE);
  145. }
  146. threshold = newThr; // 将计算的临界值保存属性中
  147. @SuppressWarnings({"rawtypes","unchecked"})
  148. // 创建新的数组大小
  149. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  150. table = newTab; // 保存新的数组到属性中
  151. // 如果原来的数组元素不为空,则重新计算位置
  152. if (oldTab != null) {
  153. for (int j = 0; j < oldCap; ++j) {
  154. Node<K,V> e;
  155. if ((e = oldTab[j]) != null) {
  156. oldTab[j] = null;
  157. if (e.next == null)
  158. newTab[e.hash & (newCap - 1)] = e;
  159. else if (e instanceof TreeNode)
  160. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  161. else { // preserve order
  162. Node<K,V> loHead = null, loTail = null;
  163. Node<K,V> hiHead = null, hiTail = null;
  164. Node<K,V> next;
  165. do {
  166. next = e.next;
  167. if ((e.hash & oldCap) == 0) {
  168. if (loTail == null)
  169. loHead = e;
  170. else
  171. loTail.next = e;
  172. loTail = e;
  173. }
  174. else {
  175. if (hiTail == null)
  176. hiHead = e;
  177. else
  178. hiTail.next = e;
  179. hiTail = e;
  180. }
  181. } while ((e = next) != null);
  182. if (loTail != null) {
  183. loTail.next = null;
  184. newTab[j] = loHead;
  185. }
  186. if (hiTail != null) {
  187. hiTail.next = null;
  188. newTab[j + oldCap] = hiHead;
  189. }
  190. }
  191. }
  192. }
  193. }
  194. return newTab;
  195. }

8.4 Properties

Properties继承自Hashtable,但是添加了setProperty和getProperty方法,只能够操作字符串。

作用是用来加载系统的配置文件。所以里面还添加了load方法,专门用来加载配置。

public class TestMain2 {
    // Properties的用法,用来加载资源文件
    public static void main(String[] args) {
        Properties prop = new Properties();
        // 设置值
        prop.setProperty("username", "root");
        prop.setProperty("password", "root");
        prop.setProperty("url", "数据库的url");
        // 获取值
        System.out.println(prop.getProperty("username"));
    }
}

8.5 TreeMap

TreeMap使用树的方式来存放数据,需要对Key对应的类实现Comparable接口。当添加后,会自动排序。

public class Student implements Comparable<Student>{
    private String id;
    private String name;
    private int age;
    public Student(String id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
    @Override
    public int compareTo(Student o) {
        return this.id.hashCode() - o.id.hashCode();
    }
}

public class TestMain3 {
    // TreeMap的用法
    public static void main(String[] args) {
        TreeMap<Student, String> map = new TreeMap<Student, String>();
        map.put(new Student("2", "张三2", 21), "aaa");
        map.put(new Student("1", "张三1", 22), "bbb");
        map.put(new Student("4", "张三4", 23), "ccc");
        map.put(new Student("3", "张三3", 24), "ddd");
        System.out.println(map);
    }
}