基础
基本数据类型
| 类型 | 字节 |
|---|---|
| byte | 1 |
| short | 2 |
| int | 4 |
| long | 8 |
| char | 2 |
| float | 4 |
| double | 8 |
String
底层实现:
java 9 将 char[] 改为了 byte[]
String 支持两种编码方案:Latin-1 和 UTF-16
Latin-1 编码下, byte 1 字节, char 2 字节
可以节省空间
运算符实现:
- '+' '-' 运算符重载 字节码中是使用的 StringBuilder
解析 String s = new String("aa");
- 首先字面量 "aa" 会在字符串常量池中创建一个对象
- new String("aa") 会在堆中创建一个 "aa" 的副本对象
String.intern() 常量池中有, 直接返回池中引用 常量池中没有, 在池中创建一个, 再返回其引用
BitSet
BitSet 内部维护了一个 long[] words, 顺序地表示 1 到 (words.length * 64)
每个 long 占用 64 位
Object
- registerNatives() 类被加载的时候,调用该方法,以完成对其他本地方法的注册
- clone
- equals
- hashCode
- toString
- wait
- notify
- notifyAll
- @Deprecated(since="9") finalize
引用类型
- 强引用(永久有效): 只要有强引用指向, 并且 GC Roots 可达, 那 GC 时就不会回收
- 软引用(内存不足) SoftReference: OOM 前会加入回收范围; 主要用来缓存服务器中间计算结果和不需要实时保存的用户行为等
- 弱引用(再次 YGC) WeakReference: 引用的对象在下一次 YGC 时回收
- 虚引用(即时失效) PhantomReference: 定义完成后就无法通过该引用获取指向的对象. 使用虚引用的唯一目的是希望能在回收时收到系统的通知. 必须与引用队列一起使用, 回收对象内存前会把虚引用加入引用队列中, 入队就算作通知
exception error
exception 能被程序本身可以处理,error 是程序本身无法处理的(jvm 通常选择线程终止) error 通常是虚拟机运行错误
受检异常和非受检异常
受检异常:编译时的异常
非受检异常:RuntimeException 和 Error
try catch finally
不要在 finally 语句块中使用 return, try 中的 return 值会被 finally 中 return 的覆盖
try-with-resources
try(Scanner scanner=new Scanner(new File("test.txt"))){}泛型
泛型类: 实例化的时候必须指定类型 泛型接口: 继承的时候要指定类型 泛型方法
SPI
SPI(Service Provider Interface), 服务提供者的接口 跟 API 有些类似, 但是接口由服务调用方规定, 然后通过 java.util.ServiceLoader 加载 /META-INF/services/接口名 文件中的全类名
例如 JDBC
I/O
属于 OSI 中的表示层
字节流: InputStream OutputStream FileInputStream
- BufferedInputStream
- DataInputStream
- ObjectInputStream
字符流: Reader Writer
为什么要有两种流:
- 字符流 JVM 将字节码转换得到的, 耗时
- 字节流不知道编码类型会出问题
java 调用 python 脚本的示例
展示 BufferedReader 的使用方式
public class PythonSupport {
public static void readLine() {
Process process;
String[] arg = {"python", "D:\\workbench\\source\\common\\practice\\src\\main\\resources\\regression.py",
Arrays.toString(new int[]{0, 60, 120, 180, 240, 300, 360}),
Arrays.toString(new int[]{1, 2, 3, 4, 5, 6, 7})};
try {
process = Runtime.getRuntime().exec(arg);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
// 等待进程执行完成
process.waitFor(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void socketClient() throws IOException {
String ip = "localhost";
int port = 50007;
try (Socket socket = new Socket(ip, port)) {
try (OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream())) {
writer.write("[0, 60, 120, 180, 240, 300, 360]|[1, 2, 3, 4, 5, 6, 7]");
writer.flush();
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
System.out.println(reader.readLine());
}
}
}
}随机访问流
RandomAccessFile 可以实现大文件断点续传 RandomAccessFile.mode
- r
- rw
- rws 对文件和元数据的修改都会同步
- rwd 对文件的修改都会同步
RandomAccessFile.seek() 定位
I/O 中的设计模式
Decorator 装饰器模式
- 使用装饰器是因为子类太多了, 单独实现比较麻烦
- 增强: 比如 ZipOutputStream 增强 BufferedOutputStream
Adapter 适配器模式
- InputStreamReader OutputStreamReader 就是适配器
- 另外, Executors 的内部类 RunnableAdapter 实现也属于适配器,用于将 Runnable 适配成 Callable。
工厂模式
观察者模式
Watchable.register(WatchService, WatchEvent.Kind<?>[]) 注册到 WatchService 进行事件监听
- StandardWatchEventKinds.ENTRY_CREATE: 文件创建
- StandardWatchEventKinds.ENTRY_DELETE: 文件删除
- StandardWatchEventKinds.ENTRY_MODIFY: 文件修改
register 返回 WatchKey, 可以获取对象的具体信息
WatchService daemon Thread 守护线程轮询
I/O 模型
应用程序发起 I/O 调用后,会经历两个步骤:
- 内核等待 I/O 设备准备好数据
- 内核将数据从内核空间拷贝到用户空间
unix 系统下, 有 5 种 I/O 模型
同步阻塞 应用程序发起 read 调用后阻塞, 等待内核准备数据和拷贝数据
同步非阻塞 应用程序 read 轮询内核, 内核准备数据不阻塞, 拷贝数据阻塞
多路复用 应用程序发起 select 调用, 阻塞直到多个 socket 中任意一个变为可读, 然后发起 read 调用, 拷贝数据
信号驱动式 IO 应用程序建立信号, 让内核在描述符就绪时回调
异步 POSIX 规范, 让内核在整个操作完成的时候通知, 与信号驱动的区别在于内核的通知一个是何时可以启动 IO, 另一个是 IO 何时完成
Java 中的 3 种常见的 I/O 模型
- BIO 应用程序发起 read 调用后, 阻塞, 直到内核把数据拷贝到用户空间
- NIO 1.4 中引入的, 提供了 Channel Selector Buffer 等抽象 可以看作多路复用模型
- AIO AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
Netty 尝试过 AIO, 效果一般
序列化协议
Kryo Protobuf ProtoStuff hessian
获取 class 对象
class C {
// 1 已知class
Class<Solution> aClass = Solution.class;
// 2 未知class
Class<?> bClass = Class.forName("packaege.com.leetcode.Solution");
// 3 已知对象
Class<? extends Solution> cClass = solution.getClass();
// 4 类加载器 Class 对象不会初始化
Class<?> dClass = ClassLoader.getSystemClassLoader().loadClass("packaege.com.leetcode.Solution");
}代理模式
静态代理 目标类要写出代理类, 也就是说手动写出.java 文件
动态代理 动态生成字节码文件到 JVM
JDK 动态代理 (模拟静态代理的方式, 代理类去实现接口) 核心: java.lang.reflect.InvocationHandler.invoke() java.lang.reflect.Proxy.newProxyInstance()
/**
handler 会被 proxy 持有; 但 proxy 是个什么东西不好说, $Proxy0.toString(Unknown Source) 报错
*/
class MyHandler implements InvocationHandler {
private final Object target;
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something
// 注意这里是 target
return method.invoke(target, args);
}
}class ProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyHandler(target)
);
}
} public void main() {
CommonInterface target = new Target();
CommonInterface proxy = (CommonInterface) ProxyFactory.getProxy(target);
proxy.func();
}CGLib 动态代理 net.sf.cglib.proxy.MethodInterceptor.intercept() net.sf.cglib.proxy.Enhancer.create()
class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// do something
return methodProxy.invokeSuper(o, objects);
}
}class ProxyFactory {
public static Object getProxy(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(target.getClass().getClassLoader());
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new MyInterceptor());
return enhancer.create();
}
} public void main() {
Target target = new Target();
Target proxy = (Target) ProxyFactory.getProxy(target);
proxy.func();
}BigDecimal
浮点数据类型不能进行值的比较(== , equals), 因为存储了精度 BigDecimal.equals 也会比较精度
Unsafe
单例模式, 获取的时候会校验类加载器, 必须是 PlatformClassLoader 才行 想获取有 2 种办法:
- 通过反射获取
- 将调用方的类路径通过 java -Xbootclasspath/a: ${path} 让引导类加载器加载
Unsafe 类的典型应用
内存操作 应用: DirectByteBuffer 直接内存操作类
内存屏障 指令重排可能引起 CPU 缓存中的数据和主存中的不一致, 内存屏障就是阻止屏障两边的指令重排
java//禁止load操作重排序。屏障前的load操作不能被重排到屏障后,屏障后的load操作不能被重排到屏障前 public native void loadFence(); //禁止store操作重排序。屏障前的store操作不能被重排到屏障后,屏障后的store操作不能被重排到屏障前 public native void storeFence(); //禁止load、store操作重排序 public native void fullFence();读屏障实例
javaclass Solution { public static void main(String[] args) { @Getter class ChangeThread implements Runnable { /* volatile */ boolean flag = false; @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("subThread flag:" + flag); flag = true; System.out.println("subThread change flag to:" + flag); } } ChangeThread changeThread = new ChangeThread(); new Thread(changeThread).start(); while (true) { // 主线程不断获取子线程的 flag, (主存中的 flag boolean flag = changeThread.isFlag(); // getUnsafe().loadFence(); if (flag) { System.out.println("detected flag changed"); break; } } System.out.println("main thread end"); } }应用: StampedLock 有{ 读 写 乐观读 }3 种模式, 乐观读的时候不会阻塞写锁, 通过 validate() 方法加入了读屏障,同步主存数据
对象操作 普通读写 volatile 读写 有序写入 创建对象 allocateInstance 不调用构造函数, 不执行初始化代码, JVM 不安全检查
数组操作 应用: AtomicIntegerArray 中获取数组的起始偏移量和第一个元素的大小
CAS 操作 CAS(指令 cmpxchg) 乐观锁
线程调度
- park / unpark 线程挂起
- monitor 相关的操作已经废弃了 应用: AbstractQueuedSynchronizer(AQS) 通过 LockSupport 的 park/unpark 实现线程的阻塞和唤醒
class 操作 静态属性
- shouldBeInitialized 是不是应该实例化
- staticFieldBase 获取静态属性的指针
- staticFieldBase 获取静态属性的偏移量 定义一个类
- defineClass 会跳过所有的 JVM 检查
- Lambda 表达式以前需要 ASM 动态生成字节码, 进而生成匿名类
系统信息
- addressSize 获取系统指针大小
- pageSize 获取内存页大小
语法糖
编译之后是什么呢?
- switch 支持 String 与枚举
- 泛型
- 自动装箱与拆箱
- 可变长参数
- 枚举
- 内部类
- 条件编译
- 断言
- 数值字面量
- for-each
- try-with-resource
- Lambda 表达式
compare
java.lang.Comparable
- java.lang.Comparable#compareTo
java.util.Comparator
- java.util.Comparator#compare