《Java特种兵》学习笔记

《Java特种兵》学习笔记java

1、功底

1.1 编译期优化

String a = "a" + "b" + 1;
String b = "ab1";
println(a == b);  // true 编译期优化
复制代码
String a = "a";
final String c = "a"; 
//a并非一个常量,而是局部变量,字节码加强技术就能够修改a的实际赋值
String b = a + "b";	
String d = c + "b";	//final

//1.编译器不会看方法内部作了什么,2.递归深度不可预测
//3.返回的是对常量引用的拷贝,不是final的,能够修改
String e = getA() + "b"; 
String compare = "ab";
println(b == compare);	//fasle
println(d == compare);	//true
println(e == compare);	//fasle

private final static String getA() {
	return "a";
}
复制代码
//String b = a + "b" 的实际编译效果
StringBuilder temp = new StringBuilder();
String b = temp.append(a).append("b");
复制代码

1.2 Intern

public native String intern();
与string pool关联,加锁寻找string字符串,用equals方法判断是不是目标字符串
Jdk1.6: string pool在 Perm Gen中
Jdk1.7:string pool在堆中mysql

public static void test3() {
    String a = "a";
    String b = a + "b";	//a是变量,new StringBuilder().append(a).append("b");
    String c = "ab";	//在string pool中新建ab字符串
    String d = new String(b);//新对象
    println(b == c);	//F
    println(c == d);	//F
    println(c == d.intern());	//True:intern:寻找string pool中的ab,返回地址,没有则建立后返回地址
    println(b.intern() == d.intern());	//True:intern:寻找string pool中的ab,返回地址,没有则建立后返回地址
}
复制代码

老年代是否能够独自清理?而新生代不清理?web

1.3 StringBuilder

StringBuilder stringBuilder = new StringBuilder();
for (...) {
    //最坏的状况是它所占用的内存空间接近Old区域1/3时发生扩容,致使OOM
    stringBuilder.append(string);	
}
复制代码

stringBuilder在append扩容的时候,取max(2倍,count+string.length),因此在小字符串append大字符串时,扩容的空间刚刚足够,而大字符串append小字符串时,就会多出大量的内存空间算法

1.4 大量断定是|否操做

举例:java字节码中的类修饰符,如下都是十六进制,只取后四位显示spring

public:0001
static:0100
final:1000
复制代码

对数字取&操做,不为0时就是true
若判断 public static final, 先取或 public_static_final=public|static|final;判断(value& public_static_final)== public_static_finalsql

1.5 数据cache

类型 Cache范围
Integer -128 ~127
Short -128 ~127
Long -128 ~127
Float double
Byte 256个值

2、计算机工做原理

2.1 栈

存储局部变量中的基本数据类型,新建对象时,储存对象引用,而对象是在堆中建立
Jvm发出指令请求,OS完成具体计算,jvm自身没法作计算数据库

2.2 Cache line

一般以连续64位字节为单位进行cache的
如数组获取,当取二维数组a[0][0]时,cache line操做一般会将一些临近的数组元素cache到CPU缓存中,故而连续访问a[0][0],a[0][1]…时,这些连续的数值只要cache一次数组

2.3 缓存一致性协议

同一份数据cache在多个cpu中时,要求数据读写一致,多个CPU之间要遵循缓存共享的一致性协议
CPU读写数据时都会广播,其余CPU监听,并保存数据一致性缓存

2.4 内存

全部程序中使用的地址都是虚拟地址(逻辑地址),在不一样的进程中能够重复
物理地址:每一个进程有一段内存区域,起始地址+逻辑地址=物理地址
OS预先给jvm分配-xms大小的内存空间,而不是当即分配一个-xmx大小的空间,许多空间是真正使用时才分配的(启动java时,-xmx设置比物理内存大均可以)安全

2.5 磁盘

每秒读取的次数IOPS越大越好
顺序读写,减小定位延迟
Java中的日志读写工具,会将日志替换为buffer的append方式,将日志写入一个缓冲区,由专门的程序实现写操做,或者只在缓冲区已满的时候写入磁盘,尽可能一次写入多条数据,顺序IO,减小硬盘寻道寻址

3、JVM

JVM内存管理模型
Old区域更大一些
静态数据存放于方法区
String对象的intern方法将string对象拷贝到常量池

3.1 类字节码

类常量池
方法:编译时会自动生成构造方法字节码
略过先

3.2 Class字节码加载器

  1. 继承关系
    ClassLoader.loadClass(“类名”)时,会先从当前ClassLoader查找该类是否已经加载,而后逐步往父类查找类,最后由父类往子类加载类,最后ClassNotFoundException
    父类先加载类 BootStrapClassLoader -> ExtClassLoader -> AppClassLoader -> 自定义ClassLoader
  2. BootStrapClassLoader
    加载java自带的核心类,如java.Lang.*( Object, Class, Number, Thread, System, Throwable…),由jvm内核实现,不能被替换掉
  3. ExtClassLoader
    加载 jre/lib/ext目录下的jar包
    • 扩展
      jre
      C:\Program Files\Java\jre : 用户运行java程序的jre环境,为jvm用
      Jdk
      另外安装的jdk则是开发程序时须要用到的:
      jdk\lib: 包括java开发环境的jar包
      jdk\jre: 开发环境下运行的是 jdk 下的 jre
      jdk\jre\lib: 开发环境中,运行时须要的jar包,如导入的外部jar包
  4. AppClassLoader
    加载classPath下面的内容
  5. 自定义ClassLoader
    加载class,jar文件,甚至其余文件,加载位置可本地,也可远程
    自定义的ClassLoader能够识别到parentClassLoader加载的类,而其余的ClassLoader加载的类须要从新拼接出classpath做为参数动态编译
    若未指定parentClassLoader,则parentClassLoader默认为调用者类对应的ClassLoader;初始化时能够设置parentClassLoader为null
  6. 启动加载
    启动时,只加载jvm核心库(如BootStrapClassLoader)和main方法相关类

3.3 class加载过程

全部类在使用前都必须被加载和初始化,初始化过程由<clinit>方法确保线程安全,若多个线程同时尝试获取该类,则必须等到static块执行完成

  1. 读取文件(ClassNotFoundException)
    加载.class文件到方法区内部(包含全部的class和static变量,都是程序中惟一的元素) 先从父classloader加载,找不到就子加载器加载,最后抛出异常
    (BootStrapClassLoader -> ExtClassLoader -> AppClassLoader ->自定义ClassLoader->classnotfoundexception)
  2. 连接(NoClassDefFoundError)
    解析校验字节码,不符合规范就抛出NoClassDefFoundError
    为class对象分配内存
  3. 初始化
    调用class对象构造方法,静态变量,static块赋值
    初始化顺序
    static块 -> 代码块 -> 构造方法
class Parent {
    public Parent() {
	System.out.println("parent constructor init....");	//4
    }
    static {
	System.out.println("parent static block init....");	//1
    }
    {
	System.out.println("parent normal block call....");	//3
    }
}

class Child extends Parent {
    static {
	System.out.println("child static block call....");	//2
    }
    {
	System.out.println("child block call....");		//5
    }
    public Child() {
	System.out.println("child constructor call....");	//6
    }
}
public class LoadObjectStepDemo {
    public static void main(String[] args) {
	new Child();
    }
}
复制代码
parent static block init....
child static block call....
parent normal block call....
parent constructor init....
child block call....
child constructor call....
复制代码

错误初始化实例ExceptionInInitializerError

class B {
    // 加载类时先调用static块
    private final static B instance = new B();
    public static B getInstance() {
	return instance;
    }
    public B() {
	instance.test(); // new B()引用instance实例,但又发现这个类未加载完成,instance为NULL
    }
    public void test() {
	System.out.println("test");
    }
}
复制代码

3.4 class其余知识点

  1. 容器跨应用访问
    在web容器中使用了不一样的ClassLoader来加载不一样的delopy(不一样应用),但能够跨classLoader互相访问信息
  2. ClassLoader一个类只加载一个
    同一个ClassLoader一个类只会加载一个,同一个类可能会被不一样ClassLoader加载,在单例模式时应该考虑这个问题
  3. Full GC释放class
    Jvm作fullGC时,只有当相应的ClassLoader下全部的Class都没有实例引用时,能够释放ClassLoader及其下全部class
  4. ClassLoader加载
    ClassLoader自己就是class,在没有ClassLoader时,由jvm内核加载
  5. Class加载与父类
    先加载父类,先初始化父类static方法
  6. JIT运行时优化
    逐步优化,会将优化后的代码存放在codeCache中
    -XX:ReservedCodeCacheSize : 修改codeCache大小,64bit server java7默认48M
    -XX:+UseCodeCacheFlushing: 清理codeCache
    -XX:CICompilerCount : 最大并行编译数,越大提升编译速度
  7. 同名类加载冲突
    同名类出如今不一样jar包中,可使用instance.getClass().getResource("").getPath();得到class来源jar包
  8. 根引用
    GC时的根引用是本地变量引用,操做数栈引用,PC寄存器,本地方法栈引用,静态引用等
    即程序运行时栈中的引用+静态引用列表
  9. 引发Full GC
    1. Old区域满 || 小于平均晋升空间大小
    2. Perm区域满:class string
    3. System.gc()
    4. Dump内存

3.5 回收算法

见jvm笔记

3.6 经常使用GC参数

  1. 跟 Java 堆大小相关的 JVM 内存参数

    参数 含义
    -Xms 设置 Java 堆的初始化大小
    -Xmx 设置最大的 Java 堆大小
    -Xss 设置Java线程堆栈大小
    -Xmn 设置新生代空间大小
  2. 关于打印垃圾收集器详情的 JVM 参数

    参数 含义
    -verbose:gc 记录 GC 运行以及运行时间,通常用来查看 GC 是不是应用的瓶颈
    -XX:+PrintGCDetails 记录 GC 运行时的详细数据信息,包括新生成对象的占用内存大小以及耗费时间等
    -XX:-PrintGCTimeStamps 打印垃圾收集的时间戳
  3. 设置 Java 垃圾收集器行为的 JVM 参数

    参数 含义
    -XX:+UseParallelGC 使用并行垃圾收集
    -XX:-UseConcMarkSweepGC 使用并发标志扫描收集
    -XX:-UseSerialGC 使用串行垃圾收集
  4. JVM调试参数,用于远程调试

-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
复制代码
  1. 关于类路径方面的 JVM 参数
    -Xbootclasspath用来指定须要加载,但不想经过校验的类路径。JVM 会对全部的类在加载前进行校验并为每一个类经过一个int数值来应用。这个是保证 JVM 稳定的必要过程,但比较耗时,若是但愿跳过这个过程,就把类经过这个参数来指定。
  2. 用于修改 Perm Gen 大小的 JVM 参数
    下面的这三个参数主要用来解决 JVM 错误: java.lang.OutOfMemoryError:Perm Gen Space.
    -XX:PermSize and -XX:MaxPermSize
    -XX:NewRatio=2  Ratio of new/old generation sizes.
    -XX:MaxPermSize=64m     Size of the Permanent Generation.
    复制代码
  3. 用来跟踪类加载和卸载的信息
    -XX:+TraceClassLoading-XX:+TraceClassUnloading 用来打印类被加载和卸载的过程信息,这个用来诊断应用的内存泄漏问题很是有用。
  4. JVM switches related to logging
    -XX:+PrintCompilation: prints out the name of each Java method Hotspot decides to JIT compile.
  5. 用于调试目的的 JVM 开关参数
    参数 含义
    -XX:HeapDumpPath=./java_pid.hprof Path to directory or file name for heap dump.
    -XX:-PrintConcurrentLocks Print java.util.concurrent locks in Ctrl-Break thread dump.
    -XX:-PrintCommandLineFlags Print flags that appeared on the command line.

3.7 Java对象内存结构

Java对象将以8字节对齐在内存中,不足则补齐
静态引用所占的空间一般不计算到对象空间自己的空间上,它的引用在方法区

对象内存结构

//32bit
class A{
   byte b1;
}
复制代码

8字节头部+1字节b1
要对齐,故16字节

  1. 继承关系的对象属性排布
    在内部结构中,父类的属性依然要被分配到相应的子类对象中,这样才能在程序中经过父类访问它的属性 父类的属性不能和子类混用,它们必须单独排布在一个地方
class A{byte b;}
class B extends A{byte b;}
class C extends B{byte b;}
复制代码

对象结构图

  1. 数组占用空间实例(32bit)
int size = 100 * 1024 * 1024;
//1
int[] values = new int[size];
for (int i = 0; i < size; i++) {
    values[i] = i;
}
//2
Integer[] valueIntegers = new Integer[size];
for (int i = 0; i < size; i++) {
    valueIntegers[i] = i;  // 自动装箱了 new Integer(i)
}
复制代码
  • 对1:
    int[]数组占用空间
    8字节头部+4字节描述数组长度+4X100x1024X1024=400MB(int 值4字节)+padding4字节 ≈ 400MB
  • 对2:
    每一个Integer对象占8字节头部+4字节int值,又须要对齐,故16字节
    实例对象一共占用空间16 X 100x1024X1024=1600MB
    Integer[] 数组占用空间
    8字节头部+4字节描述数组长度+引用空间4X100x1024X1024=400MB(每一个引用4字节)+padding4字节 ≈ 400MB
    总空间1600+400=2000MB
  1. Int[2][100] PK int[100][2]
    维度 Int[2][100] int[100][2]
    第一维数组 对象头部 8 8
    第一维 数组长度描述符 4 4
    第一维 引用宽度 2X4=8 100X4=400
    第一维 Padding 4 4
    第一维 合计 24 416
    第二维 对象头部 8 8
    第二维 数组长度描述符 4 4
    第二维 引用宽度 100X4=400 2X4=8
    第二维 Padding 4 4
    第二维 合计 416 24
    总计 24+2X416=856 416+100X24=2816

3.8 常见OOM

  1. java.lang.OutOfMemoryError: Java heap space
public static void main(String[] args) {
	List<String> list = new ArrayList<String>();
	while (true) {
	    list.add("内存溢出了");
	}
}
复制代码
  • 解决方法
    1. 对可能存活较久的大对象:object = null
    2. 代码提速:代码运行速度提高,缩短对象生命周期
    3. 修改堆大小
  1. java.lang.OutOfMemoryError: PermGen space
    1. jdk1.6 PermGen空间
    int i = 0;
    while (true) {
        ("在JDK 1.6下运行,在JDK 1.7中运行的结果将彻底不一样 "
        	+ "string常量在jdk1.7以上就再也不存放在PermGen中" + i++).intern();
    }
    复制代码
    1. 动态加载class,如使用字节码加强技术,CGlib一直建立加载class
      若须要动态加载类,动态编译java代码,最好是有单独的classLoader,当class被替换时,原来的class能够被当作垃圾释放掉
      释放class的条件是classLoader下的class都没有活着的对象
  2. DirectBuffer OOM
    java.lang.OutOfMemoryError: Direct buffer memory
    DirectBuffer区域不是java的heap,而是C heap的一部分,一般FULL GC时回收
    // -XX:MaxDirectMemorySize=26m
    public static void main(String[] args) {
    	ByteBuffer.allocateDirect(27 * 1024 * 1024);
    }
    复制代码
  3. StackOverflowError
    public void testStackOver() {
    	testStackOver();
    }
    复制代码
    注意递归层数,将递归调用次数做为参数,到达必定次数后结束递归
    子类和父类相互间调用方法

4、Java通讯

4.1 字符编码转换

若字符编码和解码的方式不一致,极可能损坏源字符,形成没法正确读取
好比变长的UTF-8编码能够由3个字节组成一个汉字,而GBK由2个字节组成汉字,GBK按2个单位 长度读取时,不在其编码范围内的则用?或其余字符替代,这就修改了原来的字符串了

4.2 流继承嵌套

  1. 继承
    有部分流的具体实现中会继承FilterInputStream, FilterInputStream提供InputStream的默认实现.则流只覆写特定的方法便可
  2. 嵌套
    能够对同一个stream使用多个stream实现类来嵌套,注意当其中一个实现stream close时,底层基础stream关闭了,而其余stream没有调用其close方法,可能会出错
    好比多个BufferedInputStream嵌套一个流,关闭其中一个BufferedInputStream后,底层input关闭,但剩余的其余BufferedInputStream没有执行close方法,其buffer的数据不会刷新到磁盘上,形成数据问题

4.3 I/O与内存

  • 文件读入与读出  
    文件读入与读出

 不关闭流会有什么问题?内存溢出吧

  • 阻塞与非阻塞 同步与异步
    同步和异步关注的是消息通讯机制,重点在于被调用的结果
    阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态,重点在于程序自身
    1. 同步
      是在发出一个调用时,在没有获得结果以前,该调用就不返回。可是一旦调用返回,就获得返回值了, 由调用者主动等待这个调用的结果
    2. 异步
      调用在发出以后,这个调用就直接返回了,因此没有返回结果。在调用发出后,被调用者经过状态、通知来通知调用者,或经过回调函数处理这个调用。
    3. 阻塞
      阻塞调用是指调用结果返回以前,当前线程会被挂起。调用线程只有在获得结果以后才会返回。
    4. 非阻塞
      非阻塞调用指在不能马上获得结果以前,该调用不会阻塞当前线程,当前线程继续执行

5、数据库

5.1 数据库基本原理

数据库基本结构层次

  1. 数据块/页
    块,数据存储单位:4KB,8KB,16KB, 块也是虚拟出来的,为数据库规范,可由数据库识别
    每一个块可存储多条数据,块都有标识符(不惟一,只有和更高层次的标识符组合才惟一)
    块内部每条数据一般都有本身的行号
    数据性能瓶颈在于磁盘IOPS上,读取1KB和1MB的连续数据,耗时相差无几
    慢缘由:每一个磁盘IO请求都是串行化,每一个IO请求须要磁道寻址,故而慢
    优化:能够将多个块的数据连续放在一块儿组成一组数据
    当连续块中有部分数据经常使用,部分不经常使用时,不经常使用的数据会被写回磁盘,则程序再次加载这些块时,只是加载未加载的部分,这就形成不连续加载,成为随机读
  • 数据字典
    一般以表的方式储存,记录数据库信息,包括每一个块在文件系统中的偏移量
    数据量少,更新少,一般加载到内存中提供访问
    数据库一般会以LRU的算法来管理块,让某些访问多的块留在内存,或让访问少的块从内存释放掉
  1. 分组extends
    将多个块组成一个分组,理论上是连续储存的
  2. segments
    多个extends组成一个segments,一个表对象能够划分到多个segments,以实现分区表独立管理
  3. 修改表
    添加一个表格字段
    某些数据库会事先在写入每一个块时预留少部分空间,以便添加字段时使用
    当预留空间的字段不够用时,会将一行数据的某个部分写入到另外一个块中,通常不在连续块上则须要屡次IO读取数据了
    某些开源数据库是没有数据字典概念,大部分描述信息存放在以表为单位的文件头部中,当表结构修改时,一般会建立一个新表,并将原表中的数据所有拷贝到新表中,为保持数据一致性,拷贝时一般会锁表
    对于大数据储存,一般采用分库分表策略
  4. 删除表
    伪删除:删除某些数据库中的表,为了防止误删除,会在默认的处理删除动做时,只是修改一个名字并将修改后的表名与原来的表名作一个简单映射,同时也改变其元数据的状态
  5. SQL执行
    解析得到关联结构信息,执行计划等
    • 软解析
      缓存SQL解析过程,一样SQL,只有参数不一样,能够直接拿解析好的结果直接运行,提升效率
    • 硬解析
      拼接处参数,而非预编译,SQL不复用,低效
    • SQL储存空间
      SQL存储空间有限,使用类LRU算法管理内存区域
    • 执行计划
      单表是否走索引,多表时是jion仍是union,排序方法选择,分钟策略肯定等
    select * from table small,table big where small.id =big.id and small.type=’1’;
    复制代码
    small.id =big.id:用小表作驱动表,更快
    对于过滤性好的条件,能够查看执行计划,让该条件先执行(Oracle和mysql执行顺序不一致)
  6. 加锁
    读写表时,一般会在数据字典上加锁,保证表结构不会被修改,通常相似于读写锁,不过写时却也能够读,可能会形成读脏数据的问题,解决方法是增长时间戳或版本号作标识
    对于某些数据库,锁住的行可能并不只仅是最终where条件筛选出的行,多是where条件中有索引字段筛选出的全部行
    update table setwhere a=xx and b=xxxx
    复制代码
    若a有索引,而b没有,则可能会锁住全部a条件筛选出的数据,锁的范围更大 有些数据库为了加快加锁的速度,会以”块”为单位加锁,块头部有内部的行信息,每一行都有1bit来标识是否已经加锁
    对应大量的数据修改,可能因锁而致使串行速度问题,能够利用cas机制代替锁
  7. 提交
    Commit操做时,会将日志写入到磁盘
    数据库会在内存分配一个日志缓冲区,隔必定时间/日志达到必定容量就存盘
    日志的存在能够为数据库提供回滚功能
    另外,也有多是数据库记录每个块的相应版本到某个空间,只要空间够用,就不会将相应版本的数据清空,回滚时直接查询对应版本号/时间点的数据

5.2 索引原理

索引方法:B+树,也有hash
索引是与数据分开储存的
索引的目的是快速定位数据,索引经过某种标识符与原表关联起来(也叫回表),标识符能够是主键,或者创建索引的字段值,也能够是数据物理位置(oracle用rowid作标识符:表空间编号+文件编号+对象编号+块号+块内行号)
除非查询的信息所有在索引上,不然索引至少会走两次操做(索引+回表),因此小规模数据索引表现很差
索引更新时,索引先删除再插入,其中删除是伪删除,索引空间不会变小
从新编译索引才会真正删除索引

  • 索引管理
    树状管理索引,数据量越大,层数越多,索引间有序,底层全部的叶子块经过双向链表互相关联

    三层索引管理
    SQL走索引时,会从树根开始查找,索引块一般也cache在缓存中(若索引块数据量过大,只缓存高层索引块),可能就不须要I/O,另外,因为索引有序,能够经过二分法快速查找,若查完索引后仍须要查询非索引字段,此时回表,不然直接走索引内部数据
    SQL某些统计操做能够经过索引完成(好比count),只统计叶子块,就能够获得全表信息
    Min,max操做,有where条件,则正常遍历索引,没有条件,直接查第一个和最后一个块
    like前缀索引能够经过范围查找,然后缀索引就多是全表扫描

  • 字段有索引并不必定要走索引
    in检索时,一般解析为OR方式完成,检索在多个离散块中进行,每一个条件都须要单独查找,in条件多就走全表扫描(将in改成exists?)
    再如,某些状态字段做为条件,可能也不会走索引

  • 位图索引bitMap
    但字段只有几种值时,bitmap能够实现高效的统计.
    位图索引结构相似于B+树,储存时以字段值类型分开,在每种值的空间中单独储存每一个数据行是否有这个值,1有,0没有
    如字段类型为A,索引中大体能够储存为101010100,表示第1 3 5 7行有A值
    缺点:锁粒度太大
    若修改状态1为2,则会锁住全部值为1和2的行,commit或rollback才会释放

5.3 数据库主从原理

主库更新数据,再同步到从库

  1. 同步方式:逻辑模式
    主库执行SQL,到从库上执行相同SQL
    主库能够优化SQL,如生成基于主键更新的SQL,从库执行会简单一些
    从库基本只有一条线程写,其他读,压力小,更高效
    缺点:SQL较多,从库写速度更不上主库,致使不一致
  2. 物理模式
    基于修改的数据块作复制
    提取比从库版本号更高的数据块复制到从库,不用SQL解析,锁,调度等问题,更加高效

5.4 执行计划

小表作驱动表,大表走索引

  • Oracle
  1. explain plan for sql,select * from table(DBMS_XPLAN.DISPLAY)
  2. SET AUTOTRACE ON EXPLAIN , SET AUTOTRACE ON, SET AUTOTRACE TRACEONLY STAT
    eg:
    explain plan for select * from tableAA a where a.tt=:vv1; :开头表明占位符
    INDEX UNIQUE SACN:走惟一索引
    INDEX RANCE SCAN:普通索引是走范围索引的,由于能够重复
    若是Oracle执行计划不对或不理想,能够经过Hint方式告诉Oracle应该如何走索引
  • Mysql
    explan<sql>

  • 执行计划
    在JOIN表以前,须要被JOIN的表能先过滤掉大部分数据
    小表驱动大表,嵌套循环时,有序会快一些
    多个结果集JOIN,可使用Hash Join的方式,当表太大,Hash Join key太多,内存会放不下,用普通嵌套方式作

  • 函数转换
    函数转换一般不属于执行计划的范畴
    select fun1(a),fun(b)… from table
    执行路径是看不到函数的处理

5.5 模型结构优化

表示树状结构,如省,市,区县,镇,乡,村,门牌,多级结构,可使用经常使用的递归方式,只保留父级id 也能够按序保留下全部的父级id,避免递归,索引也方便

6、源码基础

6.1 源码调用路径

Thread.currentThread().getStackTrace();
复制代码
new Exception().printStackTrace();
复制代码

6.2 反射

Boolean类型的属性注入

Boolean类型属性名称仍是不要加is前缀

class Node {
    private boolean good;
    public boolean isGood() {
	return good;
    }
    public void setGood(boolean good) {
	this.good = good;
    }
}
复制代码

当使用PropertyDescriptor去获取属性读写方法时,boolean类型默认都会加上is前缀
若属性名为isGood,则默认调用isIsGood方法,这时类中isGood方法就无论用了

反射与范型擦除

List<Integer> list = new ArrayList<Integer>();
Method method = ArrayList.class.getDeclaredMethod("add",Object.class);
method.invoke(list, 7);
method.invoke(list, "dfsd");
method.invoke(list, new A("aa"));
System.out.println(list);
复制代码
  1. 泛型实参只会在类、字段及方法参数内保存其签名(便可以得到实际的参数类型),没法经过反射动态获取泛型实例的具体实参,好比入参为范型,方法内部就会擦除为object。
  2. 须要获取泛型实参的状况下,方法有三: ①经过传递实参类型
    ②明肯定义泛型实参类型,经过反射获取签名
    ③经过匿名类捕获相关的泛型实参

AOP

面向切面,动态代理和字节码加强技术

  1. 若是目标对象实现了接口,默认状况下会采用JDK的动态代理实现AOP
  2. 若是目标对象实现了接口,能够强制使用CGLIB实现AOP
  3. 若是目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
  • 动态代理
    动态代理在生成动态字节码时,并非经过实现类建立子类的方式,而是经过类所拥有的接口列表来完成,即构造出的类和实现类是没有关系的
    在构造动态代理时,类必须基于接口实现动态代理,在最终得到动态代理的的实例引用时,也只能用接口来获取
    本质上仍是将实现类做为引用传入到Handler中,仍是会调用实现类的方法,只是包装了一层
    实现接口的方法,方法内再经过引用调用实现类的方法
    若是在内部方法再调用内部方法,那么在第二层内部方法中是没法完成AOP切入的,那已是直接调用了
  • 字节码加强
    若是在内部方法再调用内部方法,能够一直完成AOP切入,这是由于字节码加强是构建子类或者直接修改类的字节码,如果子类,因为子类覆写父类方法,就一直调用被修改的子类方法,能够一直AOP;如果直接修改字节码,那就是方法修改了

Annotation

注解根本上仍是依靠反射实现的

7、JDBC

7.1 JDBC注册

驱动只需加载一次,不须要反复加载,也不须要本身new,注册的driver以列表的形式保存,驱动加载也与classloader有关,全部也能够在不一样的classloader加载同一驱动的不一样版本 当程序中存在多个驱动时,DriverManager经过遍历的方式查找其classloader中全部的driver,与jdbcurl匹配,匹配上就返回

相关文章
相关标签/搜索