ArrayList:
基于动态数组实现,支持动态增长,适用于查找和遍历操作频繁的场景。
LinkedList:
基于链表实现,支持高效的插入和删除操作,适用于频繁插入和删除元素的场景。
HashMap:
基于哈希表实现,提供快速的插入、删除和查找操作,适用于键值对存储的场景。
HashSet:
基于 HashMap 实现,是一组不重复元素的集合,适用于去重或者快速查找元素的场景。
TreeMap:
基于红黑树实现的有序 Map 结构,支持按照键的顺序进行排序存储。
TreeSet:
基于 TreeMap 实现的有序 Set 集合,元素按照自然顺序排序。
LinkedHashMap:
基于链表和哈希表实现的 Map 结构,保持元素的插入顺序。
LinkedHashSet:
基于 LinkedHashMap 实现的有序 Set 集合,保持元素的插入顺序。
这些集合类提供了丰富的方法和数据结构,用于解决各种不同的问题和场景。选择合适的集合类取决于你的需求,例如是否需要有序存储、是否允许重复元素、对元素的查找、插入、删除等操作的频率等。
在 Java 中,常见的集合框架包括 List、Set、Map 等。
List:有序集合,可以存储重复元素。
ArrayList:基于动态数组实现,支持随机访问,扩容机制为当前容量的 50% 增加。
LinkedList:基于链表实现,适合插入和删除操作频繁的场景。
Set:不允许重复元素的集合。
HashSet:基于哈希表实现,使用 HashMap 存储数据,扩容机制和 HashMap 相同。
TreeSet:基于红黑树实现,元素有序且唯一。
Map:键值对的映射集合。
HashMap:基于哈希表实现,扩容因子默认为 0.75,扩容机制为当前容量的两倍。
TreeMap:基于红黑树实现的有序 Map,按照键的自然顺序进行排序。
扩容机制是为了应对当集合大小达到一定阈值时,容量不足以存储更多元素时的处理机制。扩容因子则是触发扩容的阈值。当集合中的元素数量达到容量乘以扩容因子时,集合会进行扩容操作,以保证性能和内存的合理利用。
例如,HashMap 默认的扩容因子为 0.75,即当 HashMap 中元素数量达到当前容量的 75% 时,会触发扩容操作,将当前容量翻倍以增大存储空间。这样可以在性能和空间之间取得一个平衡,避免频繁的扩容和浪费内存。
ArrayList
和 LinkedList
是 Java 中常见的两种集合实现,它们的主要区别在于底层数据结构和操作特点:
底层数据结构:
ArrayList
基于动态数组实现,内部使用数组来存储元素。它支持随机访问(根据索引快速访问元素),但在插入和删除元素时可能需要移动其他元素,影响性能。
LinkedList
基于双向链表实现,每个节点都存储了对前后节点的引用。它对于插入和删除操作更加高效,因为只需改变节点的指针,但随机访问效率较低,需要从头或尾开始遍历。
访问效率:
ArrayList
支持快速的随机访问,时间复杂度为 O(1),因为可以通过索引直接访问元素。
LinkedList
随机访问效率较差,需要从头或尾开始遍历到目标位置,时间复杂度为 O(n)。
插入和删除操作:
ArrayList
在中间插入或删除元素时需要移动元素,涉及到数组的拷贝和移动,效率较低,时间复杂度为 O(n)。
LinkedList
在链表中间插入或删除元素效率较高,只需改变节点的指针,时间复杂度为 O(1)。
空间占用:
ArrayList
在添加元素时可能需要进行扩容,会预留额外的空间,可能会浪费一些空间。
LinkedList
每个节点需要额外存储指针,可能会占用更多的空间。
综合来说,如果需要频繁地进行插入和删除操作,而不太需要随机访问元素,LinkedList
可能更适合;而如果需要频繁随机访问元素,ArrayList
可能更适合。
使用 Stream 流可以非常方便地进行集合元素的去重和筛选操作。假设有一个 List 存储了很多元素,需要对其中的元素进行去重并筛选出重复的元素,可以按照以下步骤进行操作:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
// 假设有一个存储了很多元素的 List
List elements = Arrays.asList("A", "B", "C", "A", "B", "D");
// 使用 Stream 流进行去重和筛选
List distinctDuplicates = elements.stream()
.collect(Collectors.groupingBy(e -> e)) // 按元素分组
.entrySet().stream()
.filter(entry -> entry.getValue().size() > 1) // 筛选出重复元素
.map(entry -> entry.getKey()) // 获取重复的元素
.collect(Collectors.toList()); // 收集结果到 List
// 输出结果
System.out.println("重复的元素:" + distinctDuplicates);
}
}
这段代码通过 Stream 流的 groupingBy
方法按元素进行分组,然后筛选出出现次数大于 1 的元素,最终获取重复的元素并存储到一个新的 List 中。
Stream 流是 Java 8 引入的一种数据处理方式,它提供了一种更高效、更简洁的方式来操作集合元素。Stream 流的常见作用包括:
集合数据处理:对集合元素进行过滤、映射、排序、去重、分组等操作,例如使用 filter
、map
、sorted
、distinct
、collect
等方法。
数据筛选与转换:可以轻松地从集合中筛选出满足特定条件的元素,并将元素进行转换、提取等操作。
聚合操作:执行聚合操作,例如求和、求平均值、统计元素个数等,使用 reduce
、count
、sum
、average
等方法。
并行处理:Stream 流支持并行处理,能够充分利用多核处理器提高处理效率,在某些情况下能加速数据处理。
延迟执行:Stream 流的操作都是延迟执行的,只有遇到终止操作时才会触发中间操作的执行,这种延迟特性可以优化性能。
函数式编程:Stream 流支持函数式编程风格,可以使用 Lambda 表达式来简化代码、实现更优雅的数据处理。
处理无限流:Stream 流能够处理无限流,例如 generate
、iterate
等方法可以生成无限的元素流,并在需要时进行限制或截断。
这些作用使得 Stream 流成为处理集合数据更加便捷、高效的方式,大大简化了数据处理的过程,并且提供了更多的灵活性和可读性。
List、Set 和 Map 是 Java 集合框架中常见的数据结构,它们之间有着一些明显的区别和适用场景:
特点:有序集合,可以存储重复元素,允许通过索引访问元素。
实现类:常见的实现类有 ArrayList 和 LinkedList。
优点:支持随机访问,可以根据索引快速访问元素;允许存储重复元素。
缺点:在中间插入或删除元素时,可能需要移动其他元素,影响性能。
特点:不允许重复元素的集合,无序。
实现类:常见的实现类有 HashSet 和 TreeSet。
优点:可以快速判断元素是否存在,不允许存储重复元素。
缺点:无序,不能通过索引访问元素。
特点:键值对的映射集合,存储键值对,键不能重复,值可以重复。
实现类:常见的实现类有 HashMap 和 TreeMap。
优点:可以根据键快速查找值;键不能重复,值可以重复。
缺点:不允许键重复,值可以重复;无序。
重复元素:List 允许存储重复元素,Set 不允许存储重复元素。
是否有序:List 是有序集合,Set 是无序集合。
索引访问:List 可以通过索引快速访问元素,而 Set 不支持索引访问。
键值对映射:Map 是键值对的映射集合,存储键值对的关联关系。
如果需要存储有序的元素并允许重复,使用 List。
如果需要存储不重复的元素,且不关心顺序,使用 Set。
如果需要存储键值对映射关系,使用 Map。
在选择集合类型时,根据需求的特点和操作方式,选择最适合的集合类型能够更高效地处理数据。
哈希算法:用于将任意长度的二进制值串映射为固定长度的二进制值串,映射之后得到的二进制值就是哈希值(散列值)。
哈希函数:也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值、杂凑值或者消息摘要(Message Digest)。它是一种单向密码体质,即一个从明文到密文的不可逆映射,只有加密过程,没有解密过程。
哈希算法特性:
从哈希值不能反向推导出原始数据(所以哈希算法也叫单向算法,不可逆);
对输入数据非常敏感,哪怕原始数据只修改了一个比特,最后得到的哈希值也大不相同;
散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率非常小;
哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值
①hash分类:
加法Hash;
位运算Hash;
乘法Hash;
除法Hash;
查表Hash;
混合Hash
②在实际应用中,哈希函数(hash function)被广泛应用于各种领域和用途。下面是一些常见的哈希应用:
1、场景一:安全加密
日常用户密码加密通常使用的都是 md5、sha等哈希函数,因为不可逆,而且微小的区别加密之后的结果差距很大,所以安全性更好。
2、场景二:唯一标识
比如 URL 字段或者图片字段要求不能重复,这个时候就可以通过对相应字段值做 md5 处理,将数据统一为 32 位长度从数据库索引构建和查询角度效果更好,此外,还可以对文件之类的二进制数据做 md5 处理,作为唯一标识,这样判定重复文件的时候更快捷。
3、场景三:数据校验
比如从网上下载的很多文件(尤其是P2P站点资源),都会包含一个 MD5 值,用于校验下载数据的完整性,避免数据在中途被劫持篡改。
4、场景五:散列函数
前面已经提到,PHP 中的 md5、sha1、hash 等函数都是基于哈希算法计算散列值
5、场景五:负载均衡
对于同一个客户端上的请求,尤其是已登录用户的请求,需要将其会话请求都路由到同一台机器,以保证数据的一致性,这可以借助哈希算法来实现,通过用户 ID 尾号对总机器数取模(取多少位可以根据机器数定),将结果值作为机器编号。
6、场景六:分布式缓存
分布式缓存和其他机器或数据库的分布式不一样,因为每台机器存放的缓存数据不一致,每当缓存机器扩容时,需要对缓存存放机器进行重新索引(或者部分重新索引),这里应用到的也是哈希算法的思想。
③几种Hash算法的扩展应用
1、SimHash
simHash是google用于海量文本去重的一种方法,它是一种局部敏感hash。那什么叫局部敏感呢,假定两个字符串具有一定的相似性,在hash之后,仍然能保持这种相似性,就称之为局部敏感hash。普通的hash是不具有这种属性的。simhash被Google用来在海量文本中去重。
2、GeoHash
GeoHash将地球作为为一个二维平面进行递归分解。每个分解后的子块在一定经纬度范围内拥有相同的编码。以下图为例,这个矩形区域内所有的点(经纬度坐标)都共享相同的GeoHash字符串,这样既可以保护隐私(只表示大概区域位置而不是具体的点),又比较容易做缓存。
3、布隆过滤器
布隆过滤器被广泛用于黑名单过滤、垃圾邮件过滤、爬虫判重系统以及缓存穿透问题。对于数量小,内存足够大的情况,我们可以直接用hashMap或者hashSet就可以满足这个活动需求了。但是如果数据量非常大,比如5TB的硬盘上放满了用户的参与数据,需要一个算法对这些数据进行去重,取得活动的去重参与用户数。这种时候,布隆过滤器就是一种比较好的解决方案了。
布隆过滤器其实是基于bitmap的一种应用,在1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数,用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难,主要用于大数据去重、垃圾邮件过滤和爬虫url记录中。核心思路是使用一个bit来存储多个元素,通过这样的方式来减少内存的消耗。通过多个hash函数,将每个数据都算出多个值,存放在bitmap中对应的位置上。
HashMap 和 Hashtable 都是 Java 中用于存储键值对的集合类,它们之间有一些区别:
同步性:
Hashtable
是线程安全的,所有的方法都是同步的(synchronized),即在多线程环境下使用时,不需要额外的同步措施。
HashMap
是非线程安全的,不保证线程安全性。在多线程环境中使用时,需要手动进行同步控制。
null键值的支持:
Hashtable
不允许键或值为 null,如果存入 null,会抛出 NullPointerException。
HashMap
允许键为 null,值也可以为 null,允许存储键或值为 null 的键值对。
继承关系:
Hashtable
是早期的集合类,实现了 Map 接口和 Dictionary 类,并继承自 Dictionary 类。
HashMap
实现了 Map 接口,继承自 AbstractMap 类,不直接继承自 Dictionary。
性能:
在单线程环境下,HashMap
通常比 Hashtable
的性能更好,因为 Hashtable
的方法都是同步的,在不需要同步的情况下会有一些额外开销。
综上所述,HashMap
是更常用的键值对集合,因为它不限制 null 的使用并且在单线程环境下性能更好。而 Hashtable
在多线程环境下可以提供线程安全,但在性能上可能不如 HashMap
。
在 Java 中,这些集合类对 null 的处理方式如下:
ArrayList:可以存储 null 值。ArrayList 是数组实现的动态数组,可以存储任意类型的对象,包括 null。
HashMap:允许键和值为 null。在 HashMap 中,键和值都可以存储为 null。但需要注意,若将键存储为 null,则其对应的键值对将存储在哈希表数组的第一个位置(索引为 0)。
HashSet:允许存储一个 null 元素。HashSet 是基于 HashMap 实现的,它内部使用 HashMap 来存储元素。因此,HashSet 可以存储一个 null 元素。
需要注意的是,虽然这些集合类允许存储 null,但在使用时应谨慎考虑 null 值可能带来的问题,特别是在使用 null 作为键时可能引发的空指针异常或逻辑错误。
在 Java 中,线程安全的 Map 实现主要是 ConcurrentHashMap
。ConcurrentHashMap
是一个线程安全的哈希表实现,与 Hashtable
不同,它提供了更好的并发性能。
ConcurrentHashMap
的线程安全性主要基于以下几个特性:
分段锁机制:ConcurrentHashMap
内部使用分段锁(Segment)来控制不同部分(段)的访问,将整个 Map 分成多个小的 Segment,每个 Segment 独立加锁,不同线程访问不同 Segment 的数据时可以并行进行,提高了并发性能。
读写分离:ConcurrentHashMap
允许多个线程同时读取,但对于写操作,需要加锁。因此,它的读操作不会阻塞,可以并发进行。
安全性保障:在保证并发性的同时,ConcurrentHashMap
提供了一定的安全性保障,避免了传统 HashMap 在多线程环境下可能导致的并发修改异常。
除了 ConcurrentHashMap
,还有一些线程安全的 Map 实现,如 Collections.synchronizedMap()
可以将普通的 HashMap
包装成线程安全的 Map,但它的并发性能通常不如 ConcurrentHashMap
。
如果需要保持插入顺序的 Map,可以使用 Java 提供的 LinkedHashMap
。LinkedHashMap
继承自 HashMap
,它在内部使用双向链表维护了插入顺序。
在 LinkedHashMap
中,每个 Entry(键值对)都连接着上一个和下一个 Entry,这样就形成了一个链表。这个链表定义了键值对的顺序,即按照插入顺序或者访问顺序(LRU 最近最少使用策略)来遍历 Map 的元素。
因此,LinkedHashMap
可以保持插入元素的顺序,遍历时会按照插入的顺序返回键值对。
数据结构划分:字节流、字符流 功能上划分:输入流、输出流 阻塞和非阻塞角度Java中分为: 1).BIO:BloCk IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。 2).NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。 3).AIO:AsynChronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
在 Java 中,可以通过以下几种方式获取线程安全的集合:
使用 Collections.synchronizedList
创建线程安全的 List:
ListsynchronizedList = Collections.synchronizedList(new ArrayList<>());
这种方式会返回一个线程安全的 List,它对每个方法调用都进行了同步处理。但需要注意,虽然可以保证线程安全,但在高并发情况下性能可能会有所下降。
使用 java.util.concurrent
包下的并发集合类: Java 并发包提供了许多线程安全的集合类,如 CopyOnWriteArrayList
和 ConcurrentLinkedQueue
。这些集合类通常在并发环境下表现更好,并且提供更高的并发性能。
CopyOnWriteArrayList
:适用于读多写少的场景,在写入时会复制一个新的数组,读取不会阻塞,不会抛出并发修改异常。
ListcopyOnWriteList = new CopyOnWriteArrayList<>();
使用 java.util.Collections.synchronizedXXX
创建其他线程安全的集合: 除了 Collections.synchronizedList
,还有 synchronizedSet
、synchronizedMap
等方法,可以用于创建线程安全的 Set 和 Map。
这些线程安全的集合类可以在多线程环境中安全地被多个线程访问,但需要注意,虽然它们提供了一定程度的线程安全性,但在并发量较高的情况下,仍需谨慎处理并发问题,以确保线程安全性和性能。