JAVA基础

概念
语法糖
Lambda表达式:依赖底层API
泛型
- 定义接口、类时使用类型参数,使用时再用具体的类型来替换。
- 优点:代码复用、类型安全
反射机制
- 程序运行时能获取所有类的属性和方法。
- 为什么慢:
- 不能执行某些java虚拟机优化
- 包装、拆包
- 遍历查找方法数组
- 可见性检查、参数检查
动态代理
- 在运行时动态创建代理对象。在不修改目标对象代码的情况下,为对象动态地添加额外的功能。
- 实现方式:
- JDK动态代理:
java.lang.reflect包的Proxy类和InvocationHandler接口。对象必须实现一个或多个接口。 - CGLIB:运行时动态生成一个子类对象。
- JDK动态代理:
异常
-
受检异常(Checked Exception):必须处理(捕获或抛出),否则编译报错。
-
非受检异常(Unchecked Exception):运行时报错。如空指针、数组越界等。
值传递、引用传递
- 值传递:复制出一个副本出来,传递副本。
- Java是值传递,通过复制的方式把引用关系传递了。
浅拷贝、深拷贝
- 浅拷贝:只复制对象的地址,而不是对象本身。共用一个地址。互相影响。
- 深拷贝:新建一个对象,再复制。不同地址。互不影响。
数据类型
基本类型、包装类
String
-
不可变:
- 原因:
- 缓存优势:字符串频繁使用,Java通过字符串池来缓存字符串,节省内存资源。不可变性使得多个相同内容的字符串变量可以指向同一个对象,从而实现内存节省。
- hashCode缓存:由于字符串的不可变性,它们的哈希码(hashCode)不会改变。String类利用这一点,缓存了哈希码值,提高了性能。
- 线程安全:不可变对象天然线程安全,因为它们的内容不会被改变。这意味着多个线程可以安全地共享不可变对象,无需额外的同步机制。
- 安全性:存储敏感信息。
- 原因:
-
如何实现:
- String类被声明为final,不能被继承,因此其内部方法不能被覆盖。
- String类中的字符存储数组
char[]被声明为final,一旦初始化后不能指向其他数组。 - String类没有提供修改字符串内容的公共方法,如追加、删除或修改字符的方法。任何看似修改字符串的操作实际上都会创建一个新的String对象。
StringBuffer可变、线程安全;StringBuilder可变、不安全
面向对象
三大特征
- 封装
- 继承:
- 单继承:一个类只能继承一个父类。
- 多态:
- 定义:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
- 实现:
- 方法重载(Overloading):同一个类中可以有多个同名方法,只要它们的参数列表不同(参数类型或数量不同)。
- 方法重写(Overriding):子类可以覆盖父类中的方法,提供具体的实现。当通过父类引用调用方法时,实际执行的是子类中的方法。
- 接口实现:通过实现接口中的方法,不同的类可以提供各自的实现,实现多态。
序列化
- 将对象转换为字节序列。
- 类序列化为数据流,通过输入输出流将内存中的类持久化到硬盘或网络中。
- 对象的类需要实现
java.io.Serializable接口。
创建对象的方法
- new关键字:最常见的创建对象方式,通过new关键字直接创建类的实例。
- 通过反射机制:利用Java的反射API,在运行时动态创建对象。
- 克隆:实现
Cloneable接口并重写clone()方法,通过克隆已有对象创建新对象。 - 反序列化:从文件、网络或其他来源读取对象的二进制流,反序列化创建对象。
类
抽象类和接口
- 接口:只是定义了一些方法,没有实现。
- 修饰符:
public、protect、private都有;默认public。 - 单继承;多实现。
- 作用:代码复用(模板方法模式);制定规范。
- 修饰符:
集合
分类
- List、Set、Queue、Stack、Map
List
-
ArrayList:动态数组。
-
LinkedList:双向链表。
-
Vector:类似于ArrayList,也是基于动态数组实现的。线程安全的。
-
数组转list:
asList方法;list转数组:toArray方法。 -
ArrayList 和 LinkedList 的区别:
- 底层使用的数据结构
- 操作数据效率
- 内存空间占用
- 线程安全
Set
- HashSet:底层使用哈希表实现,通过哈希码和哈希函数来快速定位元素。
- TreeSet:基于红黑树实现,能够按照元素的自然顺序或定制顺序对元素进行排序。通过树的结构来保证元素的唯一性。
Map
- hash冲突解决方法:
- 开放定址:找下一个空位置。
- 链地址(hashmap采用这个):每个哈希桶(bucket)指向一个链表。当发生冲突时,新的元素将被添加到这个链表的末尾。当链表长度超过8时,链表会转换为红黑树,以提高搜索效率。红黑树结点数量小到6时,转回链表。
- 再哈希:使用两个哈希函数,当发生冲突时,使用第二个哈希函数计算新的位置。
- 建立公共溢出区:基本表+溢出表,冲突的放溢出表。
- 一致性哈希:分布式系统中。将哈希空间视为一个环状结构,将节点和数据都映射到这个环上,并通过计算哈希值来确定数据的存储和检索位置。
HashMap
-
结构:
-
一个数组,数组的每个成员都是一个链表。
-
数组是
entry类型,每个entry包含一对 k-v。 -
红黑树:二叉查找树可以提高查找效率,但极端情况下会退化成链表。AVL树虽然保持平衡,但每次插入都需要旋转,消耗时间。红黑树作为二叉平衡树的一种,插入和删除操作最多需要两次旋转,更适合频繁的插入和删除操作。
- 所有的叶子节点都是黑色的空节点,也就是叶子节点不存数据。
- 任何相邻的节点都不能同时为红色,红色节点是被黑色节点隔开的,每个节点,从该节点到达其可达的叶子节点的所有路径,都包含相同数目的黑色节点。
- O(logn)
-
链表长度>8时变为红黑树;<6时退化为链表。
-
-
方法:
-
hash():根据key计算k-v在数组中的下标。
- 调用
Object的HashCode()方法,返回一个整数,用这个整数对容量取模。 - 提高效率:
- 位运算代替取模运算:要求容量是2^n。
- 对
HashCode进行扰动计算。
- 调用
-
get():计算键的哈希值,通过哈希值计算出在数组中的索引位置。如果该位置上的元素为空,说明没有找到对应的键值对,直接返回null。如果该位置上的元素不为空,遍历该位置上的元素,如果找到了与当前键相等的键值对,那么返回该键值对的值,否则返回null。
-
put():计算键的哈希值,通过哈希值计算出在数组中的索引位置。如果该位置上的元素为空,那么直接将键值对存储在该位置上。如果该位置上的元素不为空,那么遍历该位置上的元素,如果找到了与当前键相等的键值对,那么将该键值对的值更新为当前值,并返回旧值。如果该位置上的元素不为空,但没有与当前键相等的键值对,那么将键值对插入到链表或红黑树中。插入成功后,如果需要扩容,那么就进行一次扩容操作。
-
remove:计算键的哈希值,通过哈希值计算出在数组中的索引位置。如果该位置上的元素为空,说明没有找到对应的键值对,直接返回null。如果该位置上的元素不为空,检查是否与当前键相等,如果相等,那么将该键值对删除,并返回该键值对的值。如果该位置上的元素不为空,但也与当前键不相等,那么就需要在链表或红黑树中继续查找。遍历链表或者红黑树,查找与当前键相等的键值对,找到则将该键值对删除,并返回该键值对的值,否则返回null。
-
-
扩容:
- 参数设置:
- 初始容量=(需要存储的元素数/负载因子)+1
- 负载因子=0.75
- 阈值=容量*负载因子。超过阈值则扩容。
- 过程:
- 桶节点没有形成链表: 直接rehash到其他桶中。
- 桶中形成链表: 将链表重新链接。
- 桶中的链表已经形成红黑树,但是链表中的元素个数小于6: 取消树化。
- 参数设置:
-
JDK 1.8改动:
- 引入红黑树。
- 节点
Entry变为Node。 - 尾插法。
hash方法,位运算和异或运算少了。- 优化扩容机制。
ConcurrentHashMap
-
如何保证线程安全:
- JDK 1.7:分段锁。
- JDK1.8:节点锁,CAS+Synchronized。
- 添加元素时,如果目标桶为空,那么使用CAS操作来添加新节点。
- 如果目标桶不为空,对桶的第一个节点加
synchronized锁,然后进行修改(链表或红黑树操作)。
-
哪些地方做了并发控制:
- 初始化桶、put元素、扩容。
-
如何保证fail-safe:
- 遍历使用弱一致性迭代器,迭代过程中反映的是某一时刻的快照,不会阻塞其他线程的修改操作。
- CAS+Synchronized,而每个桶的第一个结点是原子更新的。
-
多线程协同扩容。
Stream
-
并行流:利用多核处理器的并行能力来加速集合操作。基于Fork/Join框架,将大任务分解成多个小任务(Fork),并在多个线程中并行执行这些小任务(Join)。
-
流处理:
- 流的创建
- 中间操作:
filter过滤map映射limit/skip筛选出/扔掉前n个元素sorted排序distinct去重
- 最终操作:
for each迭代每个数据count元素数collect汇总
集合的并发安全
-
HashMap并发问题:头插法,多线程并发扩容时,出现循环引用问题。
-
如何将集合变成线程安全的:
ReentrantLock、synchronized对代码加锁。ThreadLocal,将集合放到线程内访问。但是这样集合中的值就不能被其他线程访问了。Collections.synchronizedXXX()方法。可以获得一个线程安全的集合。- 使用不可变集合进行封装。当集合是不可变的时候,自然是线程安全的。
-
COW:
- 读时,共享同一真正的内容;修改时,把真正的内容复制出去形成一个新的内容,再修改。
- 读写分离。读多写少。
4490

被折叠的 条评论
为什么被折叠?



