Java学习-Day18

Java学习-Day18,第1张

集合

数组的不足:

  1. 创建时长度必须指定,而且指定了不能更改
  2. 保存的必须为同一类型的元素
  3. 使用数组增加删除比较麻烦(数组扩容要新创建一个数组)

集合:

  1. 可以动态保存任意多个对象,使用方便
  2. 提供了一系列方便的 *** 作对象的方法:add、remove、set、get
  3. 使用集合添加删除新元素的代码简洁
集合的框架体系

Java的集合类很多,主要分为两大类:

  1. 单列集合
  2. 双列集合

Collection接口有两个重要的子接口 List Set,他们的实现子类都是单列集合

Map接口实现的子类是双列集合,存放的K-V

Collection接口和常用方法
  • Collection接口实现类的特点

    1. collection实现子类可以存放多个元素,每个元素可以是Object
    2. 有些Collection的实现类,可以存放重复的元素,有些不可以
    3. 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
    4. Collection接口没有直接的实现子类,是通过它的子接口Set 和 List来实现的
  • Collection接口和常用方法

    List list = new ArrayList();
    //1、add 添加单个元素
    list.add("jack");
    list.add(10);	//list.add(new Integer(10))
    list.add(true);
    
    //2、remove 删除指定元素
    list.remove(0);//删除第一个元素
    list.remove(true);//指定删除某个元素
    
    //3、contains:查找元素是否存在
    System.out.println(list.contains("jack"));//T
    
    //4、size:获取元素个数
    System.out.println(list.size());//2
    
    //5、isEmpty:判断是否为空
    System.out.println(list.isEmpty());//F
    
    //6、clear:清空
    list.clear();
    System.out.println("list=" + list);
    
    //7、addAll:添加多个元素,形参是集合
    ArrayList list2 = new ArrayList();
    list2.add("红楼梦");
    list2.add("三国演义");
    list.addAll(list2);
    System.out.println("list=" + list);
    
    //8、containsAll:查找多个元素是否都存在
    System.out.println(list.containsAll(list2));//T
    
    //9、removeAll: 删除多个元素,形参是集合
    list.add("聊斋");
    list.removeAll(list2);
    
    //list直接输出是[元素1,元素2,...]
    
  • Collection 接口遍历元素方式 1-使用 Iterator(迭代器)

    1. Iterator对象称为迭代器,主要用于遍历 Collection集合中的元素。

    2. 所有实现了Collection接口的集合类都有一个iterator)方法,用以返回一个实现了lterator接口的对象,即可以返回一个迭代器。

    3. Iterator的结构

    4. lterator仅用于遍历集合,Iterator本身并不存放对象。

    5. lterator iterator = coll.iterator();//得到一个集合的迭代器
      hasNext():判断是否还有下一个元素while(iterator.hasNext()){
      //next0作用:1.下移2.将下移以后集合位置上的元素返回
      System.out.println(iterator.next();
      }
      //在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
      
      //itit  是while的快捷键
      // Ctrl + j 可以查看所有的快捷键
                         
                         
      Collection col = new ArrayList();
      col.add(new Book("三国演义", "罗贯中", 10.1));
      col.add(new Book("小李飞刀", "古龙", 5.1));
      col.add(new Book("红楼梦", "曹雪芹", 34.6));
      //System.out.println("col=" + col);
      //现在老师希望能够遍历 col 集合
      //1. 先得到 col 对应的 迭代器
      Iterator iterator = col.iterator();
      //2. 使用 while 循环遍历
      // while (iterator.hasNext()) {//判断是否还有数据
      // //返回下一个元素, 类型是 Object
      // Object obj = iterator.next();
      // System.out.println("obj=" + obj);
      // }
      //老师教大家一个快捷键, 快速生成 while => itit
      //显示所有的快捷键的的快捷键 ctrl + j
      while (iterator.hasNext()) {
      Object obj = iterator.next();
      System.out.println("obj=" + obj);
      } //
      3. 当退出 while 循环后 , 这时 iterator 迭代器, 指向最后的元素
      // iterator.next();//NoSuchElementException
      //4. 如果希望再次遍历, 需要重置我们的迭代器
      iterator = col.iterator();
      System.out.println("===第二次遍历===");
      while (iterator.hasNext()) {
      Object obj = iterator.next();
      System.out.println("obj=" + obj);
      }
      
  • Collection 接口遍历对象方式 2-for 循环增强

    增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。

    基本语法
    for(元素类型元素名:集合名或数组名){

    访问元素

    }

    /*
     增强for循环底层还是调用的迭代器,可以理解为简化版本的迭代器
     快捷方式:I 或者 iter
    */
    
List接口和常用方法
  • List接口是 Collection接口的子接口

    1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

    2. List集合中的每个元素都有其对应的顺序索引,即支持索引。

      // 索引是从 0 开始的
      System.out.println(list.get(3));//第四个
      
    3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

  • 方法

    //在 index = 1 的位置插入一个对象,不写索引默认插入在最后
    list.add(1, "韩顺平");
    
    //boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
    List list2 = new ArrayList();
    list2.add("jack");
    list2.add("tom");
    list.addAll(1, list2);
    
    // Object get(int index):获取指定 index 位置的元素
    
    
    // int indexOf(Object obj):返回 obj 在集合中首次出现的位置
    System.out.println(list.indexOf("tom"));//2
    
    // int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
    list.add("韩顺平");
    System.out.println("list=" + list);
    System.out.println(list.lastIndexOf("韩顺平"));
    
    // Object remove(int index):移除指定 index 位置的元素, 并返回此元素
    list.remove(0);
    System.out.println("list=" + list);
    
    // Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.索引必须存在
    list.set(1, "玛丽");
    System.out.println("list=" + list);
    
    // List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
    // 注意返回的子集合 fromIndex <= subList < toIndex
    List returnlist = list.subList(0, 2);
    //前闭后开
    System.out.println("returnlist=" + returnlist);
    
  • List的三种遍历方式:

    1. 迭代器
    2. 增强for
    3. 普通for循环
    //List 接口的实现子类 Vector LinkedList
    //List list = new ArrayList();
    //List list = new Vector();
    //List list = new LinkedList();
    /
    //遍历
    //1. 迭代器
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        Object obj = iterator.next();
        System.out.println(obj);
    } 
    
    System.out.println("=====增强 for=====");
    //2. 增强 for
    for (Object o : list) {
        System.out.println("o=" + o);
    } 
    
    System.out.println("=====普通 for====");
    //3. 使用普通 for
    for (int i = 0; i < list.size(); i++) {
        System.out.println("对象=" + list.get(i));
    }
    
ArrayList
  • ArrayList注意事项

    1. permits all elements可以放任意元素, including null , ArrayList可以加入null,并且可以放多个
    2. ArrayList是由数组来实现数据存储的(看源码)
    3. ArrayList基本等同于Vector,除了**ArrayList是线程不安全(执行效率高))**看源码,没有synchronize修饰,不互斥.在多线程情况下,不建议使用ArrayList
  • ArrayList 的底层 *** 作机制源码分析(重点, 难点.)

    1. ArrayList中维护了一个Object类型的数组elementData. [debug看源码]

    transient Object[] elementData;//transient表示瞬间,短暂的,表示该属性不会被序列化

    1. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。

    2. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,**如果需要扩容,则直接扩容elementData为1.5倍。**一开始指定大小为1,扩容1.5倍还是1,此时会将扩容后的1和所需的数组长度做比较,选择较大的

    源码:

    //老韩解读源码
    //注意, 注意, 注意, Idea 默认情况下, Debug 显示的数据是简化后的, 如果希望看到完整的数据需要做设置.
    //使用无参构造器创建 ArrayList 对象
    //ArrayList list = new ArrayList();
    ArrayList list = new ArrayList(8);
    
    //使用 for 给 list 集合添加 1-10 数据
    for (int i = 1; i <= 10; i++) {
    	list.add(i);
    } 
    
    //使用 for 给 list 集合添加 11-15 数据
    for (int i = 11; i <= 15; i++) {
    	list.add(i);
    } 
    list.add(100);
    list.add(200);
    list.add(null);
    

Vector底层结构和源码剖析
  1. Vector类的定义说明

public class vector extends AbstractList
implements List,RandomAccess,cloneable,Serializable

  1. Vector底层也是一个对象数组

    protected Object[] elementData;

  2. Vector是线程同步的,即线程安全, Vector类的 *** 作方法带有synchronized

public synchronized E get(int index){i
if (index >= elementCount)
throw new ArraylndexOutOfBoundsException(index);return elementData(index);

  1. 在开发中,需要线程同步安全时,考虑使用Vector

Vector和ArrayList的比较:

//无参构造器
//有参数的构造
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
} v
ector.add(100);
System.out.println("vector=" + vector);
//老韩解读源码
//1. new Vector() 底层
/*
public Vector() {
this(10);
}
补充: 如果是 Vector vector = new Vector(8);
走的方法:
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2. vector.add(i)
2.1 //下面这个方法就添加数据到 vector 集合
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2.3 //如果 需要的数组大小 不够用, 就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍.
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
*/
Linkedlist底层结构
  • LinkedList的全面说明

    1. LinkedList实现了双向链表和双端队列特点
    2. 可以添加任意元素(元素可以重复),包括null
    3. 线程不安全,没有实现同步
  • LinkedList的底层 *** 作机制

    1. LinkedList底层维护了一个双向链表
    2. LinkedList中维护了两个属性first和last分别指向首节点和尾节点
    3. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
    4. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
    LinkedList linkedList = new LinkedList();
    linkedList.add(1);
    linkedList.add(2);
    linkedList.add(3);
    System.out.println("linkedList=" + linkedList);
    
    //演示一个删除结点的
    linkedList.remove(); // 这里默认删除的是第一个结点
    //linkedList.remove(2);
    System.out.println("linkedList=" + linkedList);
    
    //修改某个结点对象
    linkedList.set(1, 999);
    System.out.println("linkedList=" + linkedList);
    
    //得到某个结点对象
    //get(1) 是得到双向链表的第二个对象
    Object o = linkedList.get(1);
    System.out.println(o);//999
    
    //因为 LinkedList 是 实现了 List 接口, 三种遍历方式
    System.out.println("===LinkeList 遍历迭代器====");
    Iterator iterator = linkedList.iterator();
    while (iterator.hasNext()) {
    Object next = iterator.next();
    System.out.println("next=" + next);
    }
    
    System.out.println("===LinkeList 遍历增强 for====");
    for (Object o1 : linkedList) {
    System.out.println("o1=" + o1);
    } 
    
    System.out.println("===LinkeList 遍历普通 for====");
    for (int i = 0; i < linkedList.size(); i++) {
    System.out.println(linkedList.get(i));
    }
    
    //老韩源码阅读.
    /* 
    1. LinkedList linkedList = new LinkedList();
    public LinkedList() {}
    
    2. 这时 linkeList 的属性 first = null last = null
    
    3. 执行 添加
    public boolean add(E e) {
    linkLast(e); //在尾部执行添加方法
    return true;
    }
    4.将新的结点, 加入到双向链表的最后
    void linkLast(E e) {
    final Node l = last;
    final Node newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
    first = newNode;
    else
    l.next = newNode;
    size++;
    modCount++;
    }
    */
    
    /*
    老韩读源码 linkedList.remove();
    // 这里默认删除的是第一个结点 
    //也可以remove(index) remove(Object obj)
    1. 执行 removeFirst
    public E remove() {
    return removeFirst();
    }
    2. 执行
    public E removeFirst() {
    final Node f = first;
    if (f == null)
    throw new NoSuchElementException();
    return unlinkFirst(f);
    }
    3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
    private E unlinkFirst(Node f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
    last = null;
    else
    next.prev = null;
    size--;
    modCount++;
    return element;
    }
    */
    
  • ArrayList和LinkedList比较

Set接口和常用方法
  • Set接口基本介绍

    1. 无序(添加和取出的顺序不一致),没有索引
    2. 不允许重复元素,所以最多包含一个null
    3. JDK API中Set接口的实现类有:HashSet TreeSet 等
    4. 是Collection接口的子接口,方法、遍历方式一样
  • Set接口的常用方法

    和Collection接口一样

  • Set接口的遍历方式

    和Collection接口的遍历方式一样

    1. 迭代器
    2. 增强for
    3. 不能使用索引方式(也就是不能用普通for)
  • //老韩解读
    //1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
    //2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
    //3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
    //4. 注意: 取出的顺序的顺序虽然不是添加的顺序, 但是他是固定的.
    Set set = new HashSet();
    set.add("john");
    set.add("lucy");
    set.add("john");//重复
    set.add("jack");
    set.add("hsp");
    set.add("mary");
    set.add(null);//
    set.add(null);//再次添加 null
    for(int i = 0; i <10;i ++) {
    System.out.println("set=" + set);
    }//取多少次都一样
    
    
    //遍历
    //方式 1: 使用迭代器
    System.out.println("=====使用迭代器====");
    Iterator iterator = set.iterator();
    while (iterator.hasNext()) {
    Object obj = iterator.next();
    System.out.println("obj=" + obj);
    } 
    set.remove(null);//删除null
    
    
    //方式 2: 增强 for  增强for的底层就是迭代器
    System.out.println("=====增强 for====");
    for (Object o : set) {
    System.out.println("o=" + o);
    } 
    //set 接口对象, 不能通过索引来获取
    
Set接口实现类-HashSet
  • 基本介绍

    1. HashSet实现了Set接口

    2. HashSet实际上是HashMap

      public Hashset {
      map = new HashMap<>();//源码
      }
      
    3. 可以存放null值,但是只能有一个null

    4. HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放元素的顺序和取出顺序一致)

    5. 不能有重复元素/对象

    //老韩解读
    //1. 构造器走的源码
    /*
    public HashSet() {
    map = new HashMap<>();
    }
    2. HashSet 可以存放 null ,但是只能有一个 null,即元素不能重复
    */
    Set hashSet = new HashSet();
    hashSet.add(null);
    hashSet.add(null);
    System.out.println("hashSet=" + hashSet);
    
    HashSet set = new HashSet();
    //说明
    //1. 在执行 add 方法后, 会返回一个 boolean 值
    //2. 如果添加成功, 返回 true, 否则返回 false
    //3. 可以通过 remove 指定删除哪个对象
    System.out.println(set.add("john"));//T
    System.out.println(set.add("lucy"));//T
    System.out.println(set.add("john"));//F
    System.out.println(set.add("jack"));//T
    System.out.println(set.add("Rose"));//T
    
    set.remove("john");
    System.out.println("set=" + set);//3 个
    //
    set = new HashSet();
    System.out.println("set=" + set);//0
    //4 Hashset 不能添加相同的元素/数据?
    set.add("lucy");//添加成功
    set.add("lucy");//加入不了
    set.add(new Dog("tom"));//OK
    set.add(new Dog("tom"));//Ok
    System.out.println("set=" + set);
    
    //看源码, 做分析, 先给小伙伴留一个坑, 以后讲完源码, 你就了然
    //去看他的源码, 即 add 到底发生了什么?=> 底层机制.
    set.add(new String("hsp"));//ok
    set.add(new String("hsp"));//加入不了.
    System.out.println("set=" + set);
    
  • HashSet底层机制

    HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)

    1. HashSet底层是HashMap
    2. 添加一个元素时,先得到hash值-会转成->索引值
    3. 找到存储数据表table,看这个索引位置是否已经存放的有元素
    4. 如果没有,直接加入
    5. 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后(String重写了equals方法,所以两个new的对象的哈希值是一样的)
    6. 在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
    public class HashSetSource {
    public static void main(String[] args) {
    HashSet hashSet = new HashSet();
    hashSet.add("java");//到此位置, 第 1 次 add 分析完毕.
    hashSet.add("php");//到此位置, 第 2 次 add 分析完毕
    hashSet.add("java");
    System.out.println("set=" + hashSet);
    /*
    老韩对 HashSet 的源码解读
    1. 执行 HashSet()
    public HashSet() {
    map = new HashMap<>();
    }
    
    2. 执行 add()
    public boolean add(E e) {	//e = "java"
    return map.put(e, PRESENT)==null;	//(static) PRESENT = new Object();
    }
    
    3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 
    算法 h = key.hashCode()) ^ (h >>> 16) 按位抑或
    public V put(K key, V value){
    //key = "java" value = PRESENT 共享
    return putVal(hash(key), key, value, false, true);
    }
    
    4.执行 putVal
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    boolean evict) {
    Node[] tab; Node p; int n, i; //定义了辅助变量
    //table 就是 HashMap 的一个属性, 类型是 Node[]
    //if 语句表示如果当前 table 是 null, 或者 大小=0
    //就是第一次扩容, 到 16 个空间.
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
    //(1)根据 key, 得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置
    //并把这个位置的对象, 赋给 p
    //(2)判断 p 是否为 null
    //(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
    //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
    else {
    //一个开发技巧提示: 在需要局部变量(辅助变量)时候, 在创建
    Node e; K k; //
    
    
    //如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
    //并且满足 下面两个条件之一:
    //(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
    //(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同 就不能加入(所以String重写了equals方法,即使new了两个对象,也是一样的)
    if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))
    e = p;
    
    //再判断 p 是不是一颗红黑树,
    //如果是一颗红黑树, 就调用 putTreeVal , 来进行添加
    else if (p instanceof TreeNode)
    e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
    else {//如果 table 对应索引位置, 已经是一个链表, 就使用 for 循环比较
    //(1) 依次和该链表的每一个元素比较后, 都不相同, 则加入到该链表的最后
    // 注意在把元素添加到链表后, 立即判断 该链表是否已经达到 8 个结点
    // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
    // 注意, 在转成红黑树时, 要进行判断, 判断条件
    // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
    // resize();
    // 如果上面条件成立, 先 table 扩容.
    // 只有上面条件不成立时, 才进行转成红黑树
    //(2) 依次和该链表的每一个元素比较过程中, 如果有相同情况,就直接 break
    for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {
    p.next = newNode(hash, key, value, null);
    if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
    treeifyBin(tab, hash);
    break;
    } if
    (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    break;
    p = e;
    }
    } if
    (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
    e.value = value;
    afterNodeAccess(e);
    return oldValue;
    }
    } +
    +modCount;
    //size 就是我们每加入一个结点 Node(k,v,h,next), size++
    if (++size > threshold)
    resize();//扩容
    afterNodeInsertion(evict);
    return null;
    }
    */
    

    注:equals可以重写

    HashSet的扩容和红黑树机制

    1. HashSet底层是HashMap.第一次添加时,table数组扩容到16,临界值(threshold)是16 * 加载因子(loadFactor)是0.75 = 12
    2. 如果table 数组使用到了临界值12(总共所有的元素),就会扩容到16 * 2=32,新的临界值就是32 * 0.75 =24,依次类推
    3. 如果一条链表的元素个数达到8,增加一个就扩容一次,直到扩容到64,开始树化
    4. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/723440.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存