Java集合

Java基础

集合

分类

1.Collection

image-20230328211155724

2.Map

image-20230328211235081

Collection集合

常用方法

public class TestJava {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        Map map1 = new ConcurrentHashMap<>();
        Map map2 = new Hashtable();
        List list = new ArrayList<>();
//      add():往集合里添加元素
        list.add("zhangSan");
        list.add(true);
        list.add(1);
        list.add(1.1);
        List list1 = new ArrayList<>();
        list1.add("加1");
        list1.add("加2");
        list1.add("加3");
        list1.add("加1");
        System.out.println(list);
//        remove():删除集合里的元素
        list.remove("zhangSan");
        System.out.println(list);
//        集合是否包含元素
        System.out.println(list.contains("zhangSan"));
//        size():集合的元素个数
        System.out.println(list.size());
//        添加多个元素
        list.addAll(list1);
        System.out.println(list);
//        是否存在多个
        System.out.println(list.containsAll(list1));
//        删除多个元素
        list.removeAll(list1);
        System.out.println(list);
//        clear():清空集合
        list.clear();

    }
}

迭代器

//        迭代器:collection.iterator()
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

List集合

特点:

List集合中的元素有序可重复

常用方法
//        通过下标返回数据
        System.out.println("返回的数据:"+dogs.get(1));
//        更改指定下标的值
        dogs.set(1,new Dog("d",4));
        System.out.println("更改后的数据:"+dogs.get(1));
        System.out.println(dogs);
//        指定下标插入元素
        dogs.add(2,new Dog("e",5));
        System.out.println(dogs);
//        返回对象在集合中首次出现的位置
        System.out.println(dogs.indexOf(dog2));
//        返回对象在集合中最后一次出现的位置
        int i = dogs.lastIndexOf(dog2);
//      返回指定区间的集合元素

ArrayList

ArrayList可以存放多个空值,ArrayList底层基于数组,线程不安全。

当使用无参构造器初始化时,ArrayList的elementData的容量为0,第一次添加,扩容到10,再扩容时以1.5倍扩容。

当使用有参构造器扩容时,elementData容量以给定的容量为默认,扩容以1.5倍扩容。

elementData被transient修饰,表示瞬间的,不可被序列化。

无参构造扩容

先进行初始化,默认值为0

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

调用add方法,先判断容量是否充足

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当ArrayList集合需要的容量大于当前集合长度时,调用copyOf()进行扩容

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有参构造扩容

第一次初始化时,创建指定大小的Object[capacity],第一次扩容时,直接扩容1.5倍。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Vector

特点:

Vector的底层也是通过数组实现的,默认大小也是10。主要特点:查询快,增删慢 , 涉及写的方法中都加入了,synchronized同步锁,保证线程安全,但是效率低

原理:创建对象与ArrayList类似,但有一点不同,它可以设置扩容是容量增长大小。

List vector = new Vector(10,20);

LinkedList

特点:LinkedList底层是维护双向链表,没有指定size的逻辑,只有pre节点和next节点,增删快,查询慢,线程不安全。

add():
void linkLast(E e) {
    //取出双线链表的尾节点
    final Node<E> l = last;
    //将新添加的对象装进新的节点中,并将上个节点属性指向L
    final Node<E> newNode = new Node<>(l, e, null);
    //再将LinkedList的尾节点指向新的节点
    last = newNode;
    //如果last为空,说明LinkedList为空,则将头节点也指向新节点
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
        //LinkedList的长度加1
    size++;
    modCount++;
}
modCount字段:

在单线程时可能会出现冗余,但在多线程时,当迭代器进行迭代时,当对数据进行修改时,迭代器抛ConcurrentModificationException()

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Set集合

特点:

元素唯一,不保存重复元素,

加入顺序不一致,

无索引,继承Conllection,可用迭代器遍历,

加入Set的元素必须定义equals()方法以确保对象的唯一性。

HashSet

特点:

元素唯一,无序无索引,底层是哈希表(HashMap),允许有元素为null。

注意:

如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。

创建HashSet(),默认加载因子为0.75,可以指定

临界阈值(扩容因子):只要哈希表中的所欲节点个数 >= 临界阈值,就会进行树化判断并扩容

源码:

​ 源码介绍: 哈希表的创建,添加元素

​ 当调用无参构造函数创建哈希表时,会将默认加载因子赋值给HashMap()加载阈值

​ 当HashSet() 的 add()方法被调用时,底层会调用HashMap的**put(key,value)**方法,并将new Object()作为key放入k位置。

​ 再将需要添加的元素进行hash,hash是调用元素的hashCode() ^ hashCode >>> 16,

​ 在**putVal()**方法里进行操作

​ 首先对表判断,是否为空 || 长度是否大于零,为空则调用resize()进行扩容。

​ 扩容判断结束后,再判断当前元素哈希结果位置索引是否为空,为空则直接插入。

​ 当索引位置不为空时,**与索引位置元素进行哈希值和equals()**判断,为同一元素则返回当前元素,在最外层add()方法进行判断。

​ 判断结果为不同元素,再判断是否为树类型,为树类型时,则调用树算法进行插入。

​ 不为树类型时,对节点进行死循环遍历,当p.next == null 时,进行插入。

​ 如果节点的元素个数 >= 7 时,进行树化判断,

​ 树化判断若哈希表长度小于64,则调用resize()进行 <<1 扩容。

//底层new HashMap() 哈希表
public HashSet() {
    map = new HashMap<>();
}
	//进入HashMap()方法
		//默认大小为1 << 4
            public HashMap() {
                this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
            }
		//指定参数时
            public HashMap(int initialCapacity) {
                this(initialCapacity, DEFAULT_LOAD_FACTOR);
            }

​ 添加元素 add()

//1.底层调用HashMap()的put()方法,为了保证put(key,value)方法参数一致,PRESENT参数为一个static final new Object()对象
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
		//进入put方法里,其中hash(key)方法源码是对key进行判断,若(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
		    public V put(K key, V value) {
       			 return putVal(hash(key), key, value, false, true);
    		}
					
					//进入putVal()方法
                     final V putVal(/*hash(key)*/int hash,/*key值*/ K key,/*PERSENT*/ V value,
                                    boolean onlyIfAbsent,boolean evict) {
                            Node<K,V>[] tab; Node<K,V> p; int n, i;
                         	//判断当前HashSet是否为空,或者长度为零,是则调用resize()进行初始化
                            if ((tab = table) == null || (n = tab.length) == 0)
                                n = (tab = resize()).length;
 						  //判断当前哈希表的哈希值索引为空,则直接插入                        
                         	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 &&
                                    ((k = p.key) == key || (key != null && key.equals(k))))
                                    e = p;
                                //如果不相同,判断当前索引位置的数据结构类型是否为树节点
                                else if (p instanceof TreeNode)
                                    //是则通过红黑树的算法插入元素
                                    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);
                                            //当节点的长度 >=7 时,则进行树化判断
                                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                                //treeifyBin(tab, hash)树化判断
                                                	//当表长度大于64时,将此节点树化
                                                	//当表长度小于64时,调用resize()对哈希表进行进行 <<1 扩容
                                                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;
                        }


LinkedHashSet

特点:元素唯一,有序无索引,底层是双向链表哈希表(LinkedHashMap),允许有元素为null,继承HashSet,实现Set接口

HashMap

特点:

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。

HashMap底层扩容机制

​ HashMap当add(key,value)中key值相同时,会在putVal(hash(key),k,v,boolean)方法中替换,HashMap是可以指定初始容量的,不指定的话默认初始容量是16,如果传入一个值用来指定初始容量,并不是直接使用这个值来定义map大小,而是用大于等于这个值的最小2的幂次方来作为初始容量,之所以要采用2的幂次方来作为HashMap底层数组的长度,是因为在对数据的key值进行哈希函数运算得到的哈希值范围很大,无法直接作为数据存放位置的数组下标,因此需要对得到的哈希值作进一步处理,一般来说是对数组长度进行取模运算,在数组长度为2的幂次方的时候,用哈希值与数组长度-1的值做逻辑与运算就相当于对数组长度取余,逻辑与运算因为运算符优先级的关系会比直接的取余运算效率更高,这就是HashMap底层数组长度一般为2的幂次方的原因。

 /**
     *
     * @param hash  由key计算出来的 hash值
     * @param key   要存储的key
     * @param value  要存储的value
     * @param onlyIfAbsent  如果当前位置已存在一个值,是否替换,false是替换,true是不替换
     * @param evict  表是否在创建模式,如果为false,则表是在创建模式。
     */

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

遍历:

三类方式:

        Map map = new HashMap<>(); map.put(1,"a");  map.put(2,"b"); map.put(3,"c"); map.put(4,"d"); map.put(5,"e");
//      遍历HashMap()
        //  Set<K> keySet();
        Iterator iterator = map.keySet().iterator();
        while(iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println(next +"="+map.get(next));
        }
        //  Collection<V> values();
        Collection values = map.values();
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object next = iterator1.next();
            System.out.println(next);
        }
        // public Set<Map.Entry<K,V>> entrySet()
        Set set = map.entrySet();
        Iterator iterator2 = set.iterator();
        while (iterator2.hasNext()) {
            System.out.println(iterator2.next());
        }
树化扩容

​ **条件:**节点个数 >= 8 ,并且散列表长度为64

​ 当散列表的节点数 >= 8 时,如果散列表长度小于64,会对散列表进行扩容,扩容以 << 1 倍扩容。

​ 当散列表的节点数 >= 8 时,如果散列表长度不小于64,则对此节点进行树化。

Hashtable

​ **特点:**和HashMap基本一致,在所有方法上都加了锁,效率低下,键和值都不允许有空值。

​ HashMap & Hashtable

HashMapHashtable
线程安全否,效率高是,效率低
底层哈希与运算,(table.length - 1) & hash(key)位运算(hash & 0x7FFFFFFF) % tab.length
是否为空可以有一个null不能为空
底层结构数组+链表+红黑树数组+链表
扩容机制初始值为16,(扩容为 table.lenth*2)初始值为11,(扩容为 table.length*2) +1
初始容量为给定值的最小二次幂任意值

Properties

集合工具类

一、排序

1、反转
void reverse(List list)
1
2、随机排序
void shuffle(List list)
1
3、按自然排序的升序排序
void sort(List list)
1
4、定制排序,由Comparator控制排序逻辑
void sort(List list, Comparator c)
1
5、交换两个索引位置的元素
void swap(List list, int i , int j)
1
6、旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
void rotate(List list, int distance)
1

二、查找、替换操作

1、对List进行二分查找,返回索引,注意List必须是有序的
int binarySearch(List list, Object key)
1
2、根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll)
1
3、根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
int max(Collection coll, Comparator c)
1
4、用指定的元素代替指定list中的所有元素
void fill(List list, Object obj)
1
5、统计元素出现次数
int frequency(Collection c, Object o)
1
6、统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
int indexOfSubList(List list, List target)
1
7、用新元素替换旧元素
boolean replaceAll(List list, Object oldVal, Object newVal)
1

三、同步控制

Collections 提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。

我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections 提供了多个静态方法可以把他们包装成线程同步的集合。

最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。

方法如下:

1、返回指定 collection 支持的同步(线程安全的)collection。
synchronizedCollection(Collection c)
1
2、返回指定列表支持的同步(线程安全的)List。
synchronizedList(List list)
1
3、返回由指定映射支持的同步(线程安全的)Map。
synchronizedMap(Map<K,V> m)
1
4、返回指定 set 支持的同步(线程安全的)set。
synchronizedSet(Set s)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值