Java中的数组和集合

一、List和Map

image

1、特点

(1)传统的数组结构存储数据会在内存中开辟连续得空间,结合下标从而使得可以快速访问数据,但是删除和添加数据就很浪费资源


(2)链表不需要开辟连续空间,使用指针来指向数据,因此删除和添加操作比较快,但是查询数据需要遍历全部得元素

(3)而哈希表[散列表]结合两者得长处,合二为一。使得哈希表比较牛掰(初始容量,数组长度默认为16,分为单指针和双指针,双指针每个元素指向前面元素同时指向后面元素

(1)、List

1、可以允许重复的对象。
2、可以插入多个null元素。
3、是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序。
4、常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

(2)、Map

1、不是collection的子接口或者实现类。Map是一个接口。
2、Map的每个 Entry 都持有两个对象,也就是一个键一个值,Map 可能会持有相同的值对象但键对象必须是唯一的。
3、TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
4、Map 里你可以拥有随意个 null 值但最多只能有一个 null 键。
5、Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)。

(3)、区别

1、list是存储单列数据的集合,map是存储双列数据的集合;
2、list中存储的数据是有序的,map中存储的数据是无序的;
3、list允许重复,map的键不能重复,值可以重复。

(4)、Array和ArrayList

Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据。
List
List—是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式,它继承Collection。
1、ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型的数组。
2、如果增加的元素个数超过10个,那么ArrayList底层会生成一个新的数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组中,并且后续增加的内容都会放到新的数组当中,当新的数组无法容纳增加的元素时,重读该过程。
3、对于ArrayList元素的删除操作,需要将被删除元素的后续元素向前移动,代价比较大。
4、集合当中只能放置对象的引用,无法放置原生数据类型,我们必须使用原生数据的包装类才能加入到集合当中。
5、集合当中都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转化将其转换成真正的类型(放置进去的类型)。

二、List

1、Arraylist和Vector的区别

(1)、都实现了List接口(List接口继承了Collection接口)都是有序集合List集合规范制定(数据允许重复,可有通过索引取出数据)

(2)、ArrayList和Vector都是基于数组,因此大致代码相似。区别在于 Vector在Jdk1.0就有的古老集合, 因此包含大量方法名很长的方法。 Jdk1.2开始引入集合框架引入了List接口 Vector实现了List接口因此增加了一些List接口中的方法

(3)、总体来讲ArrayList可以完全取代Vector,除非有一些老古董强制要求Vector。Vectory是线程安全的因此性能较差。ArrayList是非线程安全所以接口性能较好。即使在多线程中也应该用ArrayList,因为可以通过Collections吧ArrayList和LInkedList转换成一个线程安全的集合
例子:List ls = Collections.synchronizedList(new ArrayList<>());

2、ArrrayList、Vecor、LinkedList的存储性能和特性

(1)、ArrayList Vector ==>> 数组方式存储数据
数组元素的数据 > 实际存储的数据以及增加和插入元素
按序号索引元素
插入元素 ==>>数组移动等内存操作
索引数据快、插入数据慢
(2)、Vector ==>> synchronized方法(线程安全)
性能上较ArrayList差
(3)、LinkedList ==>> 双向链表实现存储
序号索引数据需要向前或向后遍历
插入数据时只需要记录本项的前后项即可
所以插入速度较快
线程不安全

3、ArrrayList、Vecor、LinkedList容量以及适用场景

(1)、ArrayList

特点:
1、ArrayList 内部是通过动态数组实现的,它允许对元素进行快速随机访问;
2、当数组大小不满足时需要扩容,需要将已有数组移动到新的内存空间;
3、当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动代价比较高;
4、线程不安全。

初始容量:10初始容量:10
扩容容量:(原始容量 x 3 ) / 2 + 1。
适用场景:ArrayList 适合单线程,或多线程环境,但 List 只会被单个线程操作;随机查找和遍历,不适合插入和删除。

(2)、LinkedList

特点:
1、LinkedList 是基于双向链表存储数据的,很适合数据的动态插入和删除;
2、可根据索引值获取(get(int index)))或删除(remove(int index))节点(实现原理:通过计数索引值实现,当 index > 链表长度的1/2,从链表尾部开始遍历;反之,从链表头部开始遍历;
3、可作为队列和栈使用
4、线程不安全。
5、相对于ArrayList,LinkedList插入是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。
6、类似于插入数据,删除数据时,LinkedList也优于ArrayList。
7、LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。

适用场景:适合单线程中,顺序读取,不适合随机读取和随机删除。

(3)、Vecor

特点:
1、其特点大致与 ArrayList 一样。
2、线程安全(因为内部方法都是通过 synchronized 关键字同步的)。

初始容量:10初始容量:10
扩容容量:若扩容系数 > 0,则将容量的值增加“扩容系数”;否则,将容量大小增加一倍。
适用场景:能不用就别用。

(4)、ArrayList相比LinkedList

顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置 塞一个数据就好了
LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间 势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList
ArrayList的遍历效率会比LinkedList的遍历效率高一些

LinkedList做插入、删除的时候,慢在寻址,快在只需要改变前后Node的引用地址

ArrayList做插入、删除的时候,慢在数组元素的批量copy,快在寻址

如果确定插入、删除的元素是在前半段,那么就使用LinkedList

如果确定插入、删除的元素在比较靠后的位置,那么可以考虑使用ArrayList

如果不能确定插入、删除是在哪儿呢?建议使用LinkedList,

一来LinkedList整体插入、删除的执行效率比较稳定,没有ArrayList这种越往后越快的情况

二来插入元素的时候,弄得不好ArrayList就要进行一次扩容,而ArrayList底层数组扩容是一个既消 耗时间又消耗空间的操作

三、Map

Hash也称散列。基本原理就是把任意长度的输入,通过Hash算法变成固定长度得输出,这个映射得规则对应的就是Hash算法,而原始数据映射后得二进制串就是Hash值

entry----key----hash----index哈希值就是把各种类型的key算成统一的下标(.hashcode())

Hash特点
	1、从Hash值不可反向推导出原始数据
	2、输入数据的微小变化也会得到完全不同的hash值,相同的数据的道相同的hash值
	3、哈希算法执行高效,长文本也能快速计算出哈希
	4、Hash算法冲突概率较小

Hash表在jdk1.7中使用数组+链表	jdk1.8中加入了红黑树
由于Hash的原理是将 输入空间的值 映射成 hash空间内,而hash值空间远小于输入的空间。
根据抽屉原理,一定会存在不同的输入被映射成相同的输出。

抽屉原理:9个抽屉放10个苹果,怎么放都会有一个抽屉里有2个及以上的苹果

(5)HashMap的继承体系

1、HashMap与HashTable简介

类似于ArrayList和LinkedList的区别, HashTable是JDK1.0 就存在的老东西,因此 HashTable是一个继承Dictionary 的古老集合。JDk1.2引入了集合框架的Map接口,HashTable也实现了Map接口,因此HashTable也增加了一些Map的方法

2、HashMap与HashTable区别

(1)、HashMap允许使用null作为Key或者Value,HashTable不允许
(2)、HashMap是线程不安全的因此性能较好,而HashTable是线程安全的因此性能较差

3、场景

多线程环境下也是用HashMap,Collections可以把HashMap转换成线程安全的集合.
例:Map map = Collections.synchronizedList(new HashMap())

4、底层一些常量与属性和构造方法

常量:
	//缺省大小
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

	//数组的最大长度
	static final int MAXIMUM_CAPACITY = 1 << 30;

	//缺省负载因子大小
	static final float DEFAULT_LOAD_FACTOR = 0.75f;

	//树化阈值(链表长度超过8之后并且数组长度大于64就会升级成一个树了)
	static final int TREEIFY_THRESHOLD = 8;

	//树降级成为链表的阈值(删除树中元素,元素=6降级为链表)
	static final int UNTREEIFY_THRESHOLD = 6;

	//数组长度大于64(并且某个链表长度>8)升级为红黑树
	static final int MIN_TREEIFY_CAPACITY = 64;

属性:
	//Hash表,put时初始化
	transient Node<K,V>[] table;
	
	//
	transient Set<Map.Entry<K,V>> entrySet;

	//当前Hash表中元素个数
	transient int size;

	//当前Hash表结构修改次数(插入元素或删除元素,替换不会计数)
	transient int modCount;

	//扩容阈值(Hash表中元素超过阈值时触发扩容,超过阈值不扩容影响查找性能。链化严重)
	int threshold;

	//负载因子(计算threshold = capacity数组长度	*loadFactor负载因子)
	final float loadFactor;
	
构造方法:
	//
	public HashMap(int initialCapacity, float loadFactor) {
		//做了逻辑校验
		if (initialCapacity < 0)
		    throw new IllegalArgumentException("Illegal initial capacity: " +
						       initialCapacity);
		//传入数组长度超过最大长度就设置为最大长度		
		if (initialCapacity > MAXIMUM_CAPACITY)
		    initialCapacity = MAXIMUM_CAPACITY;
		//负载因子<=0。。。。||不是数
		if (loadFactor <= 0 || Float.isNaN(loadFactor))
		    throw new IllegalArgumentException("Illegal load factor: " +
						       loadFactor);
		//赋值负载因子
		this.loadFactor = loadFactor;
		//赋值扩容阈值	   位移计算只能是2的次方数导致table的长度只能是2的次方
					传过来init。。为7计算为8,9计算为16
		this.threshold = tableSizeFor(initialCapacity);
	}

	//设置默认负载因子
	public HashMap() {
		this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
	}

5、put源码分析

put方法时会默认(new entry(key,value,null=指针next))


public V put(K key, V value) {
	//table的length不是特变长的情况下,让让key的hash值高于16位也参与路由运算
	return putVal(hash(key), key, value, false, true);
}

//实际用put向散列表插入数据
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent散列表存在欲插入的key就不插了(有就替换)
     * @param evict
     * @return previous value, or null if none
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

	//tab:引用当前HashMap的散列表
	//p:表示当前散列表的元素
	//n:表示散列表数组的长度
	//i:表示路由寻址 结果
	
	Node<K,V>[] tab; Node<K,V> p; int n, i;
	
	//第一次插入数据时才会初始化,只是new出来并不会初始化(好多new出来但并不用)
        //延迟初始化逻辑
	if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

	//寻址找桶位,刚好为null,这时直接将当前key-value转成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 &&
                ((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);
                        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;
    }

总的来说向Vector和HashTable这种JDK1.0产物尽量不使用,除非某些api强制使用~~~果然老东西得退休~

image

posted @ 2021-06-10 11:20  迷途者寻影而行  阅读(1024)  评论(0编辑  收藏  举报