Java笔记

73 阅读8分钟

String常量折叠

  • 基本数据类型以及字符串类型的常量
  • final修饰的基本数据类型以及字符串类型的变量

注:编译器在运行时才能知道其确切值的话,则无法进行优化

集合

Collection接口

image.png

java集合.jpg

  • ArrayList
    底层用Object数组存储Object[] elementData;,默认初始大小为10,每次扩容为原来的1.5倍newCapacity = oldCapacity + (oldCapacity >> 1); 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8。 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8
    System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) Native方法 Arrays.copyOf()方法(仍然是调用System.arraycopy方法) 复制数组达到变长的效果;最好在向 ArrayList 添加大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数

  • LinkedList
    底层用双向链表存储Node<E> first;Node<E> last;;

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
  • Vector
    古老实现类,方法均用synchronized修饰,线程安全;

  • Stack
    古老实现类,以synchronized修饰,线程安全;推荐使用Deque<E> stack = new ArrayDeque<>();作为栈;

  • HashSet
    底层用HashMap存储,所有的value为空对象PRESENT;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
  • LinkedHashSet
    底层用LinkedHashMap存储,所有的value为空对象PRESENT;

  • TreeSet
    底层用NavigableMap(继承自SortedMap),所有的value为空对象PRESENT;

  • PriorityQueue
    Object数组以及Comparator, newCapacity = oldCapacity + ((oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1));,Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2n+1] and queue[2(n+1)]. The priority queue is ordered by comparator, or by the elements' natural ordering, if comparator is null: For each node n in the heap and each descendant d of n, n <= d.

  • ArrayDeque
    The capacity of the deque is the length of this array, which is always a power of two. 默认为16,

    • addFirst/Last 无返回值
    • push=addFirst
    • add=addLast
    • offerFirst/Last add的返回布尔值版本
    • offer=offerLast
    • pollFirst/Last
    • poll=pollFirst
    • removeFirst/Last poll结果为null抛出异常版本
    • remove=removeFirst
    • pop=removeFirst
    • peekFirst/Last
    • peek=peekFirst
    • getFirst/Last peekFirst/Last的结果为null则抛出异常
    • element=getFirst

Map接口

image.png

  • HashMap
    默认大小为16,每次扩容翻倍;加载因子0.75,树化阈值8,非树化阈值6,最小树化容量64
    静态内部类Node<K, V>, 使用Node数组+链表+红黑树存储 hash方法 扰动函数 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);,减少碰撞;位于数组中的索引为(n-1)&hash, 扩容时只根据一位高位数为‘0’或‘1’即可计算所在数组索引位置;

-Hashtable 线程安全,方法使用synchronized修饰,不支持null键和null值,否则抛出NullPointerException;初始大小11,每次扩容2n+1;取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。” 并且采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。

  • LinkedHashMap
    每个Entry根据加入顺序形成Entry双向链表

  • TreeMap 红黑树存储;实现NavigableMap接口,有集合元素搜索功能;实现SortedMap接口,有按键排序功能

  • ConcurrentHashmap 线程安全版本 1.7 segment数组(大小初始化后不能改变 16 并发度ry数组+链表 对segment加锁 粒度大 并发度低
    Segment继承了ReentrantLock,使用tryLock()获取锁,获取不到则使用scanAndLockForPut()(自旋tryLock,超过次数则lock()阻塞知道获取到锁)继续获取;

1.8 Node数组+链表+红黑树 Node+CAS+synchronized(只锁定链表或红黑树的首节点)保证并发安全 粒度小 并发度高

fail-fast & fail-safe

fail-fast机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。fail-fast是一种错误检测机制,一旦检测到可能发生错误,就立马抛出异常,程序不继续往下执行。例:在for-each循环(使用的是Iterator迭代器)remove元素时,调用checkForComodification方法,此方法会检查,并可能抛出ConcurrentModificationException提示用户。expectedModCount在整个迭代过程除了一开始赋予初始值modCount外,并没有再发生改变,但是当另一个线程(并发修改)或者同一个线程遍历过程中,调用相关方法使集合的个数发生改变,就会使modCount发生变化,

final void checkForComodification() {
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
}

fail-safe基于容器的一个克隆。因此,对容器内容的修改不影响遍历。java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。常见的的使用fail-safe方式遍历的容器有ConcerrentHashMap和CopyOnWriteArrayList等。采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

Java异常

Throwable

  • Exception 程序可处理
    • Checked Exception 必须处理,否则编译不通过
      • IOException
      • ClassNotFoundException
      • SQLException
      • ...
    • Runtime Exception 可不处理
      • NullPointerException
      • ArithmeticException
      • ClassCastException
      • IndexOutOfBoundException
      • ...
  • Error 程序无法处理
    • OutOfMemoryError
    • VirtualMachineError
    • StackOverFlowError
    • IOError
    • ...

finally不执行的情况

  • 虚拟机退出
  • 关闭CPU
  • 程序所在线程死亡

泛型

增强代码的可读性及稳定性。 泛型擦除可能导致的问题:

  • 重载不支持泛型
  • catch不能用泛型类型参数
  • 泛型类所有的静态变量是共享的

反射

获取Class对象的方式

  • Class clazz = TargetObject.class;
  • Class clazz = Class.forName("com.example.TargetObject");
  • TargetObject o = new TargetObject(); Class clazz = o.getClass();
  • Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.example.TargetObject");

代理

使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

静态代理

在编译时就已经完成;不灵活;需要实现接口;

动态代理

JDK

  1. 定义一个接口及其实现类;
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
  4. Jdk代理效率比Cglib更高;

CGLIB

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;需要cglib jar包;
  3. 通过 Enhancer 类的 create()创建代理类;
  4. 是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final的类和方法;

注解

可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。本质上是一个继承了Annotation的特殊接口。

注解的解析方法

  • 编译期直接扫描 @Override
  • 运行期通过反射处理 @Value

序列化

将数据结构或对象转换成二进制字节流的过程。

  • 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中,常见序列号协议:Hessian、Kryo、Protobuf、ProtoStuff(以上为二进制序列化协议)、JSON和XML(文本序列化协议)
  • transient只能修饰变量,其修饰的变量在反序列化后会被设置为类型的默认值,static修饰的变量也不会被序列化,但是通过类型可以拿到其值
  • JDK自带序列化的缺点
    • 不支持跨语言调用(只支持Java服务)
    • 性能差(序列化字节数组大)
    • 存在安全问题(构造恶意输入)
  • JDK序列号版本ID的作用:反序列化时检查ID是否一致,不一致会抛出InvalidClassException异常
  • Kryo:针对Java、ThreadLocal保证线程安全、序列化为Byte数组
  • ProtoBuf:自定义IDL文件并生成对应序列化代码
  • ProtoStuff:ProtoBuf升级版
  • Hessian:轻量级、跨语言、Dubbo RPC默认方式

语法糖

Java 中真正支持语法糖的是 Java 编译器而不是 JVM ,JVM 其实并不能识别语法糖,Java 语法糖要想被正确执行,需要先通过编译器进行解糖,也就是在程序编译阶段将其转换成 JVM 认识的基本语法。

  • for-each for循环+迭代器 若执行remove方法会抛出ConcurrentModificationExceeption异常
  • switch支持String hashCode()
  • 泛型 编译阶段会擦除泛型,以最顶级父类型替换并移出所有类型参数
  • 自动拆装箱 valueOf(num)方法和xxxValue()方法
  • 可变长参数 参数数组
  • 枚举 使用enum来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承
  • 内部类 编译完后会有两个.class文件
  • 断言
  • 数值字面量下划线
  • try-with-resources
  • Lambda表达式

Unsafe

提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用,依赖Native方法。

  • 内存操作 分配、调整、拷贝、清除内存
  • 内存屏障 禁止load、store指令重排序
  • 对象操作 通过地址直接在内存中操作对象
  • 数组操作 通过地址直接在内存中操作数组
  • CAS操作
  • 线程调度 park、unpark、monitorEnter、monitorExit、tryMonitorEnter方法
  • Class操作 类加载和静态变量的操作
  • 系统信息 获取系统指针大小和内存页大小