当前位置:网站首页>常见集合特性
常见集合特性
2022-07-17 12:41:00 【季风泯灭的季节】

集合分类
集合主要可分为三类
- 单列结合:List
- 双列集合:Map
- 迭代器:Iterator
Iterator
迭代器主要用来操作集合
boolean hasNext() // 是否存在下一个元素,遍历到最后一个元素返回false。
// 获取下一个元素,在最开始时,下一个元素指针cursor=0,上一个元素指针lastRet =-1;
// 每调用一次next方法,将lastRet改为=cursor,然后cursor+1。
public E next()
// 删除上一次遍历的元素,每调用一次,lastRet的值都改为-1,同时cursor-1;
// 所以每删除上一个元素,就得遍历下一个元素,不然会报错。即只能从头删到尾。
public void remove()常见List对象
ArrayList:
特点:
- 元素有放入顺序,元素可重复 。
- 底层采用数组来实现的。即使用连续的内存。
- 实现了Cloneable接口,重写了clone方法、方法内容默认调用父类的clone方法。
- 实现了Serializable接口,可以对象流化传输。
- 继承AbstractList且实现了List接口。其实这是作者一个错误的写法。
属性:
// 序列化版 本号(类文件签名),如果不写会默认生成,类内容的改变会影响签名变化,
// 导致反序列化失败
private static final long serialVersionUID = 8683452581122892189L;
// 如果实例化时未指定容量,则在 初次添加元素时会进行扩容使用此容量作为数组长度
private static final int DEFAULT_CAPACITY = 10;
// static修饰,所有的未指定容量的实例(也未添加元素)共享此数组,两个空的数组有什么区 别呢?
// 就是第一次添加元素时知道该 elementData 从空的构造函数还是有参构造函数被初始化 的。
// 以便确认如何扩容。空的构造器则初始化为10,有参构造器则按照扩容因子扩容
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// arrayList真正存放元素的地方,长度大于等于size
transient Object[] elementData;
//arrayList中的元素个数
private int size; 构造器:
// 无参构造器,构造一个容量大小为 10 的空的 list 集合,但构造函数只是给 elementData
// 赋值了一个空的数组,其实是在第一次添加元素时容量扩大至 10 的。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 当使用无参构造函数时是把 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementDat a。
// 当 initialCapacity 为零时则是把 EMPTY_ELEMENTDATA 赋值给 elementData。
// 当 initialCapacity 大于零时初始化一个大小为 initialCapacity 的 object数组并赋值给 ele mentData。
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
//将 Collection 转化为数组,数组长度赋值给 size。 如果 size 不为零,则判断 elem entData 的 class 类型是否为 ArrayList,不是的话则做一次转换。 如果 size 为零,则把 EMPTY_ELEMENTDATA 赋值给 elementData,相当于new ArrayList(0)。
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 指向空数组
elementData = EMPTY_ELEMENTDATA;
}
}添加元素
- 尾部添加,效率较高
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}- 插入,后面的元素均要移动,效率相对较差
public void add(int index, E element) {
rangeCheckForAdd(index);//下标越界检查
ensureCapacityInternal(size + 1); //同上判断扩容,记录操作数
// 依次复制插入位置及后面的数组元素,到后面一格,不是移动,因此复制完后,
// 添加的下标位置和下一个位置指向对同一个对象
System.arraycopy(elementData, index, elementData, index + 1, size ‐ index);
elementData[index] = element;//再将元素赋值给该下标
size++;
}扩容
先直接扩容1.5倍,如果扩容后的容量小于指定要扩容的容量,则以指定的扩容扩容。如果指定容量非常大,则以Integer的最大值或其减8来扩容。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;//获取当前数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//默认将扩容至原来容量的 1.5 倍
// 如果1.5倍太小的话,则将我们所需的容量大小 赋值给newCapacity
if (newCapacity ‐ minCapacity < 0)
newCapacity = minCapacity;
// 如果1.5倍太大或者我们需要的容量太大,那就直接
// 拿newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MA X_ARRAY_SIZE 来扩容
if (newCapacity ‐ MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 然后将原数组中的数据 复制到大小为 newCapacity 的新数组中,并将新数组赋值给 elementData。
elementData = Arrays.copyOf(elementData, newCapacity);
}删除:
删除指定位置元素后,后面的元素都会往前移动,然后把最后一个元素置空
public E remove(int index) {
// 删除是否越界
rangeCheck(index);
// 操作数加1
modCount++;
// 找出删除元素
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
// 将删除位置后面的元素赋值到前面一位的位置上
System.arraycopy(elementData, index+1, elementData, index,numMoved);
// 将最后一个元素置空
elementData[--size] = null; // clear to let GC do its work
// 返回删除元素值
return oldValue;
}清空ArrayList的三种方法
List aa= new ArrayList<>();
aa.add("111");
aa.add("112");
aa.add("113");
System.out.println(aa); // [111, 112, 113]
// 直接调内部方法清空
aa.clear();
System.out.println(aa); // []
// for循环清空,遍历list总数次,每次删除第一个元素
int b =aa.size();
for (int i = 0; i < b; i++) {
aa.remove(0);
}
System.out.println(aa); // []
// 迭代器清空,每次判断下一个元素是否存在,存在先遍历该元素,然后删除该元素
Iterator it =aa.iterator();
while (it.hasNext()){
it.next();
it.remove();
}其他重要API
| boolean remove(Object o) | 删除指值元素,可去除null |
| void fastmove(int index) | 快速删除,不做越界校验 |
| void clear() | 清空集合 |
| boolean addAll(Collection e) | 将另一个集合添加原集合末尾 |
LinkedList:
底层是双向循环链表实现,增删效率搞,查找效率低。
构造函数
LinkedList() // 用于创建一个空的链表。
LinkedList(Collection<? extends E> collection) // 创建一个LinkedList,保护Collection中的全部元素。常用方法
添加元素
boolean add(Object element)// 它将元素附加到列表的末尾。
void addFirst(E element) // 元素附加到列表的头部
void addLast(Eelement) // 元素附加到列表的尾部查询头尾元素
Object getFirst() // 它返回链表的第一个元素。
Object getLast() // 它返回链接列表的最后一个元素。查找指定元素索引
int indexOf(Object element) // 如果找到元素,它将返回元素第一次出现的索引。否则,它返回-1。
int lastIndexOf(Object element) // 如果找到元素,它将返回元素最后一次出现的索引。否则,它返回-1。删除元素
E remove(int location)
E removeFirst() // 删除并返回链接列表的头部一个元素
E removeLast() // 删除并返回链接列表的尾部一个元素其他API
void clear() // 它删除列表中的所有元素。
Object clone() // 它用于制作现有链接列表的副本。
Object set(int index,Object element) // 它用于用新元素替换列表中的现有元素。
boolean contains(Object element) // 如果元素存在于列表中,则返回true。
遍历方法
// LinkedList内部有迭代器类,可以使用迭代器遍历
Iterator<String> it = li.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// for循环遍历
for(int i=0;i<lList.size();i++){
System.out.println(lList.get(i));
}常见Map对象
HashMap(Map):
特点:
存储结构:
jdk1.8以前采用数组+链表的方式实现,jdk1.8(含1.8)以后采用数组+链表+红黑树的方式实现(当链表长度大于8并且hashmap里面的元素大于等于64就会将链表转为红黑树,若只是链表长度大于8元素总数小于64则只进行扩容操作,当删除元素链表长度小于6个时,又会将红黑树转为链表的形式)。
HashMap采用延时加载机制,当创建一个Hashmap对象,只是对一个成员变量赋值,并没有初始化table对象,在首次使用(即第一次调用put方法时)才对table数组对象初始化默认长度16.调用put方法时,第一步检查table数组是否存在,若不存在则调用resize()方法初始化table数组,第二步,用hash算法算出键值对在table中的位置(返回的hash值并不是key的hash值,而是key的哈希值与key的哈希值右位移16位后进行按位异或得到的结果(这是jdk1.8的hash算法,以前的jdk版本和在这也不一样),再以这个结果和table的长度-1的结果进行与运算得到table的节点下标)。如果这个位置没有元素在里面则新建一个节点对象保存(保存前会判断新的size是否大于数组容量,如果大于则调用resize()方法扩容),如果该位置有对象存在则调用对象的equals方法判断对象值是否相同,若相同则覆盖,并返回旧的值。若不相同,则判断当前存储位置是否已经树化了之后的节点,如果是则按树的方式存储键值对,如果不是,则按链表的方式存储键值对,同时判断链表的长度是否超过阈值(8),若超过并且数组长度已经大于64,则将链表树化后重新保存,若链表长度超过8数组长度不大于64,则调用resize()方法将table数组的长度扩容2倍后将键值对重排,键值对依旧按链表方式保存。若链表长度不超过8,则直接按链表方式保存对象。
各个版本之间hashmap的区别
- 数据结构不同:
jdk1.8之前采用数组+链表的数据结构,jdk1.8(含jdk1.8)以后采用数组+链表+红黑树的数据结构。
- 插入方式不一样:
jdk1.8之前采用头插入(因为后插入的数据被使用的概率较大,头插入避免在获取的时候对整个链表进行遍历,提高效率),jdk1.8(含jdk1.8)以后采用尾插入(避免头插入在高并发的场景下引起链表成环的问题,当链表成环再调用get方法获取元素时会陷入死循环)。
- 扩容方式不一样:
二者均是扩容两倍,但jdk1.8之前扩容时会创建一个新的Entry数组,然后重新计算hash值将原来的数组元素,拷贝到新的Entry数组中,而jdk1.8(含jdk1.8)以后重新拷贝数组元素时,key<oldTable.length,元素位置不变,如果key > oldTable.length, 元素位置为原来的索引+oldTable.length;即如果原来的数组长度为16,元素a,key=5,元素b,key=21,那么在原来的数组中应该在同一个链表中;扩容为32时,元素a仍在位置5,而元素b,应在5+16=21的位置上,节省了重新计算hash的时间,提高了效率.
为什么扩容是2的倍数
- 操作系统申请内存一般都是2的倍数,可以避免内存浪费。
- 计算器擅长移位操作,不善于加减乘除,以左位移1位的方式扩容效率更高。
- 减少哈希碰撞,hashMap中散列桶下标的获取方法是以数组长度-1运算后与hash值进行与运算(即用hash值对数组长度取模运算)。当数组长度-1等于偶数时,其二进制最低为0,0和1或0进行与运行都得0,当数组长度-1等于奇数时,则相反。
如何减少碰撞
- 使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生,提高效率。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。
- 使用优秀的hash算法,尽量避免不同的key得到相同哈希值。
- 尽量使用优秀的扰动函数对key的哈希值做处理,使table序列更均匀的分布
- 尽可能的减小扩容因子。
拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
hashtable、hashMap、ConccurentHashMap的区别
hashtable在public的方法里面都加入了synchronizedd 修饰,它是线程安全的,只能串联执行,效率较低;hashMap是线程不安全的,效率较高。要想将hasMap转为线程安全可用Collections. synchronizedMap(K,V)方法处理hashMap,将其转为线程安全的,它与hashTable的区别仅在于锁对象不同,Collections. synchronizedMap是用的一把内部对象锁,而hashtable的锁是调用对象(this)的锁,使用Collections. synchronizedMap处理hashMap使得hashMap是线程安全的,同时也只能串联执行,效率下降。ConccurentHashMap也是线程安全的,但是它使用的锁更加细化,早期的ConccurentHashMap采用的16把分段锁(hashMap的数组默认默认长度),在jdk1.8后做了更细的优化,每个桶配一把锁,使得只有在多线程且发生哈希碰撞的情况下才会产生并发竞争锁资源,这使效率得到更好的提升。
区别:
hashMap线程不安全,数组+链表+红黑树实现
hashtable线程安全,锁住整个对象,数组+链表实现
ConccurentHashMap线程安全,CAS+同步锁,锁住一个桶,内部实现逻辑与hashMap相同,也是数组+链表+红黑树实现
hashMap可以以null为键值对,其他两个不行。
ConccurentHashMap的put原理:
- 判断Node[]数组是否已经初始化,没有则初始化。
- 通过hash定位数组的索引下标,是否有节点,没有则通过CAS添加节点。
- 检查内部是否在扩容,是就帮助一块扩容。
- 如果头节点不为空,则锁住头节点,剩下的操作和hashMap一样。
边栏推荐
- String type function transfer problem
- 如何使用SVG制作沿任意路径排布的文字效果
- Autojs learning - multi function treasure chest - bottom
- How to solve the problem of cross domain access by Google browser
- Attachment handling of SAP Fiori
- HCIA rip experiment 7.11
- C# 搭建一个基于.NET5的WPF入门项目
- 各厂商的数据湖解决方案
- SAP S4 Material Management 库存模块 MARD 数据库表读取技术细节介绍
- 从预测到决策,九章云极DataCanvas推出YLearn因果学习开源项目
猜你喜欢
随机推荐
R语言使用epiDisplay包的ordinal.or.display函数获取有序logistic回归模型的汇总统计信息(变量对应的优势比及其置信区间、以及假设检验的p值)、使用summary汇总统计
华为机试:报文解压缩
R语言使用epiDisplay包的aggregate函数将数值变量基于因子变量拆分为不同的子集,计算每个子集的汇总统计信息、设置na.rm参数为FALSE之后包含缺失值的分组的统计量的结果为NA
Job: enter an odd number of 1-100
Attachment handling of SAP Fiori
R language uses LM function to build linear regression model, and uses subset function to specify the subset of data set to build regression model (uses subset function to filter the data subset that
C语言之构造类型细讲
机器学习模型的评估方法
SAP AppGyver 的 Universal Theme System 使用介绍
HCIA static comprehensive experiment report 7.10
AutoJs学习-多功能宝箱-中
Convert excel table to word table, and keep the formula in Excel table unchanged
Map遍历 key-value 的4种方法
二叉树的概念及三种遍历方法(C语言)
Kirin Xin'an operating system derivative solution | host security reinforcement software, to achieve one click rapid reinforcement!
Crud code practice of user management based on koa2 + MySQL
NJCTF 2017messager
查找——平衡二叉树
Koa2 connects to MySQL database to realize the operation of adding, deleting, changing and querying
The R language uses the plot function in the native package (basic import package, graphics) to visualize the scatter plot









