JAVA集合学习笔记

笔记根据教学视频总结视频链接

数组与集合的优缺点

数组的优点数组的缺点
定义简单,访问迅速初始化后长度不可变
定义好后,类型单一
提供的方法有限,对插入,删除等操作不便,效率不高
没有获取数组实际元素的方法
要求连续的存储空间,对于较多元素,需要开辟较大连续空间
集合的优点
动态保存多个对象,长度可变
可保存多个类型元素
提供操作对象元素的方法,add,remove,set,get等

Collection

这里只列出常用的集合

Collection 特点

  • collection实现子类存放元素都是object对象,会自动装箱

  • 如果不用泛型,默认返回object对象

    List list = new ArrayList();//由于接口无法实例化,这里使用ArrayList类实现接口
    list.add(1);//自动装箱,存放的是Integer对象 list.add(new (Integer(1)))
    list.add("tom");
    list.add(true);
    

Collection常用方法

  • add : 添加单个元素

  • remove:删除单个元素

  • contains:查找单个元素是否存在

  • size:获取元素个数

  • isEmpty:判断是否为空

  • clear:清空

  • addAll:添加多个元素(先将元素存入集合中,再使用该方法添加集合)

  • containsAll:查找多个元素是否存在(也是通过集合操作)

  • removeAll:删除多个元素(通过集合操作)

Collection迭代遍历

Iterator对象迭代器

Iterator对象迭代器在Iterable中,所有集合都实现了该方法;主要使用两个方法:

  • hasNext:判断下一个元素是否存在
  • next:取出下一个元素

在使用next前必须先调用hasNext判断下一个元素是否存在。若不调用,且下一条数据为空时,会抛出异常。

Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()){
    //返回元素对象,
    Object obj = iterator.next();
    System.out.println(obj);
}

增强for循环

for (Object obj:list){
    System.out.println(obj);
}
  • 底层还是Iterator迭代器

  • 可以理解为简化版的迭代器

普通for循环

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

List

List 也是接口,这里使用ArrayList类实现接口

特点

  • List 集合元素有序,可重复

    List<Object> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(1);
    System.out.println(list);
    
    输出:[1, 2, 3, 4, 1]
    
  • List 每个元素有对应索引,支持索引操作,索引从0开始

    List<Object> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(1);
    System.out.println(list.get(3));
    
    输出:4
    

List 常用方法

  • add(object obj):加入一个元素

  • add(int index,Object obj):在下标为index位置插入元素obj

    List<Object> list = new ArrayList<>();
    list.add(0);
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(1,100);
    System.out.println(list);
    
    输出:[0, 100, 1, 2, 3]
    
  • addAll(Collection eles):添加多个元素(先将元素存入集合中,再使用该方法添加集合)

  • addAll(int index,Collection eles):在index位置插入集合eles中的元素

  • get(int index):获取index位置上的元素

  • indexOf(object obj):返回obj在集合中首次出现的位置

  • lastIndexOf(object obj):返回obj在集合中最后一次出现的位置

  • remove(int index):移除index位置上的元素,并返回该元素

  • set(int index,object obj):替换index位置尚德元素为obj,索引必须存在

  • subList(int fromIndex,int toIndex):返回子串,左闭右开[fromIndex,toIndex)

ArrayList

特点

  • 可放多个null

    List<Object> list = new ArrayList<>();
    list.add(null);
    list.add(null);
    list.add(2);
    System.out.println(list);
    
    输出:[null, null, 2]
    
  • 底层由数组实现

  • ArrayList基本等同于Vector,但ArrayList是线程不安全,没有用synchronized修饰(执行效率高,因为不用考虑线程安全问题),

  • 在多线性下不建议使用ArrayList

ArrayList扩容机制(重点)

  • ArrayList 中存放对象使用Object类型的数组

    transient Object[] elementData; //transient修饰表示该属性不会被序列化
    
  • 创建ArrayList 对象时,如果使用无参构造器,则初始化数组大小为0,第一次添加元素,数组扩容为10,以后每次扩容为之前的1.5

    List list = new ArrayList();//初始化大小为0
    
  • 如果直接使用指定大小的有参构造器,则初始化数组大小为指定大小,以后每次扩容为之前的1.5

    List list = new ArrayList(5);//初始化大小为指定的5
    

无参构造 源码

  • 初始化数组,创建elementData数组

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//数组为空,即大小为0
    
  • 执行add

    //类型装箱操作,不同类型对应不同包装类
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
  • 先判断数组容量是否够用

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
  • 如果容量不够,调用grow进行扩容

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length; //第一次oldCapacity=0
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //1.5倍扩容
        if (newCapacity - minCapacity < 0) //如果第一次扩容newCapacity=0
            newCapacity = minCapacity;  //扩容结果为minCapacity=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, newCapacity); //数组扩容为newCapacity大小,copyOf会保留原先数据
    }
    

Vector

特点

  • 可放多个null

  • 底层也是数组

  • Vector是线程同步,即线程安全(效率不高),Vector操作方法带有 synchronized关键字

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    

扩容机制

  • 如果使用无参构造器,则初始化数组大小为10,以后每次扩容为之前的2倍,可在构造函数中改变增量大小
  • 如果直接使用指定大小的有参构造器,则初始化数组大小为指定大小,以后每次扩容为之前的2倍,可在构造函数中改变增量大小

LinkedList

特点

  • 底层实现双向链表和双端队列
  • 线程不安全,没有实现线程同步
  • 增删不需要数组扩容,效率高

LinkedList底层操作机制

  • LinkedList底层维护一个双向链表

  • 维护两个节点,first和last,分别指向首节点和尾结点

  • 每个节点(Node对象)里又维护prev、next、item三个属性

  • prev直向前一个节点,next指向后一个节点,最终实现双向链表

LinkedList方法

  • add(object obj):尾插法,插入对象

  • add(int index, object obj): 在下标为index的位置插入obj元素

    List list = new LinkedList();
    list.add(0);
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(3,10);
    System.out.println(list);
    
    输出:[0, 1, 2, 10, 3]
    
  • set(int index, object obj):将下标为index的位置元素修改为obj元素

    List list = new LinkedList();
    list.add(0);
    list.add(1);
    list.add(2);
    list.add(3);
    list.set(2,"obj");
    System.out.println(list);
    
    输出:[0, 1, obj, 3]
    
  • remove(int index):删除下标为index的元素

  • remove(object obj):删除对象为obj的元素,若有多个obj,则删除第一个

  • get(int index):获取下标为index的元素

  • 。。。

ArrayList和LinkedList比较

底层结构增删效率改查效率
ArrayList数组数组扩容,较低较高
LinkedList双向链表操作节点指针,较高较低

如何选择

  • 改查多,选 ArrayList

  • 增删多,选LinkedList

  • 一般来说,80%-90%都是查询,所以大部分选ArrayList

Set

HashSet

特点

  • 无序(添加和取出顺序不一致),没有索引

  • 相同的一组数据,无论添加顺序怎么变化,输出顺序固定(内部根据hash值排序)

  • 不允许重复元素,所以最多包含一个null

            Set set = new HashSet();
            set.add(1);
            set.add(3);
            set.add("jack");
            set.add("jack"); //相同元素,不能添加,在常量池中存在jack
            set.add(new Dog("tom"));
            set.add(4);
            set.add(new Dog("tom")); // 不同对象,地址不同,所以可以加入
            set.add(null);
            set.add(2);
            set.add(new String("lucy"));
            set.add(new String("lucy")); //不能添加
            System.out.println(set);  
    
           输出:[null, 1, 2, 3, Dog{name='tom'}, 4, Dog{name='tom'}, lucy, jack]
    
  • HashSet底层是HashMap实现,而HashMap底层由数组+链表+红黑树实现

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

Set常用方法

和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样

迭代方式

  • 可以使用迭代器
  • 增强for循环(本质也是迭代器)

Set没有索引,所以不能用普通索引for循环遍历

为何相同元素不能添加

  • HashSet底层是HashMap

  • 添加一个元素时,先得到hash值,然后转成索引

  • 找到存储数据表table,看这个索引位置是否已存放元素,没有,则直接加入该索引位置上

  • 如果该索引位置上有元素,则调用equals比较,如果比较结果相同,则放弃加入,不同,则添加该索引位置的链表后面

  • 在jdk8中,如果链表长度等于8,同时数组table的长度等于64,则会转为红黑树

    添加元素源码分析

        Set set = new HashSet();
        set.add("tom");
        set.add("jack");
        set.add("jack");
    
  • 执行 HashSet()

    public HashSet() {
        map = new HashMap<>();
    }
    
  • 执行add()方法

    public boolean add(E e) {
        // e = "tom"
        return map.put(e, PRESENT)==null;    //(static) PRESENT=new Object()
    }
    
  • 执行put()方法,返回值为空,代表该位置为空,可放对象。返回值不为空,表示该位置已有对象且和该对象值相等,不能放对象

    public V put(K key, V value) { 
        //key = "tom"  value = PRESENT  (PRESENT没实际意义,占位)
        return putVal(hash(key), key, value, false, true);
    }
    
  • 执行hash()方法,得到key对应的hash值,不是hashcode值。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  //得到hashcode值,再无符号右移16位,防止冲突
    }
    
  • 执行putVal()方法,将key的hash值

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;  //定义辅助变量
        //如果当前tab为空,或大小为0,则第一次扩容,tab数组达到16
        if ((tab = table) == null || (n = tab.length) == 0)  //table是数组长度,HashMap的一个属性
            n = (tab = resize()).length; 
        //根据key,得到的hash值,去计算key应该放到table的那个索引位置,并把这个位置对象赋给p
        //如果p为空,表示该索引位置没存放数据,创建node,存值
        if ((p = tab[i = (n - 1) & hash]) == null) 
            tab[i] = newNode(hash, key, value, null);
        //如果p不为空,表示该索引位置有数据
        else {
            Node<K,V> e; K k;
             //如果当前索引位置对应的链表的第一个元素与准备添加key的hash值相等
            //并且满足当前节点对象和key是同一个对象或比较equals是否相等相等,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);
            //如果该位置已经是一个链表,则使用for循环判断有无重复
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //添加后,判断链表长度是否到达8,如果到达,则该位置的链表转为红黑树操作
                       //但treeifyBin方法会判断table数组长度是否到达64,达到,树化,否则,table数组先扩容,对象仍放在链表后面(此时链表长度已超过8)
                        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;
        
        //判断是否需要扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    

HashSet扩容机制

  • HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75=12,这里的12不是table长度,而是HashSet里的元素总个数size
  • 如果元素个数size到达12时,table数组就会扩容两倍到32,新的临界值是32*0.75=24,以此类推
  • 在jdk8中,如果一条链表的元素个数到达YREEIFY_THRESHOLD(默认是8),就会启动转红黑树操作,但是在转红黑树treeifyBin()方法时,会先判断table是否到达64,如果到达64,则转红黑树。没到64,先对table数组扩容,对象仍放在链表后面(此时链表长度已超过8)

LinkedHashSet

LinkedHashSet特点

  • LinkedHashSet是HashSet的子类,底层是LinkedHashMap,底层维护了一个数组+双向链表
  • LinkedHashSet元素根据hashcode值决定元素的存储位置,同时使用链表维护元素次序,使得元素插入和输出次序一致
  • LinkedHashSet不允许插入重复元素

TreeSet

特点

  • 底层是TreeMap

  • 当使用无参构造器创建时,默认使用字典排序

    TreeSet treeSet = new TreeSet();
    treeSet.add("jack");
    treeSet.add("tom");
    treeSet.add("lucy");
    treeSet.add("marry");
    System.out.println(treeSet);
    
    输出:[jack, lucy, marry, tom]
    
  • 使用Comparator构建器,可改变排序规则

    TreeSet treeSet = new TreeSet(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            //按字典从小到大排,调换o1,o2顺序可改变排序次序
            return ((String)o2).compareTo((String)o1);
        }
    });
    treeSet.add("jack");
    treeSet.add("tom");
    treeSet.add("lucy");
    treeSet.add("marry");
    System.out.println(treeSet);
    
    输出:[tom, marry, lucy, jack]
    
  • 如果按照字符串长度排序,则不可加入相同长度字符串

    TreeSet treeSet = new TreeSet(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return ((String)o2).length()-((String)o1).length();
        }
    });
    treeSet.add("jack");
    treeSet.add("tom");
    treeSet.add("lucy");  //相同长度,不能加
    treeSet.add("marry");
    System.out.println(treeSet);
    
    输出:[marry, jack, tom]
    

Map

HashMap

HashMap特点(JDK8)

  • Map结构是键值对key-value

  • key和value可以是任何引用类型的数据(object),封装到node节点中

    Map<Object, Object> map = new HashMap<>();
    
  • 不能添加重复key(本质是hashcode),当重复添加相同key时,会替换key中value,所以key只能一次为null

  • 可以添加重复value值,因此可以添加多个value为null

  • 一对k-v是放在HashMap中Node节点中,而node实现了Entry接口

  • Entry是为了遍历方便,它提供getKey()和getValue()两个方法

  • 与HashSet一样,不保证映射顺序,因为底层是以hash表的方式存储,HashMap底层 (数组+链表+红黑树)

  • HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥操作

HashMap接口常用方法

  • put(object key , object value):添加键值对元素
  • remove(object key):移除键值为key的元素,并返回对象
  • get(object key):获取键值为key的对象
  • size():获取map大小
  • clear():清空map
  • containsKey(object key):判断键值为key的对象是否存在
  • containsValue(object value):判value值为value的对象是否存在

HashMap六大遍历方法

Map map = new HashMap();
map.put("A", "a");
map.put("B", "b");
map.put("C", "c");
map.put("D", "d");
  • 先取出所有key,将key保存在set里,再通过key取出对应的value

    Set keySet = map.keySet();
    //(1)增强for
    for (Object key : keySet) {
        System.out.println(map.get(key));
    }
    //(2)迭代器
    Iterator iterator = keySet.iterator();
    while(iterator.hasNext()){
        Object next = iterator.next();
        System.out.println(next);
    }
    
  • 把所有value取出来,放在Collection中

    Collection values = map.values();
    //(1)增强for
    for (Object value : values) {
        System.out.println(value);
    }
    //(2)迭代器
    Iterator iterator = values.iterator();
    while(iterator.hasNext()){
        Object next = iterator.next();
        System.out.println(next);
    }
    
  • 通过EntrySet获取k-y

    Set entrySet = map.entrySet();
    //(1)增强for
    for (Object entry : entrySet) {
        //将entry向下转成Map.Entry
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey());
        System.out.println(m.getValue());
    }
    
    //迭代器
    Iterator iterator = entrySet.iterator();
    while (iterator.hasNext()) {
        Object entry = iterator.next();
        //将entry向下转成Map.Entry
        Map.Entry m = (Map.Entry) entry;
        System.out.println(m.getKey());
        System.out.println(m.getValue());
    }
    

HashMap的扩容机制和HashSet完全一样

Hashtable

Hashtable特点

  • 键和值都不能为空

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }
    
  • 线程安全

  • 基本使用和HashMap相同

  • 底层结构:数组

Hashtable底层结构

  • 底层是数组,Hashtable内部类 Entry[],初始化大小为11
  • 临界值 threshold=11*0.75=8

Hashtable和HashMap对比

线程安全(同步)效率允许null键null值
Hashtable安全较低不允许
HashMap不安全允许

Properties

基本介绍

  • Properties 类继承Hashtable并实现Map接口,也是使用键值对保存数据,键值对不能为null
  • 使用与Hashtable类似
  • Properties 还可以用于 xxx.properties 文件中,加载数据到Properties 类对象,并进行读取与修改
  • xxx.properties 通常用于配置文件

TreeMap

  • 当使用无参构造器创建时,默认使用字典排序

    TreeMap treeMap = new TreeMap();
    treeMap.add("jack");
    treeMap.add("tom");
    treeMap.add("lucy");
    treeMap.add("marry");
    System.out.println(treeMap);
    
    输出:[jack, lucy, marry, tom]
    
  • 使用Comparator构建器,可改变排序规则

    TreeMap treeMap = new TreeMap(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            //按字典从小到大排,调换o1,o2顺序可改变排序次序
            return ((String)o2).compareTo((String)o1);
        }
    });
    treeMap.add("jack");
    treeMap.add("tom");
    treeMap.add("lucy");
    treeMap.add("marry");
    System.out.println(treeMap);
    
    输出:[tom, marry, lucy, jack]
    
  • 如果按照字符串长度排序,则不可加入相同长度字符串

    TreeMap treeMap = new TreeMap(new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return ((String)o2).length()-((String)o1).length();
        }
    });
    treeMap.add("jack");
    treeMap.add("tom");
    treeMap.add("lucy");  //相同长度,不能加
    treeMap.add("marry");
    System.out.println(treeMap);
    
    输出:[marry, jack, tom]
    

如何选择集合

Collections工具类

Collections是一个操作Set、List和Map等集合的工具类

Collections中提供了一系列静态方法对集合元素进行排序、查询和修改等操作

排序操作(均为static方法)

  • reverse(List list):元素反转

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("king");
    System.out.println(list);
    Collections.reverse(list); //元素反转
    System.out.println(list);
    
    输出:[tom, jack, king]
         [king, jack, tom]
    
  • sort(List list):默认按字典排序

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("king");
    System.out.println(list);
    Collections.sort(list);
    System.out.println(list);
    
    输出:[tom, jack, king]
         [jack, king, tom]
    
  • sort(List list,Comparator com):自定义排序

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("kinging");
    System.out.println(list);
    Collections.sort(list, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            //按长度由大到小排序
            return ((String) o2).length() - ((String) o1).length();
        }
    });
    System.out.println(list);
    
    输出:       [tom, jack, kinging]
         排序后:[kinging, jack, tom]
    
  • swap(List list,int i,int j):对指定两个位置元素进行交换

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("king");
    System.out.println(list);
    Collections.swap(list,0,2);
    System.out.println(list);
    
    输出:[tom, jack, king]
         [king, jack, tom]
    

查找,替换

  • max(Collection c):按照自然排序给出最大值

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("king");
    System.out.println(Collections.max(list));
    
    输出:tom
    
  • max(Collection c,Comparator com):自定义返回值

    List list = new ArrayList();
    list.add("tom");
    list.add("jack");
    list.add("kinging");
    Object max = Collections.max(list, new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            //自定义,返回长度最大的元素
            return ((String) o1).length() - ((String) o2).length();
        }
    });
    System.out.println(max);
    
    输出:kinging
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值