Java集合

 java常用分类如下:

 Collection

 

 集合主要是两组(单列集合,双列集合)

Collection集合框架结构

  Map集合框架结构

 

 

 

 

 Collection类的主要方法

collection包含如下方法

 这里将常用的演示出来

package 集合概述.Collection;

import java.util.ArrayList;
import java.util.List;


public class CollectionMethod {
    public static void main(String[] args) {
        List list=new ArrayList<>();

//        add:添加单个元素
        list.add("jack");
        list.add(10);//自动装箱为Integer  对象的形式存储的  list.add(new Integer(10) )
        list.add(true);
//        remove:  删出指定元素
        list.remove(0);  //  删除第一个元素
        list.remove("jack"); //指定删除某个元素
//        contains:查找某个元素是否存在
        System.out.println(list.contains("jack"));//  T
//        size:获取元素的个数
        System.out.println(list.size());

//        isEmpty:判断是否为空
        System.out.println(list.isEmpty());
//        clear:清空
        list.clear();
//        addAll:添加多个元素     将list2添加进去
        List list2=new ArrayList();
        list2.add("红楼梦");
        list2.add("三国");
        list.addAll(list2);

//        containsAll:查看多个元素是否都存在
        System.out.println(list.containsAll(list2));//T

//        removeAll:删除多个元素
        list.removeAll(list2);
    }
}

 

 

  List

 

 List的三种遍历方式 

  1. 迭代器方式
  2. 增强for
  3. 使用普通for

 List排序练习

 ArrayList

在添加前会先使用Integer valueOf  的方法将int类型的数据进行自动装箱

确定容量够不够

 elementData.length指的是当前容量,比较的是:如果当前容量小于grow里面的容量,就扩容

 minCapacity    最小需要的容量

使用有参构造器的ArrayList

 Vector

 vector与ArrayLis的比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高

如果有参构造1.5倍

如果是无参

1.第一次10

2.从第二次开始按照1.5倍扩容

Vector可变数组jdk1.0

如果是无惨,默认10,满后,就按2倍扩容

如果指定大小,则每次直接按2倍扩容

 针对以下代码执行流程,分析源码

package 集合概述.Vector;

import java.util.Vector;

@SuppressWarnings({"All"})
public class Vector_ {
    public static void main(String[] args) {

        //无参构造器
        Vector vector=new Vector();
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        

    }
}

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);  就是 扩容两倍这段代码结果等同于  newCapacity = oldCapacity + 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

双向链表模拟

 模拟一个双向链表进行简单操作

package 集合概述.LinkedList;

/** 
* @Description: 简单  模拟简单的双向链表
* @Param:  
* @return:  
* @Author: 熊辉
* @Date: 2022/7/9 
*/ 
public class LinkedList01 {
    public static void main(String[] args) {
        //模拟一个简答的双向链表

        Node jack=new Node("Jack");
        Node tom = new Node("Tom");
        Node xh = new Node("xh");

        //连接三个节点,形成双向链表
        //  jack -> tom  -> xh
        jack.next=tom;
        tom.next=xh;
        // xh -> tom -> jack
        xh.pre=tom;
        tom.pre=jack;

        Node first=jack;  //让first引用指向jack,就是双向链表的头节点
        Node last=xh;     //让last引用指向xh,就是双向链表的尾节点


        System.out.println("===从头到尾进行遍历====");
        //从头到尾进行遍历
        while(true){
            if (first==null){
                break;
            }
            //输出first的信息
            System.out.println(first);
            first=first.next;
        }

        System.out.println("===从尾到到进行遍历====");
        //从尾到头进行遍历
        while(true){
            if (last==null){
                break;
            }
            //输出first的信息
            System.out.println(last);
            last=last.pre;
        }

        //演示链表的添加对象/数据
        //要求在,是在tom和xh中间,插入一个对象  smith

        // 1. 先创建一个  Node 节点 ,名字就是 smith
        Node smith = new Node("smith");

        //下面就把smith加入到双向链表
        smith.pre=tom;

        smith.next=xh;

        tom.next=smith;
        xh.pre=smith;

        //重置下first,让frist再次指向第一个人
       first=jack;
        System.out.println("===从头到尾进行遍历====");
        //从头到尾进行遍历
        while(true){
            if (first==null){
                break;
            }
            //输出first的信息
            System.out.println(first);
            first=first.next;
        }


        //  让last重新指向最后一个节点
        last=xh;
        System.out.println("===从尾到到进行遍历====");
        //从尾到头进行遍历
        while(true){
            if (last==null){
                break;
            }
            //输出first的信息
            System.out.println(last);
            last=last.pre;
        }


    }
}


//定义一个Node类,Node对象 ,表示双向链表的一个节点
class Node{
    public Object item;  //真正存放数据的地方
    public Node next;   //指向下一个节点
    public Node pre;       //指向前一个节点
    public Node(Object name){
        this.item=name;
    }
    public String toString(){
        return "Node name="+item;
    }
}

 

 针对如下代码运行的源码分析

package 集合概述.LinkedList;

import java.util.LinkedList;

@SuppressWarnings({"all"})
public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList ="+linkedList);
        linkedList.remove();  //默认删除的是第一个节点
        System.out.println("linkedList ="+linkedList);


    }
}

 添加的执行流程

1. LinkedList linkedList = new LinkedList();

    public LinkedList() {}

2. 这时 linkedList  的属性  first =null  last =null


3. 执行add方法  添加

 public boolean add(E e) {
        linkLast(e);
        return true;
    }

4. 将新的节点,加入到双向链表的最后

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

LinkedList最优使用迭代器遍历,通过节点去遍历,当使用for循环时,get(i)获取某一元素时都需要对整个List重新遍历,迭代器遍历效率最高

 执行删除操作的流程

linkedList.remove();  //默认删除的是第一个节点

1.  执行   removeFirst();

    public E remove() {
        return removeFirst();
    }

2. 执行


 public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
 

3.  执行  unlinkFirst,将 f 指向的双向链表的第一个节点拿掉

    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> 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;
    }

List集合的选择

set

set接口基本介绍

  1. 无序(添加和取出顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个null

Set 接口和常用方法

 set 接口的遍历方式

set接口对象 没有索引没有get方法 不能通过索引来获取因此没有传统的for的遍历方式

1.迭代器遍历

2.增强for循环遍历(增强for的底层就是迭代器)

package 集合概述.set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

@SuppressWarnings({"all"})
public class SetMethod {
    public static void main(String[] args) {

        //1. 以set接口的实现类 HashSet  来讲解set 接口的方法
        Set set=new HashSet();
        set.add("John");
        set.add("lucy");
        set.add("John");
        set.add("jack");
        set.add(null);
        set.add(null);

        System.out.println(set);


        //遍历:
        //1.方式一:使用迭代器
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }


        //方式二:增强for(增强for的底层就是迭代器)
        for (Object o : set) {
            System.out.println(o);
        }
    }
}

HashSet

    构造器走的源码 
1. Set set=new HashSet();  构造器走的是HashMap

   public HashSet() {
        map = new HashMap<>();
    }

2. HashSet可以存放空值但是只能有一个空值(即元素不可以重复)
  set.add(null);
  set.add(null);

//HashSet不能添加相同的元素
        Set set=new HashSet();

        set.add("lucy");  //添加成功
        set.add("lucy");  //失败
        set.add(new Dog("Tom")); //添加成功
        set.add(new Dog("Tom")); //添加成功   new 出来的是两个不一样的对象
        System.out.println(set);


class Dog{
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

 可以看到两个两个Tom都被添加进去了,lucy只能添加一个符合set集合不可添加重复元素的性质

 那么我们再来看一道经典的面试题

 set.add(new String("xh")); //OK
 set.add(new String("xh"));  //添加失败

看完这个我们又会觉得和我们之前的结论产生冲突 new String("xh") 是两个不同的对象为啥会添加失败呢

 

先看结论再来分析过程

注意注意:这里说的table的大小指的是 当我们向HashSet增加一个元素, -> Node -> 加入Table,就算是增加了一个元素

 

下面是hashset存放元素的过程

hashset是基于hashmap实现的

public class HashSetSource {
    public static void main(String[] args) {
        
        HashSet hashSet=new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        System.out.println(hashSet);
    }
}

针对如上代码分析其执行流程:


1.执行HashSet()      
    public HashSet() {
        map = new HashMap<>();
    }

2.执行add()方法
 
    public boolean add(E e) {  // e="java"
        return map.put(e, PRESENT)==null;  // PRESENT 就是一个无意义的静态对象用来占位的 private static final Object 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<K,V>[] tab; Node<K,V> 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<K,V> e; K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足下面两个条件之一的:
// (1) 准备加入的key 和 p 指向的Ndoe 节点 的key 是同一个对象
// (2) p指向的Node结点的 key的equals() 和准备加入的key比较后相同  即 (1)是先比较是否是同一个对象,(2)是在不是同一个对象的前提下比较两个对象的值是否相等   ==判断地址是否相同(判断是否是同一个对象),equals判断内容是否相同(判断两个对象的属性是否相同)
            if (p.hash == hash && 
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
// 在判断 p 是不是一颗红黑树
//如果是一颗红黑树,就调用putTreeVal方法 进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else { //如果table对应索引位置,已经是一个链表了 , 就使用for 循环比较
            //(1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
            //  注意在把元素添加到链表后,立即判断,该链表中是否已经达到8个节点,如果达到就调用treeifyBin(tab, hash);方法 对当前这个链表进行树化(转成红黑树)  注意: 在转成红黑树时,要进行判断,如果该table数组的大小 <64 则先扩容,不树化
            // (2) 依次比较的过程中,如果有相同的情况,就直接 break
                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;
                    }
                    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)
        if (++size > threshold)
            resize();  //扩容
        afterNodeInsertion(evict);
        return null;
    }

LinkedHashSet

 

 

 

 TreeSet

       TreeSet treeSet = new TreeSet();

        treeSet.add("bbb");
        treeSet.add("aaa");

        treeSet.add("ccc");

        System.out.println(treeSet);

1. new TreeSet()构造器默认执行的是TreeMap

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

Map

 

 

 

 map接口常用方法

 Map遍历方式

 

 

 

 

 

 

 HashMap

常见面试问题

 

 HashMap运行机制

    HashMap map=new HashMap();
        map.put("java",10);
        map.put("php",10);
        map.put("java",20);
        System.out.println(map);

1.  执行构造器  new HashMap()  初始化加载因子 loadfactor=0.75 HashMap$Node[] table=null

2. 执行put方法  会调用hash方法计算key的值 (h = key.hashCode()) ^ (h >>> 16); 

    public V put(K key, V value) {  k="java"  value=10
        return putVal(hash(key), key, value, false, true);
    }

3.执行putVal

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;   //辅助变量
//如果底层的table数组为null或者length=0,就扩容到16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
//计算元素下标:将 hash 值和数组长度-1 进行与操作,保证结果不会超过 table 数组范围 取出hash值对应的table的索引位置的Node,如果为null,就直接把加入的k-v,创建成一个Node,加入到该位置即可
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;  //辅助变量
            if (p.hash == hash &&  //如果table表的索引位置的key的Hash值相同和新的key的hash值相同 并 满足(table现有结点的key和准备添加的key是同一个对象 || equals 返回真) 就认为不能加入新的k-V
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode) //如果当前table已有的Node是红黑树,就按照红黑树的方式处理
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
//如果找到的结点的后面是链表就循环比较
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {//如果整个链表,没有和他相同的,就加到该链表的最后
                        p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到达8个,到8个后就调用 treeifyBin方法进行红黑树的树化,
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&  //再循环比较过程中,发现有相同的就不添加了,break,就只是替换value
                        ((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;   //key 相同 将 value进行替换
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount; //每增加一个Node就size++
        if (++size > threshold[12-24-48]) //如果size大于临界值 就扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }

5. 关于树化(转成红黑树)
//如果table表为空,或者大小还没有到64,就暂时不树化,而是进行扩容 
//否则才会真正的树化-> 剪枝

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();

 HashTable

 

TreeMap

Properties

 集合的选择

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值