Java基础
Java基础
反射相关
反射机制指的是程序在运行时能够获取自身的信息。
反射操作的目标对象(实例)是从堆(Heap)中获得的,而类的元数据(如方法、字段、构造方法等结构信息)是从方法区(Method Area)或元空间(Metaspace) 中获得的。
为什么反射慢?
- 由于反射涉及动态解析的类型,因此不能执行某些Java虚拟机优化,如JIT优化。
- 在使用反射时,参数需要包装(boxing)成Object[] 类型,但是真正方法执行的时候,又需要再拆包(unboxing)成真正的类型,这些动作不仅消耗时间,而且过程中也会产生很多对象,对象一多就容易导致GC,GC也会导致应用变慢。
- 反射调用方法时会从方法数组中遍历查找,并且会检查可见性。这些动作都是耗时的。
a=a+b 与 a+=b 的区别
+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。
byte a = 127;
byte b = 127;
b = a + b; // error : cannot convert from int to byte
b += a; // ok
// (因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)
finalize是什么
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,但是什么时候调用 finalize 没有保证。
为什么不能用BigDecimal的equals方法做等值比较?
因为BigDecimal的equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale),而对于0.1和0.10这两个数字,他们的值虽然一样,但是精度是不一样的,所以在使用equals比较的时候会返回false。
String、StringBuilder和StringBuffer
Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。
StringBuilder线程不安全 StringBuffer线程安全
finally中代码一定会执行吗?
如果没有符合这两个条件的话,finally中的代码就无法被执行,如发生以下情况,都会导致finally不会执行:
1、System.exit()方法被执行
2、Runtime.getRuntime().halt()方法被执行
3、try或者catch中有死循环
4、操作系统强制杀掉了JVM进程,如执行了kill -9
5、其他原因导致的虚拟机崩溃了
6、虚拟机所运行的环境挂了,如计算机电源断了
7、如果一个finally是由守护线程执行的,那么是不保证一定能执行的,如果这时候JVM要退出,JVM会检查其他非守护线程,如果都执行完了,那么就直接退出了。这时候finally可能就没办法执行完。
CopyOnWriteArraylist是如何实现线程安全的
CopyOnWriteArrayList底层也是通过一个数组保存数据,使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后,其他线程可以及时感知到。
在写入操作时,加了一把互斥锁ReentrantLock以保证线程安全。
看到源码可以知道写入新元素时,首先会先将原来的数组拷贝一份并且让原来数组的长度+1后就得到了一个新数组,新数组里的元素和旧数组的元素一样并且长度比旧数组多一个长度,然后将新加入的元素放置都在新数组最后一个位置后,用新数组的地址替换掉老数组的地址就能得到最新的数据了。
在我们执行替换地址操作之前,读取的是老数组的数据,数据是有效数据;执行替换地址操作之后,读取的是新数组的数据,同样也是有效数据,而且使用该方式能比读写都加锁要更加的效率。
现在我们来看读操作,读是没有加锁的,所以读是一直都能读
HashMap的大小为什么是2的n次方?
在 JDK1.7 中,HashMap 整个扩容过程就是分别取出数组元素,一般该元素是最后一个放入链表中的元素,然后遍历以该元素为头的单向链表元素,依据每个被遍历元素的 hash 值计算其在新数组中的下标,然后进行交换。这样的扩容方式会将原来哈希冲突的单向链表尾部变成扩容后单向链表的头部。
而在 JDK 1.8 中,HashMap 对扩容操作做了优化。由于扩容数组的长度是 2 倍关系,所以对于假设初始 tableSize = 4 要扩容到 8 来说就是 0100 到 1000 的变化(左移一位就是 2 倍),在扩容中只用判断原来的 hash 值和左移动的一位(newtable 的值)按位与操作是 0 或 1 就行,0 的话索引不变,1 的话索引变成原索引加上扩容前数组。
之所以能通过这种“与运算“来重新分配索引,是因为 hash 值本来就是随机的,而 hash 按位与上 newTable 得到的 0(扩容前的索引位置)和 1(扩容前索引位置加上扩容前数组长度的数值索引处)就是随机的,所以扩容的过程就能把之前哈希冲突的元素再随机分布到不同的索引中去。
Linux常用的命令有哪些
以下是一份Linux常用命令速查表,涵盖文件操作、系统管理、网络工具等核心场景:
📂文件与目录操作
命令 | 作用 | 常用示例 |
---|---|---|
ls | 列出目录内容 | ls -alh (详细列表含隐藏文件) |
cd | 切换目录 | cd ~ (返回家目录) |
pwd | 显示当前路径 | pwd |
mkdir | 创建目录 | mkdir -p dir1/dir2 (递归创建) |
rmdir | 删除空目录 | rmdir empty_dir |
cp | 复制文件 | cp -r dir1/ dir2/ (递归复制目录) |
mv | 移动/重命名文件 | mv old.txt new.txt |
rm | 删除文件 | rm -rf dir/ (⚠️强制递归删除) |
touch | 创建空文件/更新时间戳 | touch file.txt |
cat | 显示文件内容 | cat file.txt |
less / more | 分页查看文件 | less -N log.log (显示行号) |
head / tail | 查看文件头部/尾部 | tail -f app.log (实时追踪日志) |
find | 搜索文件 | find /home -name "*.conf" |
grep | 文本搜索 | grep -r "error" /var/log/ |
chmod | 修改权限 | chmod 755 script.sh |
chown | 修改所有者 | chown user:group file.txt |
💻系统与进程管理
命令 | 作用 | 常用示例 |
---|---|---|
ps | 查看进程 | `ps -ef |
top / htop | 动态监控进程资源 | top -u mysql (按用户过滤) |
kill | 终止进程 | kill -9 12345 (强制终止) |
systemctl | 系统服务管理 | systemctl restart apache2 |
df | 磁盘空间统计 | df -hT (人类可读+文件系统类型) |
du | 目录空间占用 | du -sh /var/ (汇总大小) |
free | 内存使用情况 | free -m (以MB显示) |
uname | 系统信息 | uname -a (内核版本等) |
uptime | 系统运行时间 | uptime (负载情况) |
🌐网络工具
命令 | 作用 | 常用示例 |
---|---|---|
ping | 测试网络连通性 | ping -c 4 google.com |
ifconfig / ip | 网络接口配置 | ip addr show (查看IP地址) |
netstat | 网络连接状态 | netstat -tulpn (监听端口) |
ss | 更高效的socket统计 | ss -ltn (监听TCP端口) |
curl | 网络数据传输 | curl -I https://example.com |
wget | 下载文件 | wget -c http://file.zip |
ssh | 远程登录 | ssh user@192.168.1.100 |
scp | 安全传输文件 | scp file.txt user@host:/tmp/ |
traceroute | 路由追踪 | traceroute example.com |
📦压缩与解压
命令 | 作用 | 常用示例 |
---|---|---|
tar | 打包/解包 | tar -czvf backup.tar.gz dir/ (压缩) tar -xzvf backup.tar.gz (解压) |
gzip / gunzip | .gz压缩/解压 | gzip file.txt → file.txt.gz |
zip / unzip | .zip压缩/解压 | unzip archive.zip -d target/ |
select poll epoll的区别和特点
特性 | select | poll | epoll |
---|---|---|---|
基本原理 | 遍历+条件触发 | 遍历+条件触发 | 回调+事件触发 |
时间复杂 (检查就绪) | O(n) | O(n) | O(1) (就绪数目) |
fd 数量限制 | 有 (通常 1024) | 无 (由系统资源限制) | 无 (由系统资源限制) |
数据结构 | fd_set (位图) | struct pollfd 数组 | 内核红黑树 + 就绪链表 |
内核/用户空间交互 | 每次调用拷贝 fd_set | 每次调用拷贝 pollfd 数组 | 使用 mmap 共享内存 |
工作模式 | 条件触发 (LT) | 条件触发 (LT) | 支持条件触发 (LT) 和边缘触发 (ET) |
性能 (fd 多且活跃少时) | 低 | 低 | 高 |
跨平台 | 广泛支持 (POSIX) | 多数 UNIX | Linux 特有 (内核2.5.44+) |
API 使用复杂度 | 中等 | 中等 | 较高 |
适用场景 | 低并发/跨平台要求 | 稍高并发/需突破 select 限制 | 高并发/高性能服务器 |
select
特点与原理:
select
使用三个位图集合 (fd_set
) 来分别表示需要监视的可读、可写、异常文件描述符。- 调用时,用户态将这三个集合传递给内核。内核遍历所有传入的 fd,检查它们的状态。
select
会阻塞(直到有 fd 就绪或超时),或者非阻塞轮询。- 当
select
返回时,它会修改传入的fd_set
,标识哪些 fd 已经就绪,同时返回就绪的 fd 总数。 - 用户程序需要再次遍历自己之前传入的所有 fd,找到那些被
select
标记为就绪的 fd 进行处理。
优点:
- 跨平台: 被几乎所有主流操作系统支持,有良好的可移植性。
- API 相对简单: 概念清晰。
缺点:
fd 数量限制: 单个进程所能监视的 fd 数量有上限(由
FD_SETSIZE
定义,通常为 1024)。这是fd_set
位图的结构限制。线性扫描瓶颈:
- 内核态: 每次调用,内核都必须线性扫描 (O(n)) 所有传递给它的 fd(即使只有几个活跃)。
- 用户态:
select
返回后,用户程序也必须线性扫描所有自己关心的 fd 来确定哪些就绪(因为fd_set
告诉你有就绪,但不告诉你是哪几个,除非遍历)。
重复数据拷贝: 每次调用
select
都需要将包含大量 fd 信息的fd_set
从用户空间拷贝到内核空间;返回时又要将修改后的fd_set
从内核空间拷贝回用户空间。这对 fd 集合很大时带来显著开销。仅支持条件触发 (LT):
select
只支持水平触发模式。
总结: 简单、跨平台,但性能低下,只适用于连接数较少的场景。
poll
特点与原理:
poll
使用一个struct pollfd
的数组来管理 fd。每个pollfd
结构包含 fd 本身、用户关心的事件掩码 (events
)、以及内核返回的已发生事件掩码 (revents
)。- 调用时,用户态将这个数组指针传递给内核。内核遍历数组中的所有 fd,检查它们的状态。
poll
的行为同样会阻塞(或非阻塞),直到有 fd 就绪或超时。- 当
poll
返回时,它会修改每个pollfd
结构中的revents
字段来标识该 fd 上发生了什么事件(可读、可写、错误等),同时返回就绪的 fd 总数。 - 用户程序需要遍历整个
pollfd
数组,检查每个元素的revents
字段,找出就绪的 fd 进行处理。
优点:
- 突破 fd 数量限制: 使用数组传递 fd,不再有类似
select
的硬编码FD_SETSIZE
限制。fd 数量的理论上限仅受系统资源(如进程能打开的最大文件数)约束。 - 事件区分更精细:
pollfd
中的events
和revents
允许指定和区分更多类型的事件(如普通数据、带外数据、错误、连接挂断等),比select
的三种简单集合更灵活。
- 突破 fd 数量限制: 使用数组传递 fd,不再有类似
缺点:
线性扫描瓶颈依然存在:
- 内核态: 仍然需要线性扫描所有传入的 fd (O(n))。
- 用户态: 仍然需要线性扫描整个数组 (
pollfd
) 以找到就绪的 fd (revents
!= 0)。
重复数据拷贝: 每次调用
poll
都需要将包含大量pollfd
结构的数组从用户空间拷贝到内核空间;返回时也要将修改后的revents
从内核空间拷贝回用户空间。仅支持条件触发 (LT): 和
select
一样。
总结: 解决了
select
的 fd 数量限制,提供了更精细的事件描述,但性能瓶颈(内核和用户态的双重线性扫描、数据拷贝)与select
本质相同,仍不适用于大规模高并发。
epoll
特点与原理:
是 Linux 特有的高效 I/O 多路复用机制。它的核心设计思想是:
避免每次调用时的无差别轮询扫描和大量数据拷贝,直接跟踪真正活跃的 fd。
三个阶段:
epoll_create()
: 创建一个 epoll 实例(一个句柄epfd
),该实例在内核对应一个数据结构(通常包含一个红黑树和一个就绪链表)。
epoll_ctl()
: 向 epoll 实例(epfd
)注册/修改/删除 需要监听的 fd。这个操作只需要在你关注的事件集合变化时才调用(例如新连接建立时添加)。调用时提供epfd
,op
(操作类型),fd
, 和struct epoll_event
(包含用户关心的事件和关联数据)。epoll_wait()
: 等待事件发生。调用时,它阻塞(或限时)直到至少有一个之前注册的 fd 上发生事件,或者超时。当它返回时,它只填充用户提供的epoll_event
数组,其中只包含实际就绪的事件信息(fd 和具体事件)。用户程序只需遍历这个填充好的数组(里面全是就绪事件)即可处理,无需扫描所有被监视的 fd。
高效的核心机制:
- 内核数据结构: 内核使用一个红黑树来高效管理所有注册的 fd(
epoll_ctl
插入/删除 O(log n))。更重要的是,使用一个就绪链表(队列) 来存放被触发的事件。当某个 fd 上的事件发生时(例如收到数据),内核会通过注册的回调函数(callback)迅速地将一个表示该事件的epitem
结构放入就绪链表。这是epoll
事件驱动特性的关键,避免了轮询扫描。
- 内核数据结构: 内核使用一个红黑树来高效管理所有注册的 fd(
内存映射 (
mmap
):epoll
使用mmap
在内核空间和用户空间共享一块内存。当epoll_wait
返回时,内核可以直接将就绪事件的信息填入这块共享内存区域 (epoll_event
数组),用户程序也能直接读取。这避免了select/poll
那种数据在用户-内核间来回拷贝的开销。
触发模式:
- 条件触发 (LT - Level Triggered): 默认模式。只要 fd 处于就绪状态(如接收缓冲区有数据可读),每次调用
epoll_wait
都会报告它。类似于select/poll
的行为。 - 边缘触发 (ET - Edge Triggered): 只有当 fd 的状态发生改变时(例如从无数据变为有数据,或者新数据到达),才会报告一次该 fd 上的事件。使用 ET 模式时,用户程序必须一次性处理完所有可用的数据(循环读取/写入直到出现
EAGAIN
错误),否则后续epoll_wait
调用不会再通知你(除非又有新事件导致状态变化)。ET 模式可以进一步减少epoll_wait
的调用次数,提高效率。
- 条件触发 (LT - Level Triggered): 默认模式。只要 fd 处于就绪状态(如接收缓冲区有数据可读),每次调用
优点:
极致性能:
- 内核无扫描: 检查就绪事件的复杂度是 O(1),因为它只看就绪链表,而不是扫描所有 fd。
用户无扫描:
epoll_wait
返回的数组中只包含活跃事件,用户只需处理这些事件即可 (O(就绪事件数) )。无数据拷贝: 利用
mmap
共享内存,避免了用户-内核空间的大数据拷贝。无内置 fd 数量限制: 仅受系统全局资源限制。
支持边缘触发 (ET): 提供更高效的工作模式(需要正确使用)。
缺点:
- Linux 专属: 是 Linux 独有的特性,不具备
select/poll
的跨平台性。 - API 相对复杂: 需要理解三个函数 (
create
,ctl
,wait
) 和事件结构。ET 模式的使用有特殊要求。 - 调试相对复杂: 内核回调机制使得追踪事件来源不像遍历那么简单。
- Linux 专属: 是 Linux 独有的特性,不具备