容器-常见面试题

目录

ArrayList 的扩容机制

HashMap 和 Hashtable 的区别

HashMap的底层实现

JDK1.8之前

JDK1.8之后

HashMap的长度为什么是2的幂次方

HashMap 多线程操作导致死循环问题

ConcurrentHashMap 

ConcurrentHashMap线程安全的具体实现方式/底层具体实现

JDK1.7

JDK1.8 

集合框架底层数据结构总结

Collection

Map


ArrayList 的扩容机制

详见笔主的这篇文章:通过源码一步一步分析ArrayList 扩容机制

 

HashMap 和 Hashtable 的区别

  • 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
  • 初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
  • 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

 

HashMap的底层实现

JDK1.8之前

JDK1.8 之前HashMap底层是数组和链表结合在一起使用也就是链表散列。HashMap通过key的hashCode经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。

所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法,换句话说使用扰动函数之后可以减少碰撞

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

jdk1.8ä¹åçåé¨ç»æ

JDK1.8之后

相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。

HashMap的长度为什么是2的幂次方

为了能让HashMap存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。

用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“(n-1)&hash”(n代表数组长度)。这也就解释了HashMap的长度为什么是2的幂次方。

这个算法应该如何设计呢?

我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说hash%length==hash&(length-1)的前提是length是2的n次方)。并且采用二进制位操作&,相对于%能够提高运算效率,这就解释了HashMap的长度为什么是2的幂次方。

HashMap 多线程操作导致死循环问题

主要原因在于 并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。

详情请查看:https://coolshell.cn/articles/9606.html

 

ConcurrentHashMap 

在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 

到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

ConcurrentHashMap线程安全的具体实现方式/底层具体实现

img

img

JDK1.7

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。

ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成

Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。

static class Segment<K,V> extends ReentrantLock implements Serializable {
}

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK1.8 

ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(long(N)))

synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

 

集合框架底层数据结构总结

Collection

1. List

  • Arraylist: Object数组
  • Vector: Object数组
  • LinkedList: 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) 详细可阅读JDK1.7-LinkedList循环链表优化

2. Set

  • HashSet(无序(不保证迭代顺序),唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

Map

  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  • LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
  • Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)
<think>好的,我现在需要整理一些关于Docker Compose的面试题。首先,我得回顾一下Docker Compose的基本概念和常用命令,这样才能确保问题覆盖到关键点。用户提供的引用中有一些安装和配置相关的命令,比如引用3和4提到了安装Docker Compose的方法,这可能涉及到版本选择和权限设置的问题,可以作为面试题的一部分。 接下来,我应该考虑面试题的不同类型,比如基础概念、命令使用、配置文件编写、实际应用场景以及故障排除等。例如,Docker Compose与Dockerfile的区别是一个常见的基础问题,可以引用用户提到的docker-compose.yaml文件(引用2)来举例说明。另外,引用2中的docker restart命令可能涉及到容器管理的相关问题,比如如何通过Compose管理多个容器。 用户还提到了离线安装(引用4),这可能涉及到如何在无网络环境下部署Docker Compose,这也是一个实用的面试题方向。此外,引用3中的curl命令下载特定版本,可以引申到版本管理和兼容性问题。 需要确保问题涵盖核心功能,比如服务定义、网络配置、数据卷挂载等。同时,高级主题如多环境配置、扩展性和与CI/CD的集成也可能被问到。还要注意实际应用,比如如何通过Compose文件定义多个服务并设置依赖关系,这可以参考用户提供的docker-compose.yaml示例(引用2)。 最后,故障排除和调试命令也是面试中常考的内容,比如查看日志、重建服务等。需要结合用户提供的命令如docker restart(引用2)和docker-compose up(引用3)来设计相关问题。要确保每个问题都能考察候选人的实际应用能力和理解深度,同时引用中的内容可以作为答案的支持材料。</think>### Docker Compose 常见面试题整理 --- #### **基础概念** 1. **Docker Compose 的作用是什么?** Docker Compose 用于定义和运行多容器 Docker 应用,通过 YAML 文件配置服务、网络和卷,简化多容器编排过程[^3][^4]。 2. **Docker Compose 与 Dockerfile 的区别?** - Dockerfile 用于构建单个镜像,定义容器内的环境。 - Docker Compose 用于编排多个容器,定义服务间的依赖关系和资源(如网络、卷)[^2][^3]。 3. **如何指定 Docker Compose 的版本?** 在 `docker-compose.yaml` 文件中使用 `version` 字段,例如: ```yaml version: '3.8' ``` --- #### **核心命令** 4. **如何启动 Docker Compose 定义的所有服务?** ```bash docker-compose up -d ``` `-d` 表示后台运行[^4]。 5. **如何查看 Docker Compose 服务的日志?** ```bash docker-compose logs [服务名] ``` 6. **如何重建并重启所有服务?** ```bash docker-compose up --build -d ``` --- #### **配置文件解析** 7. **如何定义服务间的依赖关系?** 使用 `depends_on` 字段,例如: ```yaml services: web: depends_on: - db ``` 但需注意:`depends_on` 仅控制启动顺序,不保证服务就绪。 8. **如何配置数据卷(Volume)?** ```yaml volumes: - ./app:/app # 挂载宿主机目录 - db_data:/var/lib/mysql # 命名卷 volumes: db_data: # 声明命名卷 ``` 9. **如何设置环境变量?** - 直接在 `environment` 字段定义: ```yaml environment: MYSQL_ROOT_PASSWORD: password ``` - 通过 `.env` 文件引用: ```yaml env_file: .env ``` --- #### **高级场景** 10. **如何实现多环境配置(开发/生产)?** 使用多个 Compose 文件(如 `docker-compose.prod.yml`),通过 `-f` 指定: ```bash docker-compose -f docker-compose.yml -f docker-compose.prod.yml up ``` 11. **Docker Compose 如何管理网络?** - 默认创建名为 `<项目名>_default` 的网络,服务可通过服务名互相通信。 - 自定义网络示例: ```yaml networks: app_net: driver: bridge ``` 12. **如何限制容器资源(CPU/内存)?** ```yaml services: web: deploy: resources: limits: cpus: '0.5' memory: 512M ``` --- #### **故障排查与优化** 13. **如何调试服务启动失败的问题?** - 查看日志:`docker-compose logs [服务名]` - 手动启动容器:`docker-compose run [服务名] sh` 14. **离线安装 Docker Compose 的步骤?** 1. 从 [GitHub Releases](https://github.com/docker/compose/tags) 下载对应版本的二进制文件[^4]。 2. 复制到 `/usr/local/bin/` 并添加执行权限: ```bash sudo cp docker-compose /usr/local/bin/ sudo chmod +x /usr/local/bin/docker-compose ``` --- #### **实际应用** 15. **如何用 Docker Compose 部署一个 Web 应用 + MySQL 服务?** ```yaml version: '3.8' services: web: image: nginx:alpine ports: - "80:80" depends_on: - db db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: example ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值