集---合

引用数据类型:
  类 数组 接口 枚举  注解
Scanner   Random  数组  Date SimpleDateFormat  String StringBuilder等
   
  想使用,new  new完了就可以点  如果new不出来,一般都是类名直接调用
Arrays
      
面向对象:
  1.什么是面向对象:
    万物皆对象,我们自己的事儿,找对象去做
  2.为什么要使用面向对象:懒
    洗衣服->洗衣机
    大学带饭
   
    就是为了干点事方便,我们直接找一个对象,调用对象身上的功能,帮我们去做事
      
  3.什么时候使用面向对象:
    当在一个类中想使用其他类的成员时,就需要面向对象思想了
  4.怎么使用面向对象:
    new呀   点呀
        
今日重点:
  1.会使用Collection接口中的方法
  2.会使用ArrayListLinkedList中的方法以及遍历
  3.会使用迭代器去遍历集合
  4.会使用增强for遍历集合

第一章.集合框架(单列集合)

1.数组特点:
  a.既可以存储基本类型,还可以存储引用类型
  b.定长
2.集合:容器
3.集合作用:存储数据
4.集合特点:
  a.只能存储引用数据类型
  b.长度可变
  c.集合有很多强大的方法类直接操作元素
1.集合分两大类:
  a.单列集合:集合中的元素单独存储
            list.add("三上");
            list.add("松下");
  b.双列集合:集合中的元素都是成对(键值对 key,value的形式)
            map.put("涛哥","柳岩")  前面的是key,后面的是value

在这里插入图片描述

第二章.Collection接口

1.概述:单列集合的顶级接口
2.特点:
  a.元素有序
  b.元素可重复
  c.无索引(没有用过索引操作元素的方法)
3.使用:
  a.创建对象:Collection<E> 集合名 = new 实现类对象<E>()
            <E>:泛型  -> 泛型必须是引用数据类型   ;  泛型是为了明确集合中要存储什么类型的元素
                        如果不确定泛型,集合元素默认类型为Object
            比如:  Collection<String> coll = new 实现类对象<String>()
                  Collection<Integer> coll = new 实现类对象<Integer>()
            注意:等号右边的<>中不用写
                 Collection<Integer> coll = new 实现类对象<>()
4.常用方法:
  boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
  boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
  void clear():清除集合中所有的元素
  boolean contains(Object o)  :判断当前集合中是否包含指定的元素
  boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
  boolean remove(Object o):将指定的元素从集合中删除
  int size() :返回集合中的元素数。
  Object[] toArray(): 把集合中的元素,存储到数组中
public class Test01_Collection {
    public static void main(String[] args) {
        //创建Collection集合
        Collection<String> collection = new ArrayList<>();
        //boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
        collection.add("刘备");
        collection.add("宋江");
        collection.add("赵云");
        collection.add("关羽");
        collection.add("张飞");
        collection.add("柳岩");
        System.out.println(collection);


        //boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
        Collection<String> collection1 = new ArrayList<>();
        collection1.add("释迦牟尼佛");
        collection1.add("斗战胜佛");
        collection1.add("旃檀功德佛");
        collection1.add("阿弥陀佛");
        collection1.add("南无大圣舍利尊王佛");

        collection.addAll(collection1);
        System.out.println(collection);


        //void clear():清除集合中所有的元素
        Collection<String> collection2 = new ArrayList<>();
        collection2.add("樱桃小丸子");
        collection2.add("铁臂阿童木");
        collection2.add("熊出没");
        collection2.add("百变小樱魔术卡");
        System.out.println("collection2 = " + collection2);
        //collection2.clear();
        //System.out.println("collection2 = " + collection2);


        //boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
        boolean empty = collection2.isEmpty();
        System.out.println("empty = " + empty);

        //boolean contains(Object o)  :判断当前集合中是否包含指定的元素
        System.out.println(collection2.contains("熊出没"));

        //boolean remove(Object o):将指定的元素从集合中删除
        //System.out.println(collection2.remove("熊出没"));
        //System.out.println(collection2);

        //int size() :返回集合中的元素数。
        System.out.println(collection2.size());
        //Object[] toArray(): 把集合中的元素,存储到数组中
        Object[] array = collection2.toArray();
        //遍历数组
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }
}

第三章.迭代器

1.迭代器基本使用

1.概述:迭代器是一个接口 -> Iterator
2.获取:
  Collection中的方法:Iterator<E> iterator() -> 凡是Collection以及实现类都可以用此方法获取迭代器对象
3.方法:
  boolean hasNext() -> 判断有没有下一个元素  
  E next() -> 获取下一个元素
public class Test01_Iterator {
    public static void main(String[] args) {
        //创建ArrayList对象
        ArrayList<String> list = new ArrayList<>();
        list.add("柳岩");
        list.add("三上");
        list.add("松下");
        list.add("大乔");
        list.add("涛哥");

        //获取迭代器对象
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

经验:使用迭代器的时候尽量不要连续调用多次next()

public class Test02_Iterator {
 public static void main(String[] args) {
     //创建ArrayList对象
     ArrayList<String> list = new ArrayList<>();
     list.add("柳岩");
     list.add("三上");
     list.add("松下");
     list.add("大乔");
     list.add("涛哥");

     //获取迭代器对象
     Iterator<String> iterator = list.iterator();
     while(iterator.hasNext()){
         System.out.println(iterator.next());
         //System.out.println(iterator.next());
     }
 }
}
image-20220325105056462

2.迭代器迭代原理

3.迭代器底层原理

1.Iterator<String> iterator = list.iterator();
  等号左边是Iterator接口,那么咱们也没有new Iterator的实现类对象,那么Iterator接口接收的是谁呢?是哪个实现类对象呢?
      
2.大家记住一点:
  只要是等号左边是接口类型或者抽象类类型,等号右边又没有直接new对象,那么等号左边是接口类型或者抽象类类型也肯定指向了某一个实现类或者子类,一般都是通过源码来看

1.注意:Iterator指向ArrayList内部类Itr,这种情况,仅仅局限于迭代的是ArrayList集合

2.如果迭代HashSet集合,Iterator指向的实现类就不是Itr了,那么是谁呢?

4.并发修改异常

1.需求:定义集合,存储"孙悟空","唐僧","猪八戒","沙和尚",遍历集合,在遍历的过程中,如果获取出来的是"猪八戒",就直接在集合中添加"白龙马"

public class Test04_Iterator {
    public static void main(String[] args) {
        /*
          1.需求:定义集合,存储"孙悟空","唐僧","猪八戒","沙和尚",遍历集合,
          在遍历的过程中,如果获取出来的是"猪八戒",就直接在集合中添加"白龙马"
         */
        //创建集合
        ArrayList<String> list = new ArrayList<>();
        list.add("孙悟空");
        list.add("唐僧");
        list.add("猪八戒");
        list.add("沙和尚");

        //迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            //获取集合元素
            String element = iterator.next();
            if ("猪八戒".equals(element)){
               list.add("白龙马");
            }
        }

        System.out.println(list);
    }
}

以上代码看似方方面面么有问题,但是一运行报错了:
Exception in thread "main" java.util.ConcurrentModificationException   -> 并发修改异常 
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.atguigu.b_iterator.Test04_Iterator.main(Test04_Iterator.java:23)
    
结论:在使用迭代器的过程中,不要随便改变集合长度

问题1:为什么上面的代码会出现并发修改异常呢?
========================================
Iterator<String> iterator = list.iterator();
public Iterator<E> iterator() {
    return new Itr();
}
private class Itr implements Iterator<E> {
        int lastRet = -1; // index of last element returned; -1 if no such
    
        /*
         expectedModCount:预期操作次数
         modCount:实际操作次数
        */
        int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
        }
    
        final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        }
}

结论:当实际操作次数和预期操作次数不相等时,直接抛出"并发修改异常"

问题2.我们做什么操作了,导致了modCount和expectedModCount不相等了呢?-> 调用了ArrayList中的add方法
====================================================================================
list.add("白龙马");
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++
}

最总结论:当调用add的时候,只给modCount++,然后expectedModCount并没有重新赋值,此时再调用next方法,next方法底层直接做了判
        断,如果modCount和expectedModCount不等时,抛出并发修改异常

public class Test04_Iterator {
    public static void main(String[] args) {
        /*
          1.需求:定义集合,存储"孙悟空","唐僧","猪八戒","沙和尚",遍历集合,
          在遍历的过程中,如果获取出来的是"猪八戒",就直接在集合中添加"白龙马"
         */
        //创建集合
        ArrayList<String> list = new ArrayList<>();
        list.add("孙悟空");
        list.add("唐僧");
        list.add("猪八戒");
        list.add("沙和尚");

        //迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            //获取集合元素

            /*
              next底层判断了预期操作次数如果和实际操作次数不相等
              抛"并发修改异常"
             */
            String element = iterator.next();
            if ("猪八戒".equals(element)){

                /*
                  当调用add的时候,只给实际操作次数++
                  再次回头调用next方法的时候,并没有重新给预期操作次数赋值
                  导致了预期操作次数如果和实际操作次数不相等
                 */
               list.add("白龙马");
            }
        }

        System.out.println(list);
    }
}


扩展:ListIterator迭代器(了解)
方法:ListIterator<E> listIterator()      
public class Test05_Iterator {
 public static void main(String[] args) {
     /*
       1.需求:定义集合,存储"孙悟空","唐僧","猪八戒","沙和尚",遍历集合,
       在遍历的过程中,如果获取出来的是"猪八戒",就直接在集合中添加"白龙马"
      */
     //创建集合
     ArrayList<String> list = new ArrayList<>();
     list.add("孙悟空");
     list.add("唐僧");
     list.add("猪八戒");
     list.add("沙和尚");

     //迭代器
     ListIterator<String> iterator = list.listIterator();
     while(iterator.hasNext()){
         //获取集合元素
         String element = iterator.next();
         if ("猪八戒".equals(element)){
             /*
               ListIterator中的add方法
              */
             iterator.add("白龙马");
         }
     }

     System.out.println(list);
 }
}


第四章.数据结构

1.栈

1.特点:
  先进后出
2.好比:手枪压子弹

在这里插入图片描述

2.队列

1.特点:先进先出
2.好比:排队

3.数组

1.特点:
  查询快(有索引)
  增删慢(定长)
2.如果向往数组中添加一个元素,首先要创建一个新数组,将老数组的数据复制到新数组中,然后在最后添加新的元素

4.链表

1.特点:
  查询慢
  增删快
2.分类:
  单向链表
  双向链表

4.1单向链表

1.特点:
  一个节点分成2部分
  a.第一部分:数据域->存数据的地方
  b.第二部分:指针域->下一个元素的地址
2.前面的节点记录后面节点的地址
  但是后面节点的地址不记录前面节点的地址
      
3.如果集合底层数据结构为单向链表,集合元素是无序的

4.2双向链表

1.特点:
  一个节点分3部分
  a.第一部分:指针域->上一个节点地址
  b.第二部分:数据域->存的数据
  c.第三部分:指针域->下一个节点地址
      
2.一个节点上,既记录下一个节点地址,也记录上一个节点地址
   前面的记录后面的,后面的记录前面的节点
      
3.如果集合底层数据结构是双向链表,元素有序 -> 比如LinkedList  LinkedHashSet  LinkedHashMap

小结:

栈:先进后出

队列:先进先出

数组:查询快(索引) 增删慢(定长)

链表:查询慢,增删快

​ 单向链表:元素无序

​ 双向链表:元素有序

第五章.List接口

1.概述:List接口 extends Collection接口
2.特点:
  a.元素有序
  b.元素可重复
  c.有索引
3.数据结构:不同的实现类,数据结构不一样      

第六章.List集合下的实现类

1.ArrayList集合

1.概述:ArrayListList接口的实现类
2.特点:
  a.元素有序
  b.元素可重复
  c.有索引
3.数据结构:数组
4.使用:
   ArrayList<泛型> 集合名 = new ArrayList<>()
5.常用方法:
  boolean add(E e) -> 添加元素->往集合末尾添加
  void add(int index, E element)->将元素添加到指定的索引位置上 
  boolean remove(Object o) -> 删除指定的元素
  E remove(int index)  -> 删除指定索引位置上的元素,返回的是被删除的元素
  E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
  E get(int index)  -> 获取指定索引位置上的元素
  int size()  -> 获取集合元素个数

1.ArrayList集合使用

public class Test01_ArrayList {
    public static void main(String[] args) {
        //创建ArrayList集合
        ArrayList<String> list = new ArrayList<>();
        //boolean add(E e) -> 添加元素->往集合末尾添加
        list.add("蜻蜓队长");
        list.add("蝎子莱莱");
        list.add("鲨鱼辣椒");
        list.add("蟑螂恶霸");
        list.add("卡布达");
        System.out.println(list);
        //void add(int index, E element)->将元素添加到指定的索引位置上
        list.add(2,"呱呱蛙");
        System.out.println(list);

        System.out.println("===============================");
        //boolean remove(Object o) -> 删除指定的元素
        //System.out.println(list.remove("呱呱蛙"));
        //System.out.println(list);


        //E remove(int index)  -> 删除指定索引位置上的元素,返回的是被删除的元素
        //System.out.println("list.remove(0) = " + list.remove(0));
        //System.out.println(list);


        //E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
        String s = list.set(0, "涛哥");
        System.out.println(s);
        System.out.println(list);
        //E get(int index)  -> 获取指定索引位置上的元素
        System.out.println(list.get(0));
        //int size()  -> 获取集合元素个数

        System.out.println(list.size());
    }
}

public class Test02_ArrayList {
    public static void main(String[] args) {
        //创建ArrayList集合
        ArrayList<String> list = new ArrayList<>();
        list.add("蜻蜓队长");
        list.add("蝎子莱莱");
        list.add("鲨鱼辣椒");
        list.add("蟑螂恶霸");
        list.add("卡布达");

        /*
          直接输出集合不叫遍历
          只是看一看集合中有啥
          要是想操作元素,就需要先将元素拿出来
         */
        System.out.println(list);

        //迭代器
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        System.out.println("===========================");

        //普通for遍历
        for (int i = 0; i < list.size();i++){
            System.out.println(list.get(i));
        }

        System.out.println("===========================");

        /*
          利用快捷键遍历

          集合名.fori
         */
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

经验:

public class Test03_ArrayList {
  public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<>();
      list.add(2);

      //删除元素2

      /*
        两个remove:
        1.remove(Object o)-> 删除指定元素
        2.remove(int index)->删除指定索引位置上的元素

        注意:调用remove方法,传递2会自动匹配 remove(int index)
        但是现在的集合中没有索引2,所以报错

        解决:
          直接写2为int型,只需要将int型的2变成Integer,而Integer是Object的子类

       */
      //list.remove(2);
      list.remove(new Integer(2));
      System.out.println(list);
  }
}

2.底层源码分析(扩展)

1.ArrayList构造:
  a.ArrayList() 构造一个初始容量为 10 的空列表(数组)
                并不是一 new ArrayList底层就会马上创建一个长度为10的空数组
                而是第一次调用add的时候ArrayList底层才会创建一个长度为10的空数组
      
  b.ArrayList(int initialCapacity) -> 构造一个具有指定初始容量的空列表
      
2.数组是定长的,集合长度是可变的,那么ArrayList底层是数组,为什么还要说ArrayList长度可变呢?怎么变的长度?
  elementData = Arrays.copyOf(elementData, newCapacity);

3.如果超出了集合中数组的初始容量10,ArrayList底层自动扩容,怎么扩容?
  elementData = Arrays.copyOf(elementData, newCapacity);

4.扩容多少倍?
  int newCapacity = oldCapacity + (oldCapacity >> 1)
  1.5
ArrayList<String> list = new ArrayList<>();
===========================================
//elementData就是ArrayList底层的那个数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

===============================================
第一次调用add的时候ArrayList底层才会创建一个长度为10的空数组
list.add("abc");
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
}

private void ensureCapacityInternal(int minCapacity -->1) {
    /*
      calculateCapacity(elementData->集合底层数组, minCapacity->1)->接收的是10
      ensureExplicitCapacity(10)
    */
    ensureExplicitCapacity(calculateCapacity(elementData->集合底层数组, minCapacity->1));
}

private static int calculateCapacity(Object[] elementData->集合底层数组, int minCapacity->1) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY->10, minCapacity->1);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity->10) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity->10);
}

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

有参构造创建ArrayList集合
ArrayList<String> list1 = new ArrayList<>(10);
=============================================
public ArrayList(int initialCapacity->10) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}    

第七章.LinkedList集合

1.概述:LinkedListList 的实现类
2.特点:
  a.元素有序
  b.有索引
  c.元素可重复
3.数据结构:
  双向链表
      
4.特有方法->很多直接操作首尾元素的方法
    - public void addFirst(E e):将指定元素插入此列表的开头。
    - public void addLast(E e):将指定元素添加到此列表的结尾。
    - public E getFirst():返回此列表的第一个元素。
    - public E getLast():返回此列表的最后一个元素。
    - public E removeFirst():移除并返回此列表的第一个元素。
    - public E removeLast():移除并返回此列表的最后一个元素。
    - public E pop():从此列表所表示的堆栈处弹出一个元素。
    - public void push(E e):将元素推入此列表所表示的堆栈。
    - public boolean isEmpty():如果列表不包含元素,则返回true
public class Test05_LinkedList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.add("金莲");
        list.add("大郎");
        list.add("柳岩");
        list.add("涛哥");
        //- public void addFirst(E e):将指定元素插入此列表的开头。
        list.addFirst("三上");
        System.out.println(list);
        //- public void addLast(E e):将指定元素添加到此列表的结尾。
        list.addLast("松下");
        System.out.println(list);
        //- public E removeFirst():移除并返回此列表的第一个元素。
        System.out.println(list.removeFirst());
        System.out.println(list);
        //- public E removeLast():移除并返回此列表的最后一个元素。
        System.out.println(list.removeLast());
        System.out.println(list);
    }
}

public class Test06_LinkedList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.add("金莲");
        list.add("大郎");
        list.add("柳岩");
        list.add("涛哥");

        //迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("====================");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}


- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。

public class Test07_LinkedList {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.add("金莲");
        list.add("大郎");
        list.add("柳岩");
        list.add("涛哥");
        //public E pop():从此列表所表示的堆栈处弹出一个元素。
        System.out.println(list.pop());
        System.out.println(list);
        //public void push(E e):将元素推入此列表所表示的堆栈。
        list.push("二郎");
        System.out.println(list);

    }
}

1 LinkedList底层成员解释说明

1.LinkedList底层成员
  transient int size = 0;  元素个数
  transient Node<E> first; 第一个节点对象
  transient Node<E> last;  最后一个节点对象
  
2.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;
        }
    }

2 LinkedList中add方法源码分析

LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");    

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

3.LinkedList中get方法源码分析

即使链表查询慢,但是我们在底层实现的时候,也会尽量的加快查询速度

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
} 

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

index < (size >> 1)采用二分思想,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历

第八章.增强for

1.增强for的介绍以及基本使用

1.作用:主要是遍历集合以及数组的
2.格式:
  for(元素类型 变量名:集合名或者数组名){
      变量名接收的就是集合或者数组中的每一个元素
  }

public class Test01_Foreach {
    public static void main(String[] args) {
        //method01();

        method02();
    }

    private static void method02() {
        int[] arr = {1,2,3,4,5};
        //增强for-> 快捷键-> 集合名.for
        for (int i : arr) {
            System.out.println(i);
        }
    }

    private static void method01() {
        ArrayList<String> list = new ArrayList<>();
        list.add("蜻蜓队长");
        list.add("蝎子莱莱");
        list.add("鲨鱼辣椒");
        list.add("蟑螂恶霸");
        list.add("卡布达");

        //迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("==========================");

        //普通for
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.println("==========================");

        //增强for-> 快捷键-> 集合名.for
        for (String s : list) {
            System.out.println(s);
        }
    }
}


增强for快捷键
    数组名或者集合名.for-> 完事

2.使用增强for的注意事项

1.注意:如果使用增强for,遍历集合,过程中不能随意删除,添加元素,否则,会抛出并发修改异常
2.原因:
  在使用增强for遍历集合时,底层原理是迭代器
  在使用增强for遍历数组时,底层原理是普通for

============================================

day20.集合

今日重点:
  1.会使用Collections集合工具类
  2.会使用HashSet,以及知道HashSet的特点
  3.会使用LinkedHashSet,以及知道LinkedHashSet的特点
  4.知道HashSetLinkedHashSet如何去重复
  5.会使用Map集合,对元素进行增删改查,遍历

第一章.Collections集合工具类

1.概述:Collections是一个集合工具类
2.特点:
  a.构造私有
  b.方法都是静态的
3.使用:
  类名直接调用
4.常用方法:
  static <T> boolean addAll(Collection<? super T> c, T... elements)->批量将元素存到集合中  
  static void shuffle(List<?> list) -> 打乱集合元素 
  static <T> void sort(List<T> list) -> 将集合中的元素按照默认规则排序->ASCII
  static <T> void sort(List<T> list, Comparator<? super T> c) -> 将集合汇总的元素按照指定的规则排序   
public class Test01_Collections {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        //static <T> boolean addAll(Collection<? super T> c, T... elements)->批量将元素存到集合中
        Collections.addAll(list,"张三","李四","王五","赵六","田七","猪八");
        System.out.println(list);
        //static void shuffle(List<?> list) -> 打乱集合元素
        Collections.shuffle(list);
        System.out.println(list);

        //static <T> void sort(List<T> list) -> 将集合中的元素按照默认规则排序->ASCII
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("Bc");
        list1.add("Ca");
        list1.add("Ad");
        list1.add("Db");
        Collections.sort(list1);
        System.out.println(list1);
    }
}

比较器:Comparator接口

 static <T> void sort(List<T> list, Comparator<? super T> c) -> 将集合汇总的元素按照指定的规则排序 
     
 1.Comparator:比较器,接口
 2.Comparator接口中的方法:
   int compare(T o1,T o2)
       o1-o2:升序
       o2-o1:降序
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test02_Collections {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("涛哥",18));
        list.add(new Person("柳岩",36));
        list.add(new Person("三上",26));

        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                //按照Person对象的年龄去排序
                return o2.getAge()-o1.getAge();
            }
        });

        System.out.println(list);

    }

}

比较器:Comparable接口

方法:
public int compareTo(T o)
比较:
this-o : 升序
o-this : 降序
public class Person implements Comparable<Person>{
 private String name;
 private int age;

 public Person() {
 }

 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 "Person{" +
             "name='" + name + '\'' +
             ", age=" + age +
             '}';
 }

 
 /*
    实现Comparable不加泛型
  */
// @Override
// public int compareTo(Object o) {
//     Person p = (Person)o;
//     return this.getAge()-p.getAge();
// }

 
 //======================================
 
 /*
   
    实现Comparable加泛型
  */
 @Override
 public int compareTo(Person o) {
     return o.getAge()-this.getAge();
 }
}

public class Test03_Collections {
 public static void main(String[] args) {
     ArrayList<Person> list = new ArrayList<>();
     list.add(new Person("涛哥",18));
     list.add(new Person("柳岩",36));
     list.add(new Person("三上",26));

     Collections.sort(list);

     System.out.println(list);

 }

}


使用Junit单元测试,使用步骤:

1.概述:Junit是一个Java语言的单元测试框架,简单理解为在一定程度上用于取代main方法,Junit属于第三方工具,需要导入jar包后再使用
 
Oracle属于第一方
我们自己属于第二方
其他的属于第三方
 
jar包其实就是一个压缩包,里面装的都是Class文件,我们直接导入就可以使用jar包中的class文件
 
如何导jar包:
a.在当前模块下,创建文件夹,取名为lib
b.将jar包粘贴到lib下
c.对着lib文件夹-> 右键-> add as library-> module level->ok
     
2.作用:可以单独去运行一个我们想要测试的功能,不用提供main方法了

>

第二章.泛型

1.为什么要使用泛型

1.从使用的角度:统一元素的数据类型,防止类型转换异常
2.从定义的角度:使用起来很方便,可以传递任意类型,传递什么类型,我们就操作什么类型的数据,通用

2.泛型的定义

2.1含有泛型的类

1.格式:
  public class 类名<E>{
      
  }
2.什么时候确定这个泛型类型:new的时候确定类型

public class ArrayList_MySelf<E> {
    public void add(E e){
        System.out.println(e);
    }
}

public class Test01 {
    public static void main(String[] args) {
        ArrayList_MySelf<Integer> list1 = new ArrayList_MySelf<>();
        list1.add(1);
    }
}


2.2含有泛型的方法

1.格式:
  修饰符 <E> 返回值类型 方法名(E e){
      
  }
2.什么时候确定泛型类型
  调用的时候确定类型

public class ArrayList_Myself{
    public <E> void add(E e){
        System.out.println(e);
    }
}

public class Test01 {
    public static void main(String[] args) {
        ArrayList_Myself list = new ArrayList_Myself();
        list.add("哈哈哈");
        list.add(1);
    }
}

2.3含有泛型的接口

1.定义格式:
  public interface 接口名<E>{}
2.什么时候确定泛型类型
  a.在实现类的时候确定类型->Scanner
  b.在实现类的时候还不确定类型,在创建实现类对象的时候确定类型-> ArrayList
    

public interface Iterator_Myself<E> {
    public E next(E e);
}

public class Scanner_Myself implements Iterator_Myself<String>{
    @Override
    public String next(String s) {
        return s;
    }
}


public class Test01 {
    public static void main(String[] args) {
        Scanner_Myself scanner_myself = new Scanner_Myself();
        String result = scanner_myself.next("哈哈哈");
        System.out.println(result);
    }
}


public interface List_Myself<E> {
    public void add(E e);
}

public class ArrayList_Myself<E> implements List_Myself<E>{
    @Override
    public void add(E e) {
        System.out.println(e);
    }
}

public class Test01 {
    public static void main(String[] args) {
        ArrayList_Myself<String> list = new ArrayList_Myself<>();
        list.add("哈哈哈");
    }
}

3.泛型的高级使用

3.1 泛型通配符 ?

1.什么时候使用泛型通配符:一般都是在参数位置当不知道接收什么类型时->一般用在集合方便

public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("郭嘉");
        list1.add("庞统");
        list1.add("诸葛亮");
        list1.add("贾诩");
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        list2.add(3);
        
        method(list1);
        method(list2);
        
    }
    
    //定义方法,遍历以上两个集合
    public static void method(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        } 
    }
}


3.2 泛型的上限下限

泛型通配符的高级使用--受限泛型

1.上限:
  a.格式: 类型名称<? extends> 对象名
  b.意义: ?只能接受extends后面的本类以及子类
2.下限:
  a.格式: 类型名称<? super> 对象名
  b.意义: ?只能接受super后的本类以及父类

/*
  Integer->Number->Object
  String->Object
 */
public class Test02 {
        public static void main(String[] args) {
            Collection<Integer> list1 = new ArrayList<>();
            Collection<String> list2 = new ArrayList<>();
            Collection<Number> list3 = new ArrayList<>();
            Collection<Object> list4 = new ArrayList<>();

            getElement1(list1);
            //getElement1(list2);报错
            getElement1(list3);
            //getElement1(list4);报错

            System.out.println("==================");

            //getElement2(list1);//报错
            //getElement2(list2);//报错
            getElement2(list3);
            getElement2(list4);
        }

        //上限  ?只能接受extends后面的本类以及子类
        public static void getElement1(Collection<? extends Number> collection){

        }

        //下限 ?只能接受super后的本类以及父类

        public static void getElement2(Collection<? super Number> collection){

        }
}


第三章.红黑树(了解)

1.结论:加入红黑树的目录-> 查询快

https://www.cs.usfca.edu/~galles/visualization/RedBlack

第四章.Set集合

1.Set集合介绍

1.概述:Set接口, 继承自 Collection接口
2.注意:
  a.Set接口中的方法,并没有堆Collection接口中的方法进行扩充
  b.底层都是依靠map去实现的
  c.set集合看似没有意义,但是我们看上的是它的元素唯一这个特点,可以快速去重复  

2.HashSet集合的介绍和使用

1.概述:HashSet 实现 Set接口
2.特点:
  a.元素无序
  b.元素唯一(如果元素一样,后面的会把前面的覆盖)
  c.没有索引(迭代器遍历,增强for遍历)
3.数据结构:哈希表
  jdk8之前:哈希表 = 数组+链表
  jdk8之后:哈希表 = 数组+链表+红黑树
  加入红黑树的目的:查询快,提高效率
4.方法:Collection一毛一样   

public class Test01_HashSet {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("abc");
        hashSet.add("abc");
        hashSet.add("通话");
        hashSet.add("重地");
        hashSet.add("吕布");
        hashSet.add("貂蝉");
        System.out.println(hashSet);

        //迭代器
        Iterator<String> iterator = hashSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("======================");

        //增强for
        for (String s : hashSet) {
            System.out.println(s);
        }

    }
}


3.LinkedHashSet的介绍以及使用

1.概述:LinkedHashSet extends HashSet
2.特点:
  a.元素有序
  b.元素唯一(如果元素一样,后面的会把前面的覆盖)
  c.没有索引(迭代器遍历,增强for遍历)
3.数据结构:哈希表+链表(双向链表)
4.方法:
  和他爹(HashSet)一毛一样

public class Test02_LinkedHashSet {
    public static void main(String[] args) {
        LinkedHashSet<String> hashSet = new LinkedHashSet<>();
        hashSet.add("abc");
        hashSet.add("abc");
        hashSet.add("通话");
        hashSet.add("重地");
        hashSet.add("吕布");
        hashSet.add("貂蝉");
        System.out.println(hashSet);

        //迭代器
        Iterator<String> iterator = hashSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

        System.out.println("======================");

        //增强for
        for (String s : hashSet) {
            System.out.println(s);
        }

    }
}

4.哈希值

1.概述:计算机计算出来的十进制,可以理解为对象的地址值
2.获取哈希值:
  调用Object类中的hashCode()方法
3.结论:
  a.如果想要获取对象内容的哈希值,重写hashCode方法
  b.内容一样,算出来的哈希值一定一样
  c.内容不一样,算出来的哈希值也有可能一样(哈希冲突,哈希碰撞)

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

    public Person() {
    }

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

    /*
      重写了hashCode,计算的就是属性的哈希值了
    */
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}


public class Test01 {
    public static void main(String[] args) {
        Person p1 = new Person("柳岩", 36);
        Person p2 = new Person("柳岩", 36);
        System.out.println(p1.toString());
        System.out.println(p2.toString());
        System.out.println("===================");
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
        System.out.println("===================");
        System.out.println(Integer.toHexString(21685669));
        System.out.println(Integer.toHexString(2133927002));

        System.out.println("==========================================");

        /*
          由于String底层重写了Object类中的hashCode方法
          不管怎么创建String,都会调用重写的hashCode方法
          计算的是String内容的哈希值
         */
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());

        System.out.println("==========================================");


        //内容不一样,算出来的哈希值也有可能一样
        String s4 = "通话";
        String s5 = "重地";
        System.out.println(s4.hashCode());//1179395
        System.out.println(s5.hashCode());//1179395

    }
}


5.字符串的哈希值时如何算出来的

String s = "abc";
System.out.println(s.hashCode());//96354

由于String底层是一个char数组,所以String s = "abc"
这个"abc"到了String底层会变成
char[] value = {'a','b','c'}

 public int hashCode() {
     int h = hash;
     if (h == 0 && value.length > 0) {
         char val[] = value;

         for (int i = 0; i < value.length; i++) {
             h = 31 * h + val[i];
         }
         hash = h;
     }
     return h;
 }

第一圈:
  h = 31 * h + val[0]-> 31*0+97 = 97
      
第二圈:
  h = 31 * h + val[1]->31*97+98 = 3105

第三圈:
  h = 31 * h + val[2]->31*3105+99 = 96354

问题:

为什么要用31去乘?

​ 因为 31是一个质数,用31,就是为了尽量的降低哈希冲突的概率(内容不一样,但是哈希值一样)

6.HashSet的存储去重复的过程

1.先计算元素的哈希值(重写的hashCode方法),然后比较哈希值
2.如果哈希值不一样,直接存
3.如果哈希值一样,在比较元素的内容(重写的equals方法)
4.如果哈希值一样,内容不一样,直接存
5.如果哈希值一样,内容也一样,去重复,后面的会把前面的覆盖

public class Test01 {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("abc");//96354
        hashSet.add("通话");//1179395
        hashSet.add("重地");//1179395
        hashSet.add("abc");//96354
        System.out.println(hashSet);
    }
}

7.HashSet存储自定义类型如何去重复

set集合存储自定义对象,想要去重复,应该重写hashCode和equals方法,去比较属性的哈希值和内容

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

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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


public class Test01 {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("abc");//96354
        hashSet.add("通话");//1179395
        hashSet.add("重地");//1179395
        hashSet.add("abc");//96354
        System.out.println(hashSet);

        System.out.println("=================================");

        HashSet<Person> hashSet1 = new HashSet<>();
        /*
          如果Person中没有重写hashCode和equals
          那么set去重复的时候就会调用Object中的hashCode和equals进行比较
          那么三个对象,哈希值肯定不一样,所以无法去重复

          解决:Person中重写hashCode和equals方法
              让set比较属性的哈希值和属性的内容
         */
        hashSet1.add(new Person("柳岩",36));
        hashSet1.add(new Person("柳岩",36));
        hashSet1.add(new Person("涛哥",18));

        System.out.println(hashSet1);
    }
}


第五章.双列集合_Map

1.Map的介绍

1.概述:Map接口,是双列集合的顶级接口
2.特点:
  a.无序(除了LinkedHashMap之外)
  b.无索引
  c.元素都是键值对形式: key value形式
  d.key唯一,value可以重复(如果key重复了,后面的键值对会把前面的键值对覆盖)

2.HashMap的介绍以及使用

1.概述:HashMap 实现  Map接口
2.特点:
  a.无序
  b.无索引
  c.元素都是键值对形式: key value形式
  d.key唯一,value可以重复(如果key重复了,后面的键值对会把前面的键值对覆盖)
3.数据结构:
  哈希表
4.常用方法:
  V put(K key, V value)  -> 存储元素
  V remove(Object key)  ->根据key删除对应的键值对,返回的是被删除的value
  V get(Object key)  -> 根据key获取对应的value
  boolean containsKey(Object key) ->判断Map中是否包含指定的key
  Collection<V> values()  ->Map中所有的value存储到Collection集合中
  
  Set<K> keySet() ->Map中所有的key获取出来存到Set集合中 
  Set<Map.Entry<K,V>> entrySet() -> 获取Map中所有的键值对对象,放到set集合中
      
5.注意:
  如果key重复了,后面的会把前面的覆盖,去重复的过程和set一毛一样
  先比较key的哈希值,在比较key的内容

public class Test01_HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        //V put(K key, V value)  -> 存储元素
        map.put("涛哥","柳岩");
        map.put("黄晓明","杨颖");
        map.put("文章","马伊琍");
        map.put("陈羽凡","白百何");
        map.put("王宝强","马蓉");
        System.out.println(map);
        //V remove(Object key)  ->根据key删除对应的键值对,返回的是被删除的value
        String value = map.remove("文章");
        System.out.println(value);
        System.out.println(map);
        //V get(Object key)  -> 根据key获取对应的value
        String value1 = map.get("涛哥");
        System.out.println(value1);
        //boolean containsKey(Object key) ->判断Map中是否包含指定的key
        boolean result = map.containsKey("涛哥");
        System.out.println(result);
        //Collection<V> values()  -> 将Map中所有的value存储到Collection集合中
        Collection<String> collection = map.values();
        for (String s : collection) {
            System.out.println(s);
        }


    }

}

1.一次写多行:

a.在idea中预留出足够的地方

b.按照alt键不放,往下拉鼠标,发现鼠标变长了

2.快速将一段代码抽取到一个方法中:ctrl+alt+m

>

3.HashMap的两种遍历方式

3.1 方式1:获取key,然后根据key获取value

 Set<K> keySet() ->Map中所有的key获取出来存到Set集合中 

public class Test02_HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        //V put(K key, V value)  -> 存储元素
        map.put("涛哥","柳岩");
        map.put("黄晓明","杨颖");
        map.put("文章","马伊琍");
        map.put("陈羽凡","白百何");
        map.put("王宝强","马蓉");
        /*
         Set<K> keySet() -> 将Map中所有的key获取出来存到Set集合中
         */

        Set<String> set = map.keySet();
        //遍历set集合,然后在根据key获取map中的value
        for (String key : set) {
            String value = map.get(key);
            System.out.println(key+"..."+value);
        }

    }

}


3.2 方式2:同时获取key和value

Set<Map.Entry<K,V>> entrySet() -> 获取Map中所有的键值对对象,放到set集合中

public class Test03_HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        //V put(K key, V value)  -> 存储元素
        map.put("涛哥","柳岩");
        map.put("黄晓明","杨颖");
        map.put("文章","马伊琍");
        map.put("陈羽凡","白百何");
        map.put("王宝强","马蓉");

        /*
          Set<Map.Entry<K,V>> entrySet() -> 获取Map中所有的键值对对象,放到set集合中
         */
        Set<Map.Entry<String, String>> set = map.entrySet();

        //遍历set集合,将Map.Entry遍历出来
        for (Map.Entry<String, String> entry : set) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"..."+value);
        }
    }

}

4.HashMap存储自定义对象

1.key存储自定义对象,value随意
2.要想让自定义对象作为key,还要保持唯一,自定义对象需要重写hashCode和equals方法,比较属性的哈希值和内容

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

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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


public class Test04_HashMap {
    public static void main(String[] args) {
        HashMap<Person, String> map = new HashMap<>();
        map.put(new Person("柳岩",36),"湖南");
        map.put(new Person("柳岩",36),"廊坊");
        map.put(new Person("涛哥",16),"廊坊");

        System.out.println(map);
    }
}


5.Map的练习

1.需求:利用HashMap集合统计字符串中字符出现的次数
    
2.步骤:
  1.使用Scanner键盘录入一个字符串
  2.创建Map集合,key保存字符,value保存字符个数
  3.遍历字符串,将每一给字符遍历出来
  4.判断,map中是否包含遍历出来的字符
  5.如果不包含,直接将字符和1存进去
  6.如果包含,根据字符将value获取出来,进行加1
  7.再将此字符和变化后的value重新存进去

public class Test05_HashMap {
    public static void main(String[] args) {
        //1.使用Scanner键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请你输入一个字符串:");
        String data = sc.next();
        //2.创建Map集合,key保存字符,value保存字符个数
        HashMap<String, Integer> map = new HashMap<>();
        //3.遍历字符串,将每一给字符遍历出来
        char[] chars = data.toCharArray();
        for (char aChar : chars) {
            String key = aChar + "";
            //4.判断,map中是否包含遍历出来的字符
            //5.如果不包含,直接将字符和1存进去
            if (!map.containsKey(key)) {
              map.put(key,1);
            }else{
            //6.如果包含,根据字符将value获取出来,进行加1
                Integer count = map.get(key);
                count++;

                //7.再将此字符和变化后的value重新存进去
                map.put(key,count);
            }

        }

        System.out.println(map);
    }
}


6.LinkedHashMap

1.概述:LinkedHashMap extends HashMap
2.特点:
  a.有序
  b.无索引
  c.元素都是键值对形式: key value形式
  d.key唯一,value可以重复(如果key重复了,后面的键值对会把前面的键值对覆盖)
3.数据结构:
  哈希表+双链表
4.使用:HashMap一毛一样,遍历一毛一样,key如何去重复一毛一样

public class Test06_LinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        //V put(K key, V value)  -> 存储元素
        map.put("涛哥","柳岩");
        map.put("黄晓明","杨颖");
        map.put("文章","马伊琍");
        map.put("陈羽凡","白百何");
        map.put("王宝强","马蓉");
        /*
         Set<K> keySet() -> 将Map中所有的key获取出来存到Set集合中
         */
        Set<String> set = map.keySet();
        //遍历set集合,然后在根据key获取map中的value
        for (String key : set) {
            String value = map.get(key);
            System.out.println(key+"..."+value);
        }
    }
}

根据value值获取key

for (Integer mapKey : map.keySet()) {
    if (map.get(mapKey).equals(country)) {
        System.out.println(mapKey);
    }
}

====================================

day21.集合

1.Collections集合工具类
  a.方法:
    addAll-> 一次添加多个元素
    shuffle->打乱
    sort:排序 -> ascii
    sort(List list,Comparator c)->按照指定的规则进行排序
2.泛型:
  a.含有泛型的类-> public class 类名<E>{}
    new对象的时候确定泛型类型
  b.含有泛型的方法-> public <E> 返回值类型 方法名(参数){}
    调用的时候
  c.含有泛型的接口-> public interface 接口名<E>{}
    在实现类的时候确定类型
    在实现类的时候还不确定类型,只能到new的时候确定
  d.泛型通配符:?
    上限:<? extends> -> ?接收的类型要是extends后面类的本类以及子类
    下限:<? super>-> ?接收的类型要是super后面类的本类以及父类
3.红黑树:查询快
4.Set集合:用法和Collection一样,底层实现都是依靠Map
  a.HashSet
    无序,无索引,元素唯一
    哈希表
  b.LinkedHashSet
    有序,无索引,元素唯一
    哈希表+双向链表
  c.set去重复过程:
    比较元素的哈希值,再比较内容
    如果哈希值不一样,直接存
    如果哈希值一样,再比较内容
    如果哈希值一样,内容不一样(哈希冲突),直接存
    如果哈希值一样,内容也一样,去重复,后面的吧前面的覆盖
  d.set存储自定义对象,去重复,需要元素重写hashCode和equals方法,比较属性的哈希值和内容
5.Map集合:
  a.HashMap
    无序,无索引,元素都是key value形式,key唯一(去重复过程和set一样),数据结构为哈希表
  b.方法:
    put:存储key value
    remove:根据key删除键值对
    get:根据key获取value
    containsKey:判断是会否包含指定的key
    values:获取所有的value
    keySet:获取key放到set中
    entrySet:获取键值对
      
  c.LinkedHashMap:
    有序,无索引,元素都是key value形式,key唯一(去重复过程和set一样),数据结构为哈希表+链表
    用法和HashMap一样
        
今日重点:
  1.会使用TreeMapTreeSet
  2.要会使用Properties集合
  3.会遍历嵌套的集合
  4.File类的基本操作

第一章.TreeSet

1.概述:TreeSetSet接口中的实现类
2.特点:
  a.可以对元素进行排序
  b.元素唯一
  c.无索引
3.数据结构:
   红黑树
4.使用:
  方法,遍历,都和HashSet一样
构造:
  TreeSet() -> 对元素进行自然排序
  TreeSet(Comparator<? super E> comparator) -> 对元素进行指定规则进行排序-> 存自定义对象的时候使用

排序

public class Test01_TreeSet {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("d");
        set.add("c");
        set.add("a");
        set.add("b");
        System.out.println(set);
    }
}
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test02_TreeSet {
    public static void main(String[] args) {
        /*
          TreeSet(Comparator<? super E> comparator)
             对元素进行指定规则进行排序-> 存自定义对象的时候使用

             Comparator是一个接口,需要传递
         */
        TreeSet<Person> set = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        set.add(new Person("柳岩",36));
        set.add(new Person("晓楠",16));
        set.add(new Person("涛哥",18));
        System.out.println(set);
    }
}

第二章.TreeMap

1.概述:TreeMapMap的实现类
2.特点:
  a.可以对key进行排序
  b.元素都是key value形式
  c.无索引
  d.key唯一
3.底层数据结构:
  红黑树
TreeMap() -> 按照自然顺序对key进行排序(ascii)
TreeMap(Comparator<? super K> comparator)-> 按照指定顺序对key进行排序 

排序

public class Test01_TreeMap {
    public static void main(String[] args) {
        TreeMap<String, String> treeMap = new TreeMap<>();
        treeMap.put("d","低头思故乡");
        treeMap.put("b","疑是地上霜");
        treeMap.put("a","床前明月光");
        treeMap.put("c","举头望明月");

        System.out.println(treeMap);
    }
}
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test02_TreeMap {
    public static void main(String[] args) {
        TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o2.getAge()-o1.getAge();
            }
        });
        treeMap.put(new Person("柳岩",36),"湖南");
        treeMap.put(new Person("涛哥",18),"廊坊");
        treeMap.put(new Person("杨幂",32),"北京");

        System.out.println(treeMap);
    }
}

第三章.HashTable和Vector集合(了解)

1.HashTable集合

1.概述:开始于JDK1.0版本,从java1.2开始,此类降级,变成了Map的实现类
2.特点:
  a.key唯一
  b.无索引
  c.无序
  d.线程安全,效率低
3.数据结构:
  哈希表
4.使用:HashMap一毛一样
public class Test01_Hashtable {
    public static void main(String[] args) {
        Hashtable<String, String> hashtable = new Hashtable<>();
        hashtable.put("张三","李四");
        hashtable.put("涛哥","柳岩");
        hashtable.put("涛哥","杨幂");
        hashtable.put("田七","猪八");
        hashtable.put("二郎","金莲");
        System.out.println(hashtable);

        System.out.println("=================");

        Set<String> set = hashtable.keySet();
        for (String key : set) {
            String value = hashtable.get(key);
            System.out.println(key+"..."+value);
        }

        System.out.println("===============");
        Set<Map.Entry<String, String>> entrySet = hashtable.entrySet();
        for (Map.Entry<String, String> entry : entrySet) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
    }
}

2.Vector集合

1.概述:出现在jdk1.0版本,从java1.2开始,此类降级,改进为可以实现List接口的实现类
2.特点:
  a.有序
  b.有索引
  c.元素可重复
  d.线程安全,效率低
3.数据结构:
  数组
4.使用方式:ArrayList一样
5.底层分析:
  a.Vectornew底层就会直接产生一个长度为10的空数组
  b.数组扩容:Arrays.copyOf
    扩容2
public class Test02_Vector {
    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();
        vector.add(1);
        vector.add(2);
        System.out.println(vector);

        for (Integer integer : vector) {
            System.out.println(integer);
        }
    }
}

问题:Hashtable和HashMap的区别

1.相同点:
都是哈希表结构,都是key唯一,无索引,无序
2.不同点:
HashMap:线程不安全,效率高,能存储null,nullHashtable:线程安全,效率低,不能存储null,null

第四章.Properties集合(属性集)

1.概述:Properties extends Hashtable
2.特点:
  a.无序
  b.无索引
  c.key唯一
  d.key value 固定都是String
  e.不能存储null,null键
  f.线程安全,效率低
3.数据结构:哈希表
4.使用:HashMap一样,Map中的方法Properties都能使用
5.这里我们不学map中的方法,我们要学Properties中的特有方法
  Object setProperty(String key, String value)-> 存元素  
  String getProperty(String key)  -> 根据key获取value
  Set<String> stringPropertyNames()  -> 获取所有的key,存到set集合中
    
  void load(InputStream inStream)  -> 将流中的数据信息加载到Properties集合中(IO部分再说)
public class Test01_Properties {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //Object setProperty(String key, String value)-> 存元素
        properties.setProperty("username","root");
        properties.setProperty("password","123");
        System.out.println(properties);
        //String getProperty(String key)  -> 根据key获取value
        String value = properties.getProperty("username");
        System.out.println(value);
        //Set<String> stringPropertyNames()  -> 获取所有的key,存到set集合中

        Set<String> set = properties.stringPropertyNames();
        for (String key : set) {
            String value1 = properties.getProperty(key);
            System.out.println(key+"="+value1);
        }
    }
}

第五章.集合嵌套

1.List嵌套List

需求:创建2List集合,每个集合中分别存储一些字符串,2个集合存储到第3List集合中
public class Demo01_List_In_List {
    public static void main(String[] args) {
        //创建2个List集合,每个集合中分别存储一些字符串,将2个集合存储到第3个List集合中
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("杨过");
        list1.add("小龙女");
        list1.add("尹志平");

        ArrayList<String> list2 = new ArrayList<>();
        list2.add("周芷若");
        list2.add("张无忌");
        list2.add("赵敏");


        //创建第三个集合
        ArrayList<ArrayList<String>> list = new ArrayList<>();
        list.add(list1);
        list.add(list2);

        System.out.println(list);

        //遍历,先遍历大list,将两个小集合获取出来
        for (ArrayList<String> arrayList : list) {
            //遍历每一个小集合,将小集合中的元素获取出来
            for (String s : arrayList) {
                System.out.println(s);
            }
        }
    }
}

2.List嵌套Map

1班级有第三名同学,学号和姓名分别为:1=张三,2=李四,3=王五,2班有三名同学,学号和姓名分别为:1=黄晓明,2=杨颖,3=刘德华,请将同学的信息以键值对的形式存储到2Map集合中,在将2Map集合存储到List集合中。
public class Demo02_List_In_Map {
    public static void main(String[] args) {
        /*
          1班级有第三名同学,学号和姓名分别为:1=张三,2=李四,3=王五,
          2班有三名同学,学号和姓名分别为:1=黄晓明,2=杨颖,3=刘德华,
          请将同学的信息以键值对的形式存储到2个Map集合中,在将2个Map集合存储到List集合中。
         */

        //创建两个map集合
        HashMap<Integer, String> map1 = new HashMap<>();
        map1.put(1,"张三");
        map1.put(2,"李四");

        HashMap<Integer, String> map2 = new HashMap<>();
        map2.put(1,"黄晓明");
        map2.put(2,"杨颖");

        //创建List集合,存储两个Map
        ArrayList<HashMap<Integer, String>> list = new ArrayList<>();
        list.add(map1);
        list.add(map2);

        //遍历list集合,将两个小map获取出来
        for (HashMap<Integer, String> map : list) {
            /*
              map代表的就是List中的两个小map
              遍历map,将key和value获取出来
             */
            Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
            for (Map.Entry<Integer, String> entry : entrySet) {
                System.out.println(entry.getKey()+"..."+entry.getValue());
            }
        }
    }
}

3.Map嵌套Map

- JavaSE  集合 存储的是 学号 键,值学生姓名
  - 1  张三
  - 2  李四
- JavaEE 集合 存储的是 学号 键,值学生姓名
  - 1  王五
  - 2  赵六
public class Demo03_Map_In_Map {
    public static void main(String[] args) {
        /*
          - JavaSE  集合 存储的是 学号 键,值学生姓名
            - 1  张三
            - 2  李四
          - JavaEE 集合 存储的是 学号 键,值学生姓名
            - 1  王五
            - 2  赵六
         */

        //创建两个小map
        HashMap<Integer, String> map1 = new HashMap<>();
        map1.put(1,"张三");
        map1.put(2,"李四");


        HashMap<Integer, String> map2 = new HashMap<>();
        map2.put(1,"王五");
        map2.put(2,"赵六");


        //创建大Map,存储两个小Map
        HashMap<String, HashMap<Integer, String>> map = new HashMap<>();
        map.put("javase",map1);
        map.put("javaee",map2);


        //遍历大map,将小map获取
        Set<String> set = map.keySet();
        for (String key : set) {
            //根据key获取小map
            HashMap<Integer, String> hashMap = map.get(key);
            //遍历小map
            Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
            for (Map.Entry<Integer, String> entry : entries) {
               System.out.println(key+"..."+entry.getKey()+"..."+entry.getValue());
            }
        }
    }
}

第六章.哈希表结构存储过程

1.HashMap底层数据数据结构:哈希表
2.jdk7:哈希表 = 数组+链表
  jdk8:哈希表 = 数组+链表+红黑树
3.
  先算哈希值,此哈希值在HashMap底层经过了特殊的计算得出
  如果哈希值不一样,直接存
  如果哈希值一样,再去比较内容,如果内容不一样,也存
  如果哈希值一样,内容也一样,直接去重复(后面的value将前面的value覆盖)
  
  哈希值一样,内容不一样->哈希冲突(哈希碰撞)
4.要知道的点:
  a.在不指定长度时,哈希表中的数组默认长度为16,HashMap创建出来,一开始没有创建长度为16的数组
  b.什么时候创建的长度为16的数组呢?在第一次put的时候,底层会创建长度为16的数组
  c.哈希表中有一个数据加[加载因子]->默认为0.75(加载因子)->代表当元素存储到百分之75的时候要扩容了->2倍
  d.如果对个元素出现了哈希值一样,内容不一样时,就会在同一个索引上以链表的形式存储,当链表长度达到8并且当前数组容量>64,链表就会改成使用红黑树存储
  e.加入红黑树目的:查询快
外面笔试时可能会问到的变量
default_initial_capacity:HashMap默认容量  16
default_load_factor:HashMap默认加载因子   0.75f
threshold:扩容的临界值   等于   容量*0.75 = 12  第一次扩容临界值
treeify_threshold:链表长度默认值,转为红黑树:8
min_treeify_capacity:链表被树化时最小的数组容量:64

1.HashMap无参数构造方法的分析

//HashMap中的静态成员变量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

解析:使用无参数构造方法创建HashMap对象,将加载因子设置为默认的加载因子,loadFactor=0.75F。

2.HashMap有参数构造方法分析

HashMap(int initialCapacity, float loadFactor) ->创建Map集合的时候指定底层数组长度以及加载因子
    
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
    	throw new IllegalArgumentException("Illegal initial capacity: " +
    initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
    	initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    	throw new IllegalArgumentException("Illegal load factor: " +
    loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);//10
}

解析:带有参数构造方法,传递哈希表的初始化容量和加载因子

  • 如果initialCapacity(初始化容量)小于0,直接抛出异常。
  • 如果initialCapacity大于最大容器,initialCapacity直接等于最大容器
    • MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
  • 如果loadFactor(加载因子)小于等于0,直接抛出异常
  • tableSizeFor(initialCapacity)方法计算哈希表的初始化容量。
    • 注意:哈希表是进行计算得出的容量,而初始化容量不直接等于我们传递的参数。

3.tableSizeFor方法分析

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

8  4  2  1规则->无论指定了多少容量,最终经过tableSizeFor这个方法计算之后,都会遵循8421规则去初始化列表容量

解析:该方法对我们传递的初始化容量进行位移运算,位移的结果是 8 4 2 1 码

  • 例如传递2,结果还是2,传递的是4,结果还是4。
  • 例如传递3,结果是4,传递5,结果是8,传递20,结果是32。

4.Node 内部类分析

哈希表是采用数组+链表的实现方法,HashMap中的内部类Node非常重要,证明HashSet是一个单向链表

 static class Node<K,V> implements Map.Entry<K,V> {
     final int hash;
     final K key;
     V value;
     Node<K,V> next;
 Node(int hash, K key, V value, Node<K,V> next) {
     this.hash = hash;
     this.key = key;
     this.value = value;
     this.next = next;
}

解析:内部类Node中具有4个成员变量

  • hash,对象的哈希值
  • key,作为键的对象
  • value,作为值得对象(讲解Set集合,不牵扯值得问题)
  • next,下一个节点对象

5.存储元素的put方法源码

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

解析:put方法中调研putVal方法,putVal方法中调用hash方法。

  • hash(key)方法:传递要存储的元素,获取对象的哈希值
  • putVal方法,传递对象哈希值和要存储的对象key

6.putVal方法源码

Node<K,V>[] tab; Node<K,V> p; int n, i;
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;

解析:方法中进行Node对象数组的判断,如果数组是null或者长度等于0,那么就会调研resize()方法进行数组的扩容。

7.resize方法的扩容计算

if (oldCap > 0) {
     if (oldCap >= MAXIMUM_CAPACITY) {
         threshold = Integer.MAX_VALUE;
         return oldTab;
     }
     else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
              oldCap >= DEFAULT_INITIAL_CAPACITY)
         newThr = oldThr << 1; // double threshold
}

解析:计算结果,新的数组容量=原始数组容量<<1,也就是乘以2。

8.确定元素存储的索引

if ((p = tab[i = (n - 1) & hash]) == null)
	 tab[i] = newNode(hash, key, value, null);

解析:i = (数组长度 - 1) & 对象的哈希值,会得到一个索引,然后在此索引下tab[i],创建链表对象。

不同哈希值的对象,也是有可能存储在同一个数组索引下。

其中resize()扩容的方法,默认是16
 tab[i] = newNode(hash, key, value, null);->将元素放在数组中  i就是索引

 i = (n - 1) & hash
     0000 0000 0000 0000 0000 0000 0000 1111->15
                                                    &   0&0=0 0&1=0 1&1=1
     0000 0000 0000 0001 0111 1000 0110 0011->96355
--------------------------------------------------------
     0000 0000 0000 0000 0000 0000 0000 0011->3
     0000 0000 0000 0000 0000 0000 0000 1111->15
                                                    &   0&0=0 0&1=0 1&1=1
     0000 0000 0001 0001 1111 1111 0001 0010->1179410
--------------------------------------------------------
     0000 0000 0000 0000 0000 0000 0000 0010->2

9.遇到重复哈希值的对象

 Node<K,V> e; K k;
 if (p.hash == hash &&
 	((k = p.key) == key || (key != null && key.equals(k))))
		 e = p;

解析:如果对象的哈希值相同,对象的equals方法返回true,判断为一个对象,进行覆盖操作。

else {
     for (int binCount = 0; ; ++binCount) {
     	if ((e = p.next) == null) {
     		p.next = newNode(hash, key, value, null);
     	if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
     		treeifyBin(tab, hash);
     	break;
 }

解析:如果对象哈希值相同,但是对象的equals方法返回false,将对此链表进行遍历,当链表没有下一个节点的时候,创建下一个节点存储对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值