Java集合

Java中的集合类有哪些? 怎么分类的?

Java的整个集合框架中,主要分为List, Set Queue Stack, Map等五种数据结构。其中,前四种数据结构都是单一元素的集合,而最后的Map则是以KV对的形式使用。

  • 从继承关系上讲,List,Set,Queue都是Collection的子接口,Collection又继承了Iterable接口,说明这几种集合都是可以遍历的
  • 从功能上讲,List代表一个容器,可以是先进先出,也可以是先进后出。而Set相对于List来说,是无序的,同时也是一个去重的列表,既然会去重,就一定会通过equals,compareTo,hashcode等方法进行比较。Map则是KV的映射,也会涉及到Key值的查询等能力。
  • 从实现上讲,List可以有链表实现或者数组实现,两者各有优劣,链表增删快,数组查询快。Queue则可以分为优先队列,双端队列等等。Map则可以分为普通的HashMap和可以排序的TreeMap等等。

Collection和Collections有什么区别?

1.Collection是一个集合接口: 它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java类库中有很多具体的实现。 是list,set等的父接口。

2.Collections是一个包装类: 它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架

日常开发中,不仅要了解Java中的Collection及其子类的用法,还要了解Collections用法。可以提升很多处理集合类的效率。

Set是如何保证元素不重复的?

在Java的Set体系中,根据实现方式不同主要分为两大类。 HashSetTreeSet.

1.Treeset是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值;底层基于TreeMap

2.Hashset是哈希表实现的,HashSet中的数据是无序的,可以放入null 但只能放入一个null, 两者中的值都不能重复,就如数据库中唯一约束;底层基于HashMap

在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向Hashset中添加元素的时候,首先计算元素的hashcode值,然后通过抚动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加

TreeSet的底层是TreeMap的keySet,而TreeMap是基于红黑树实现的,红黑树是一种平衡二查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。

TreeMap是按key排序的,元素在插入TreeSet时compareTo0方法要被调用,所以TreeSet中的元素要实现Comparable接口。 TreeSet作为一种Set, 它不允许出现重复元素。TreeSet是用compareTo来判断重复元素的。

HashSet TreeSet LinkedhashSet BitSet有何区别?

1.功能不同: Hashset是功能最简单的Set,只提供去重的能力;LinkedHashSet不仅提供去重功能,而且还能记录插入和查询顺序;TreeSet提供了去重和排序的能力;Bitset不仅能提供去重能力,同时也能减少存储空的浪费,不过对于普通的对象不太友好,需要做额外处理

2实现方式不同:HashSet基于HashMap,去重是根据HashCode和equals方法的;LinkedHashSet是基于

LinkedHashMap,通过双向链表记录插入顺序;TreeSet是基于TreeMap的,去重是根据compareTo方法

的; Bitset基于位数组,一般只用于数字的存储和去重

3.其实Bitset只是叫做Set而已, 它既没有实现Collection接口,也和lterable接口没有什么关系,但是是名字相同而已

什么是BitSet? 有什么作用?

顾名思义,Bitset是位集合,通常来说,位集合的底层的数据结构是一个bit数组,如果第n位为1,则表明数字r该数组中。

举个例子,如果调用Bitset#set(10),业务语意是把10放到Bitset中,内部的操作则是通过把二进制的第十位(位)置为1。这样,就代表BitSet中包含了10这个数字。

不过,对于Java中的BitSet来讲, 因为Java不知道bit类型,所以它的底层结构并不是一个bit类型数组,但是也是一个byte类型数组,而是一个long类型的数组,这样设置的目的是因为long有64位,,每次可以读取64位,在行set或者or操作的时候,for循环的次数会更少,提高了性能

它最大的好处就是对于多个数字来说,降低了存储空间,如正常情况下,将每一个int类型(32bit)的数字存储内存中需要4B*(2个31-1)=8GB,但是如果用Bitset的话,就会节省到原来的1/32

一个整型占4个字节,一共有231-1个

Bitset常见的使用例子往往和大数相关

1.现在有1千万个随机数, 随机数的范围在1到1亿之间。求出将1到1亿之间没有在随机数中的数

2统计N亿个数据中没有出现的数据

3,将N亿个不同数据进行排序等

但是Bitset也有缺点,如集合中存储一些差值比较大的数,如1亿和1两个数,就会导致内存的严重浪费

ArrayList、 LinkedList与Vector的区别?

List主要有ArrayList、LinkedList与Vector几种实现。这三者都实现了List接口, 使用方式也很相似,主要区别在子

因为实现方式的不同,所以对不同的操作具有不同的效率

ArrayList是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问因为ArrayList本质上就是一个数组

LinkedList是一个双向链表,在添加和删除元素时具有比ArrayList更好的性能,但在get与set方面弱于

ArrayList。当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比如果数据和运算量很小那么对比将失

去意义。

Vector和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象)那么使用ArrayList是更好的选择

Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每

次对size增长50%.

而LinkedList还实现了Queue和Deque接口该接口比List提供了更多的方法,包括offeropeek0poll0等

注意:默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践

这样可以减少调整大小的开销

ArrayList是如何扩容的?

首先,我们要明白ArrayList是基于数组的,我们都知道,申请数组的时候,只能申请一个定长的数组,那么List是如何通过数组扩容的呢?ArrayList的扩容分为以下几步:

1.检查新增元素后是否会超过数组的容量,如果超过, 则进行下一步扩容

2.设置新的容量为老容量的1.5倍,最多不超过2个31-1 ava8中ArrayList的容量最大是

nteger.MAX_VALUE-8,即231-9。这是由于在Java8中,ArrayList内部实现进行了一些改进,使用了些数组复制的技巧来提高性能和内存利用率,而这些技巧需要额外的8个元素的空间来进行优化。)

3之后,申请一个容量为1.5倍的数组,并将老数组的元素复制到新数组中,扩容完成

ArrayList的序列化是怎么实现的?

在序列化过程中,如果被序列化的类中定义了writeObject和readobject方法,虚拟机会试图调用对象类里的writeobject和readobject方法,进行用户自定义的序列化和反序列化。

如果没有这样的方法, 则默认调用是ObjectOutputStream的defaultWriteObject方法以及

ObjectlnputStream的defaultReadobject方法

用户自定义的writeObject和readobject方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值

ArrayList底层是通过Object数组完成数据存储的,但是这个数组被声明成了transient,说明在默认的序列化策略中并没有序列化数组字段

为什么底层数组要使用transient

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化

ArrayList把元素数组设置为transient

所以,为了避免Java自带的序列化机制造成的空间浪费,把数组定义为transient,然后重写writeObject和

readobject来实现序列化操作。

HashMap、 Hashtable 、ConcurrentHashMap的区别?

线程安全:

HashMap是非线程安全的

Hashtable中的方法是同步的,所以它是线程安全的。

ConcurrentHashMap在JDK1.8之前使用分段锁保证线程安全,ConcurrentHashMap默认情况下将hash表分为

16个桶(分片),在加锁的时候,针对每个单独的分片进行加锁,其他分片不受影响。锁的粒度更细,所以他的

性能更好。

ConcurrentHashMap在JDK1.8中,采用了一种新的方式来实现线程安全,即使用了CAS+synchronized,这个

实现被称为分段锁的变种,也被称为锁分离",它将锁定粒度更细,把锁的粒度从整个Map降低到了单个桶

继承关系:

Hashtable是基于陈的Dictionary类继承来的。

HashMap继承的抽象类AbstractMap实现了Map接口

ConcurrentHashMap同样继承了抽象类AbstractMap,并且实现了ConcurrentMap口接口

允不允许null值

HashTable中 key和value都不允许出现null值,否则会抛出NulIPointerException异常。HashMap中, null可以作为键或者值都可以

ConcurrentHashMap中,key和value都不允许为null。

默认初始容量和扩容机制

HashMap的默认初始容量为16,默认的加载因子为0.75,即当HashMap中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍,并将原来的元素重新分配到新的桶中

Hashtable,默认初始容量为11,默认的加载因子为0.75,即当Hashtable中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍加1,并将原来的元素重新分配到新的桶中

ConcurrentHashMap,默认初始容量为16,默认的加载因子为0.75,即当ConcurrentHashMap中元素个数超过容量的75%时,会进行扩容操作。扩容时,容量会扩大为原来的两倍,并会采用分段锁机制,将

ConcurrentHashMap分为多个段(segment),每个段独立进行扩容操作,避免了整个ConcurrentHashMap的锁竞争。
遍历方式的内部实现上不同:

HashMap使用EntrySet进行遍历,即先获取到HashMap中所有的键值对(Entry),然后遍历Entry集合。支持fail-fast,也就是说在遍历过程中,若HashMap的结构被修改(添加或删除元素),则会抛出

ConcurrentModificationException 如果只需要遍历HashMap中的key或value,可以使用KeySet或Values来遍历。

Hashtable使用Enumeration进行遍历,即获取Hashtable中所有的key,然后遍历key集合。这个过程也会判断是否存在并发修改

ConcurrentHashMap使用分段锁机制, 因此在遍历时需要注意,遍历时ConcurrentHashMap的某个段被修改不会影响其他段的遍历。可以使用EntrySet、 KeySet或Values来遍历ConcurrentHashMap,其中Entryset遍历时

效率最高。遍历过程中,ConcurrentHashMap的结构发生变化时,不会抛出ConcurrentModificationException异常,但是在遍历时可能会出现数据不一致的情况,因为遍历器仅提供了弱一致性保障

相关推荐
小白学大数据8 分钟前
Python爬虫与数据可视化:构建完整的数据采集与分析流程
开发语言·爬虫·python·信息可视化
牵牛老人11 分钟前
C++中的数据结构
开发语言·数据结构·c++
明戈戈13 分钟前
设计模式-观察者模式
java·观察者模式·设计模式
末央&14 分钟前
【C语言】文件的顺序读写
c语言·开发语言
阑梦清川20 分钟前
数学建模--Matlab求解线性规划问题&&两种类型&&实际应用
开发语言·数学建模·matlab
喝哈喝哈21 分钟前
MatLab 二维图像绘制基础
开发语言·matlab
唤醒手腕24 分钟前
2024 年最新 Python 基于火山引擎豆包大模型搭建 QQ 机器人详细教程(更新中)
开发语言·python·火山引擎
concisedistinct26 分钟前
探索iOS开发语言基础与Xcode工具:从零开始构建你的第一个iOS应用
开发语言·ios·objective-c·xcode·swift
苟且.27 分钟前
ThreadLocal、InheritableThreadLocal 和 TransmittableThreadLocal
java·多线程
南棱笑笑生27 分钟前
20240626确认飞凌开发板OK3588-C使用的I2C扩展GPIO芯片io扩展芯片TCA6424ARGJR的GPIO号的对应关系!
c语言·开发语言·单片机