第十六章 集合

1. 集合继承结构图

集合继承结构图.png

1.1 集合的基本概念

  • 数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据
  • 集合不能直接存储基本数据类型(list.add(100); //自动装箱Integer),另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址(或者说集合中存储的是引用)
  • 所有的集合类和集合接口都在java.util包下
  • 在java中集合分为两大类:

    一类是单个方式存储元素:
    单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
    一类是以键值对儿的方式存储元素
    以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

2. Map集合继承结构图

Map集合继承结构图.png

3. collection接口常用方法

3.1 Collection中能存放什么元素

没有使用“泛型”之前,Collection中可以存储Object的所有子类型。使用了“泛型”之后,Collection中只能存储某个具体的类型。集合后期我们会学习“泛型”语法。目前先不用管。Collection中什么都能存,只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)

3.2 Collection中的常用方法

boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【了解】

3.3 集合遍历/迭代

  1. public class CollectionTest02 {
  2. public static void main(String[] args) {
  3. // 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
  4. // 在Map集合中不能用。在所有的Collection以及子类中使用。
  5. // 创建集合对象
  6. Collection c = new ArrayList(); // 后面的集合无所谓,主要是看前面的Collection接口,怎么遍历/迭代。
  7. // 添加元素
  8. c.add("abc");
  9. c.add("def");
  10. c.add(100);
  11. c.add(new Object());
  12. // 对集合Collection进行遍历/迭代
  13. // 第一步:获取集合对象的迭代器对象Iterator
  14. Iterator it = c.iterator();
  15. // 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
  16. /*
  17. 以下两个方法是迭代器对象Iterator中的方法:
  18. boolean hasNext()如果仍有元素可以迭代,则返回 true。
  19. Object next() 返回迭代的下一个元素。
  20. */
  21. while(it.hasNext()){
  22. Object obj = it.next();
  23. System.out.println(obj);
  24. }
  25. // 一直取,不判断,会出现异常:java.util.NoSuchElementException
  26. /*while(true){
  27. Object obj = it.next();
  28. System.out.println(obj);
  29. }*/
  30. /*boolean hasNext = it.hasNext();
  31. System.out.println(hasNext);
  32. if(hasNext) {
  33. // 不管你当初存进去什么,取出来统一都是Object。
  34. Object obj = it.next();
  35. System.out.println(obj);
  36. }*/
  37. }
  38. }
  1. public class CollectionTest03 {
  2. public static void main(String[] args) {
  3. // 创建集合对象
  4. Collection c1 = new ArrayList(); // ArrayList集合:有序可重复
  5. // 添加元素
  6. c1.add(1);
  7. c1.add(2);
  8. c1.add(3);
  9. c1.add(4);
  10. c1.add(1);
  11. // 迭代集合
  12. Iterator it = c1.iterator();
  13. while(it.hasNext()){
  14. // 存进去是什么类型,取出来还是什么类型。
  15. Object obj = it.next();
  16. /*if(obj instanceof Integer){
  17. System.out.println("Integer类型");
  18. }*/
  19. // 只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法。
  20. System.out.println(obj);
  21. }
  22. // HashSet集合:无序不可重复
  23. Collection c2 = new HashSet();
  24. // 无序:存进去和取出的顺序不一定相同。
  25. // 不可重复:存储100,不能再存储100.
  26. c2.add(100);
  27. c2.add(200);
  28. c2.add(300);
  29. c2.add(90);
  30. c2.add(400);
  31. c2.add(50);
  32. c2.add(60);
  33. c2.add(100);
  34. Iterator it2 = c2.iterator();
  35. while(it2.hasNext()){
  36. System.out.println(it2.next());
  37. }
  38. }
  39. }

3.4 深入Collection集合的contains方法

  • contains方法是用来判断集合中是否包含某个元素的方法,那么它在底层是怎么判断集合中是否包含某个元素的呢:调用了equals方法进行比对,equals方法返回true,就表示包含这个元素。 ```java / 测试contains方法 测试remove方法。 结论:存放在一个集合中的类型,一定要重写equals方法。 / public class CollectionTest05 { public static void main(String[] args) {

    1. // 创建集合对象
    2. Collection c = new ArrayList();
    3. // 创建用户对象
    4. User u1 = new User("jack");
    5. // 加入集合
    6. c.add(u1);
    7. // 判断集合中是否包含u2
    8. User u2 = new User("jack");
    9. // 没有重写equals之前:这个结果是false
    10. //System.out.println(c.contains(u2)); // false
    11. // 重写equals方法之后,比较的时候会比较name。
    12. System.out.println(c.contains(u2)); // true
    13. c.remove(u2);
    14. System.out.println(c.size()); // 0
    15. /*Integer x = new Integer(10000);
    16. c.add(x);
    17. Integer y = new Integer(10000);
    18. System.out.println(c.contains(y)); // true*/
    19. // 创建集合对象
    20. Collection cc = new ArrayList();
    21. // 创建字符串对象
    22. String s1 = new String("hello");
    23. // 加进去。
    24. cc.add(s1);
    25. // 创建了一个新的字符串对象
    26. String s2 = new String("hello");
    27. // 删除s2
    28. cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
    29. // 集合中元素个数是?
    30. System.out.println(cc.size()); // 0

    } }

class User{ private String name; public User(){} public User(String name){ this.name = name; }

  1. // 重写equals方法
  2. // 将来调用equals方法的时候,一定是调用这个重写的equals方法。
  3. // 这个equals方法的比较原理是:只要姓名一样就表示同一个用户。
  4. public boolean equals(Object o) {
  5. if(o == null || !(o instanceof User)) return false;
  6. if(o == this) return true;
  7. User u = (User)o;
  8. // 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)
  9. return u.name.equals(this.name);
  10. }

}

  1. <a name="UXqhu"></a>
  2. ### 3.5 关于集合元素的remove
  3. - 当**集合的结构发生改变时,迭代器必须重新获取**,如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModificationException
  4. - 在迭代集合元素的过程中,不能调用集合对象的remove方法,会出现:java.util.ConcurrentModificationException
  5. - 在迭代元素的过程当中,一定要**使用迭代器Iterator的remove方法,删除元素**,不要使用集合自带的remove方法删除元素。
  6. ```java
  7. public class CollectionTest06 {
  8. public static void main(String[] args) {
  9. // 创建集合
  10. Collection c = new ArrayList();
  11. // 注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
  12. // 一定要注意:集合结构只要发生改变,迭代器必须重新获取。
  13. // 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException
  14. Iterator it = c.iterator();
  15. // 添加元素
  16. c.add(1); // Integer类型
  17. c.add(2);
  18. c.add(3);
  19. // 获取迭代器
  20. //Iterator it = c.iterator();
  21. /*while(it.hasNext()){
  22. // 编写代码时next()方法返回值类型必须是Object。
  23. // Integer i = it.next();
  24. Object obj = it.next();
  25. System.out.println(obj);
  26. }*/
  27. Collection c2 = new ArrayList();
  28. c2.add("abc");
  29. c2.add("def");
  30. c2.add("xyz");
  31. Iterator it2 = c2.iterator();
  32. while(it2.hasNext()){
  33. Object o = it2.next();
  34. // 删除元素
  35. // 删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
  36. // 但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException
  37. // 出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)
  38. //c2.remove(o); // 直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。)
  39. // 使用迭代器来删除可以吗?
  40. // 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。
  41. it2.remove(); // 删除的一定是迭代器指向的当前元素。
  42. System.out.println(o);
  43. }
  44. System.out.println(c2.size()); //0
  45. }
  46. }

4. List接口中常用方法

  • List集合存储元素特点:有序可重复

    1. 有序:List集合中的元素有下标。<br /> 0开始,以1递增。<br /> 可重复:存储一个1,还可以再存储1.
  • List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法:

    1. 以下只列出List接口特有的常用的方法:<br /> void add(int index, Object element) // 用的不多效率低<br /> Object set(int index, Object element)<br /> Object get(int index)<br /> int indexOf(Object o)<br /> int lastIndexOf(Object o)<br /> Object remove(int index)
  • 因为有下标,所以List集合有自己比较特殊的遍历方式,通过下标遍历。【List集合特有的方式,Set没有。】

4.1 ArrayList集合

1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)

2、集合底层是一个Object[]数组。

  1. 3、构造方法:<br /> new ArrayList();<br /> new ArrayList(20);
  2. 4ArrayList集合的扩容:增长到**原容量的1.5倍**。<br />ArrayList集合底层是数组,怎么优化?<br />尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。

5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)

6、数组缺点:
随机增删元素效率比较低,另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)

7、向数组末尾添加元素,效率很高,不受影响。

  1. public class ArrayListTest02 {
  2. public static void main(String[] args) {
  3. // 默认初始化容量10
  4. List myList1 = new ArrayList();
  5. // 指定初始化容量100
  6. List myList2 = new ArrayList(100);
  7. // 创建一个HashSet集合
  8. Collection c = new HashSet();
  9. // 添加元素到Set集合
  10. c.add(100);
  11. c.add(200);
  12. c.add(900);
  13. c.add(50);
  14. // 通过这个构造方法就可以将HashSet集合转换成List集合。
  15. List myList3 = new ArrayList(c);
  16. for(int i = 0; i < myList3.size(); i++){
  17. System.out.println(myList3.get(i));
  18. }
  19. }
  20. }

链表的优点: 由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高,在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。 链表的缺点: 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。

4.2 LinkList源码分析

  1. public class LinkedListTest01 {
  2. public static void main(String[] args) {
  3. // LinkedList集合底层也是有下标的。
  4. // 注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
  5. // LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
  6. List list = new LinkedList();
  7. list.add("a");
  8. list.add("b");
  9. list.add("c");
  10. for(int i = 0; i <list.size(); i++){
  11. Object obj = list.get(i);
  12. System.out.println(obj);
  13. }
  14. // LinkedList集合有初始化容量吗?没有。
  15. // 最初这个链表中没有任何元素。first和last引用都是null。
  16. // 不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
  17. // 因为我们要面向接口编程,调用的方法都是接口中的方法。
  18. //List list2 = new ArrayList(); // 这样写表示底层你用了数组。
  19. List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。
  20. // 以下这些方法你面向的都是接口编程。
  21. list2.add("123");
  22. list2.add("456");
  23. list2.add("789");
  24. for(int i = 0; i < list2.size(); i++){
  25. System.out.println(list2.get(i));
  26. }
  27. }
  28. }

5. vector

1、底层也是一个数组

2、初始化容量:10

3、怎么扩容的?
扩容之后是原容量的2倍

4、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少了。

5、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;
java.util.Collection 是集合接口。
java.util.Collections 是集合工具类。

  1. public class VectorTest {
  2. public static void main(String[] args) {
  3. // 创建一个Vector集合
  4. List vector = new Vector();
  5. //Vector vector = new Vector();
  6. // 添加元素
  7. // 默认容量10个。
  8. vector.add(1);
  9. vector.add(2);
  10. vector.add(3);
  11. vector.add(4);
  12. vector.add(5);
  13. vector.add(6);
  14. vector.add(7);
  15. vector.add(8);
  16. vector.add(9);
  17. vector.add(10);
  18. // 满了之后扩容(扩容之后的容量是20.)
  19. vector.add(11);
  20. Iterator it = vector.iterator();
  21. while(it.hasNext()){
  22. Object obj = it.next();
  23. System.out.println(obj);
  24. }
  25. // 这个可能以后要使用!!!!
  26. List myList = new ArrayList(); // 非线程安全的。
  27. // 变成线程安全的
  28. Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!
  29. // myList集合就是线程安全的了。
  30. myList.add("111");
  31. myList.add("222");
  32. myList.add("333");
  33. }
  34. }

建议先学习第十七章的泛型再看一下内容

6. HashSet

1、存储时顺序和取出的顺序不同
2、不可重复。(存入多个相同的内容,存放到集合中的只有一个)
3、放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。

7. Map接口

1、Map和Collection没有继承关系。

2、Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址
key起到主导的地位,value是key的一个附属品

3、Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection values() 获取Map集合中所有的value,返回一个Collection
Set keySet() 获取Map集合所有的key(所有的键是一个set集合)
Set> entrySet() //Map.Entry是一个静态内部类

将Map集合转换成Set集合 假设现在有一个Map集合,如下所示: map1集合对象

key value

1 zhangsan 2 lisi 3 wangwu 4 zhaoliu Set set = map1.entrySet(); set集合对象 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry】 2=lisi 【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】 3=wangwu 4=zhaoliu —-> 这个东西是个什么?Map.Entry

image.png

7.1 Map集合遍历

  1. public class MapTest02 {
  2. public static void main(String[] args) {
  3. // 第一种方式:获取所有的key,通过遍历key,来遍历value
  4. Map<Integer, String> map = new HashMap<>();
  5. map.put(1, "zhangsan");
  6. map.put(2, "lisi");
  7. map.put(3, "wangwu");
  8. map.put(4, "zhaoliu");
  9. // 遍历Map集合
  10. // 获取所有的key,所有的key是一个Set集合
  11. Set<Integer> keys = map.keySet();
  12. // 遍历key,通过key获取value
  13. // 迭代器可以
  14. /*Iterator<Integer> it = keys.iterator();
  15. while(it.hasNext()){
  16. // 取出其中一个key
  17. Integer key = it.next();
  18. // 通过key获取value
  19. String value = map.get(key);
  20. System.out.println(key + "=" + value);
  21. }*/
  22. // foreach也可以
  23. for(Integer key : keys){
  24. System.out.println(key + "=" + map.get(key));
  25. }
  26. // 第二种方式:Set<Map.Entry<K,V>> entrySet()
  27. // 以上这个方法是把Map集合直接全部转换成Set集合。
  28. // Set集合中元素的类型是:Map.Entry
  29. Set<Map.Entry<Integer,String>> set = map.entrySet();
  30. // 遍历Set集合,每一次取出一个Node
  31. // 迭代器
  32. /*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
  33. while(it2.hasNext()){
  34. Map.Entry<Integer,String> node = it2.next();
  35. Integer key = node.getKey();
  36. String value = node.getValue();
  37. System.out.println(key + "=" + value);
  38. }*/
  39. // foreach
  40. // 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
  41. // 这种方式比较适合于大数据量。
  42. for(Map.Entry<Integer,String> node : set){
  43. System.out.println(node.getKey() + "--->" + node.getValue());
  44. }
  45. }
  46. }

8. hashMap

8.1 哈希表数据结构

image.png

8.2 哈希表

  1. - Hashtablekey可以为null吗?
  2. **Hashtablekeyvalue都是不能为nullHashMap集合的keyvalue都是可以为null**
  3. - Hashtable方法都带有synchronized:**线程安全**的。

线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。

  1. - HashtableHashMap一样,底层都是哈希表数据结构。

Hashtable的初始化容量是11,默认加载因子是:0.75f,Hashtable的扩容是:原容量 * 2 + 1

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. /*
  4. 1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
  5. equals方法有可能调用,也有可能不调用。
  6. 拿put(k,v)举例,什么时候equals不会调用?
  7. k.hashCode()方法返回哈希值,
  8. 哈希值经过哈希算法转换成数组下标。
  9. 数组下标位置上如果是null,equals不需要执行。
  10. 拿get(k)举例,什么时候equals不会调用?
  11. k.hashCode()方法返回哈希值,
  12. 哈希值经过哈希算法转换成数组下标。
  13. 数组下标位置上如果是null,equals不需要执行。
  14. 2、注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
  15. 并且equals方法返回如果是true,hashCode()方法返回的值必须一样。
  16. equals方法返回true表示两个对象相同,在同一个单向链表上比较。
  17. 那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
  18. 所以hashCode()方法的返回值也应该相同。
  19. 3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。
  20. 4、终极结论:
  21. 放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
  22. 5、对于哈希表数据结构来说:
  23. 如果o1和o2的hash值相同,一定是放到同一个单向链表上。
  24. 当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
  25. */
  26. public class HashMapTest02 {
  27. public static void main(String[] args) {
  28. Student s1 = new Student("zhangsan");
  29. Student s2 = new Student("zhangsan");
  30. // 重写equals方法之前是false
  31. //System.out.println(s1.equals(s2)); // false
  32. // 重写equals方法之后是true
  33. System.out.println(s1.equals(s2)); //true (s1和s2表示相等)
  34. System.out.println("s1的hashCode=" + s1.hashCode()); //284720968 (重写hashCode之后-1432604525)
  35. System.out.println("s2的hashCode=" + s2.hashCode()); //122883338 (重写hashCode之后-1432604525)
  36. // s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
  37. // 按说只能放进去1个。(HashSet集合特点:无序不可重复)
  38. Set<Student> students = new HashSet<>();
  39. students.add(s1);
  40. students.add(s2);
  41. // 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。
  42. //怎么办?---> 重写hashcode方法
  43. System.out.println(students.size());
  44. }
  45. }
  1. import java.util.HashMap;
  2. import java.util.Map;
  3. /*
  4. HashMap集合key部分允许null吗?
  5. 允许
  6. 但是要注意:HashMap集合的key null值只能有一个。
  7. 有可能面试的时候遇到这样的问题。
  8. */
  9. public class HashMapTest03 {
  10. public static void main(String[] args) {
  11. Map map = new HashMap();
  12. // HashMap集合允许key为null
  13. map.put(null, null);
  14. System.out.println(map.size()); // 1
  15. // key重复的话value是覆盖!
  16. map.put(null, 100);
  17. System.out.println(map.size()); //1
  18. // 通过key获取value
  19. System.out.println(map.get(null)); // 100
  20. }
  21. }

8.3 hashMap

1、HashMap集合底层是哈希表/散列表的数据结构

2、哈希表是一个怎样的数据结构呢?
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表是一个数组和单向链表的结合体,哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。

3、HashMap集合底层的源代码:

  1. public class HashMap{
  2. // HashMap底层实际上就是一个数组。(一维数组)
  3. Node<K,V>[] table;
  4. // 静态的内部类HashMap.Node
  5. static class Node<K,V> {
  6. // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,
  7. //可以转换存储成数组的下标。)
  8. final int hash;
  9. final K key; // 存储到Map集合中的那个key
  10. V value; // 存储到Map集合中的那个value
  11. Node<K,V> next; // 下一个节点的内存地址。
  12. }
  13. }

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)

4、这两个方法的实现原理,是必须掌握的
map.put(k,v)
v = map.get(k)

5、HashMap集合的key部分特点:
无序,不可重复

为什么无序? 因为不一定挂到哪个单向链表上。 不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。放在HashMap集合key部分的元素其实就是放到HashSet集合中了。所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

6、哈希表HashMap使用不当时无法发挥性能
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们成为:散列分布不均匀

什么是散列分布均匀? 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题? 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了,也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。


7、HashMap集合的默认初始化容量是16,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。重点,记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。

重点放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法

9. TreeSet

1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合

2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标

3、TreeSet集合底层实际上是一个TreeMap,TreeMap集合底层是一个二叉树

4、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了

5、对自定义的类型来说,TreeSet可以排序吗?没有指定Person对象之间的比较规则,无法排序

6、放在TreeSet集合中的元素需要实现java.lang.Comparable接口;并且实现compareTo方法(equals可以不写),不然可能出现java.lang.ClassCastException … cannot be cast to class java.lang.Comparable

  1. import java.util.TreeSet;
  2. public class TreeSetTest04 {
  3. public static void main(String[] args) {
  4. Customer c1 = new Customer(32);
  5. Customer c2 = new Customer(20);
  6. Customer c3 = new Customer(30);
  7. Customer c4 = new Customer(25);
  8. // 创建TreeSet集合
  9. TreeSet<Customer> customers = new TreeSet<>();
  10. // 添加元素
  11. customers.add(c1);
  12. customers.add(c2);
  13. customers.add(c3);
  14. customers.add(c4);
  15. // 遍历
  16. for (Customer c : customers){
  17. System.out.println(c);
  18. }
  19. }
  20. }
  21. // 放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
  22. // 并且实现compareTo方法。equals可以不写。
  23. class Customer implements Comparable<Customer>{
  24. int age;
  25. public Customer(int age){
  26. this.age = age;
  27. }
  28. // 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
  29. // k.compareTo(t.key)
  30. // 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
  31. // 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
  32. @Override
  33. public int compareTo(Customer c) { // c1.compareTo(c2);
  34. // this是c1
  35. // c是c2
  36. // c1和c2比较的时候,就是this和c比较。
  37. /*int age1 = this.age;
  38. int age2 = c.age;
  39. if(age1 == age2){
  40. return 0;
  41. } else if(age1 > age2) {
  42. return 1;
  43. } else {
  44. return -1;
  45. }*/
  46. //return this.age - c.age; // =0 >0 <0
  47. return c.age - this.age;
  48. }
  49. public String toString(){
  50. return "Customer[age="+age+"]";
  51. }
  52. }
  1. import java.util.TreeSet;
  2. /*
  3. 先按照年龄升序,如果年龄一样的再按照姓名升序。
  4. */
  5. public class TreeSetTest05 {
  6. public static void main(String[] args) {
  7. TreeSet<Vip> vips = new TreeSet<>();
  8. vips.add(new Vip("zhangsi", 20));
  9. vips.add(new Vip("zhangsan", 20));
  10. vips.add(new Vip("king", 18));
  11. vips.add(new Vip("soft", 17));
  12. for(Vip vip : vips){
  13. System.out.println(vip);
  14. }
  15. }
  16. }
  17. class Vip implements Comparable<Vip>{
  18. String name;
  19. int age;
  20. public Vip(String name, int age) {
  21. this.name = name;
  22. this.age = age;
  23. }
  24. @Override
  25. public String toString() {
  26. return "Vip{" +
  27. "name='" + name + '\'' +
  28. ", age=" + age +
  29. '}';
  30. }
  31. /*
  32. compareTo方法的返回值很重要:
  33. 返回0表示相同,value会覆盖。
  34. 返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
  35. 返回<0,会继续在左子树上找。
  36. */
  37. @Override
  38. public int compareTo(Vip v) {
  39. // 写排序规则,按照什么进行比较。
  40. if(this.age == v.age){
  41. // 年龄相同时按照名字排序。
  42. // 姓名是String类型,可以直接比。调用compareTo来完成比较。
  43. return this.name.compareTo(v.name);
  44. } else {
  45. // 年龄不一样
  46. return this.age - v.age;
  47. }
  48. }
  49. }
  1. import java.util.Comparator;
  2. import java.util.TreeSet;
  3. /*
  4. TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
  5. 最终的结论:
  6. 放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
  7. 第一种:放在集合中的元素实现java.lang.Comparable接口。
  8. 第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
  9. Comparable和Comparator怎么选择呢?
  10. 当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
  11. 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
  12. Comparator接口的设计符合OCP原则。
  13. */
  14. public class TreeSetTest06 {
  15. public static void main(String[] args) {
  16. // 创建TreeSet集合的时候,需要使用这个比较器。
  17. // TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。
  18. // 给构造方法传递一个比较器。
  19. //TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
  20. // 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
  21. TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
  22. @Override
  23. public int compare(WuGui o1, WuGui o2) {
  24. return o1.age - o2.age;
  25. }
  26. });
  27. wuGuis.add(new WuGui(1000));
  28. wuGuis.add(new WuGui(800));
  29. wuGuis.add(new WuGui(810));
  30. for(WuGui wuGui : wuGuis){
  31. System.out.println(wuGui);
  32. }
  33. }
  34. }
  35. // 乌龟
  36. class WuGui{
  37. int age;
  38. public WuGui(int age){
  39. this.age = age;
  40. }
  41. @Override
  42. public String toString() {
  43. return "小乌龟[" +
  44. "age=" + age +
  45. ']';
  46. }
  47. }
  48. // 单独在这里编写一个比较器
  49. // 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
  50. /*
  51. class WuGuiComparator implements Comparator<WuGui> {
  52. @Override
  53. public int compare(WuGui o1, WuGui o2) {
  54. // 指定比较规则
  55. // 按照年龄排序
  56. return o1.age - o2.age;
  57. }
  58. }
  59. */

9.1 自平衡二叉树

image.png

10. 属性类Properties

  • Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。Properties被称为属性类对象。Properties是线程安全的。
  1. public class PropertiesTest01 {
  2. public static void main(String[] args) {
  3. // 创建一个Properties对象
  4. Properties pro = new Properties();
  5. // 需要掌握Properties的两个方法,一个存,一个取。
  6. pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
  7. pro.setProperty("driver","com.mysql.jdbc.Driver");
  8. pro.setProperty("username", "root");
  9. pro.setProperty("password", "123");
  10. // 通过key获取value
  11. String url = pro.getProperty("url");
  12. String driver = pro.getProperty("driver");
  13. String username = pro.getProperty("username");
  14. String password = pro.getProperty("password");
  15. System.out.println(url);
  16. System.out.println(driver);
  17. System.out.println(username);
  18. System.out.println(password);
  19. }
  20. }

11. Collections工具类

  1. import java.util.*;
  2. /*
  3. java.util.Collection 集合接口
  4. java.util.Collections 集合工具类,方便集合的操作。
  5. */
  6. public class CollectionsTest {
  7. public static void main(String[] args) {
  8. // ArrayList集合不是线程安全的。
  9. List<String> list = new ArrayList<>();
  10. // 变成线程安全的
  11. Collections.synchronizedList(list);
  12. // 排序
  13. list.add("abf");
  14. list.add("abx");
  15. list.add("abc");
  16. list.add("abe");
  17. Collections.sort(list);
  18. for(String s : list){
  19. System.out.println(s);
  20. }
  21. List<WuGui2> wuGuis = new ArrayList<>();
  22. wuGuis.add(new WuGui2(1000));
  23. wuGuis.add(new WuGui2(8000));
  24. wuGuis.add(new WuGui2(500));
  25. // 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
  26. Collections.sort(wuGuis);
  27. for(WuGui2 wg : wuGuis){
  28. System.out.println(wg);
  29. }
  30. // 对Set集合怎么排序呢?
  31. Set<String> set = new HashSet<>();
  32. set.add("king");
  33. set.add("kingsoft");
  34. set.add("king2");
  35. set.add("king1");
  36. // 将Set集合转换成List集合
  37. List<String> myList = new ArrayList<>(set);
  38. Collections.sort(myList);
  39. for(String s : myList) {
  40. System.out.println(s);
  41. }
  42. // 这种方式也可以排序。
  43. //Collections.sort(list集合, 比较器对象);
  44. }
  45. }
  46. class WuGui2 implements Comparable<WuGui2>{
  47. int age;
  48. public WuGui2(int age){
  49. this.age = age;
  50. }
  51. @Override
  52. public int compareTo(WuGui2 o) {
  53. return this.age - o.age;
  54. }
  55. @Override
  56. public String toString() {
  57. return "WuGui2{" +
  58. "age=" + age +
  59. '}';
  60. }
  61. }

第十七章 泛型

1. 泛型基本概念

1、JDK5.0之后推出的新特性:泛型
2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用
3、使用了泛型好处是什么?
第一:集合中存储的元素类型统一了
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
4、泛型的缺点是什么?
导致集合中存储的元素缺乏多样性,大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可
5、可以自定义泛型,<>里面的标识符可以随便写,在源码中一般是E或T

1.1 自动类型推断机制(又称为钻石表达式)

  1. // ArrayList<这里的类型会自动推断>(),前提是JDK8之后才允许。
  2. // 自动类型推断,钻石表达式!
  3. List<Animal> myList = new ArrayList<>();

2. foreach

  • JDK5.0之后推出了一个新特性:叫做增强for循环,或者叫做foreach ```java public class ForEachTest02 { public static void main(String[] args) { // 创建List集合 List strList = new ArrayList<>();

    // 添加元素 strList.add(“hello”); strList.add(“world!”); strList.add(“kitty!”);

    // 遍历,使用迭代器方式 Iterator it = strList.iterator(); while(it.hasNext()){

    1. String s = it.next();
    2. System.out.println(s);

    }

    // 使用下标方式(只针对于有下标的集合) for(int i = 0; i < strList.size(); i++){

    1. System.out.println(strList.get(i));

    }

    // 使用foreach for(String s : strList){ // 因为泛型使用的是String类型,所以是:String s

    1. System.out.println(s);

    }

    List list = new ArrayList<>(); list.add(100); list.add(200); list.add(300); for(Integer i : list){ // i代表集合中的元素

    1. System.out.println(i);

    } } }

```