代码风格
- 无论如何都不能让魔法值直接出现在代码中,应使用枚举
- 常量命名用大写,bool型变量不要加is前缀
- 单行字符数不超过120个,单个方法的总行数不超过80
- 避免使用取反逻辑运算符
JVM
字节码对应的操作码助记符:
- 将局部变量加载到操作栈中:
- ILOAD(Int类型变量)
- ALOAD(对象引用)
- 从操作栈存储到局部变量表:ISTORE,ASTORE
- 将常量加载到操作栈顶:
- ICONST(-1~5)
- BIPUSH(-128~127)
- SIPUSH(-32768~32767)
- LDC(-2147483648~2147483647或者字符串)
- 运算指令:IADD,IMUL
- 类型转换:I2L,D2F
- 对象相关:
- 创建对象:NEW,NEWARRAY
- 访问对象属性: GETFIELD,PUTFIELD,GETSTATIC
- 检查实例类型:INSTANCEOF,CHECKCAST
- 操作栈
- 出栈:POP,POP2;复制栈顶元素并压入栈:DUP
- 方法调用与返回
- 调用对象的实例方法:INVOKEVIRTUAL
- 调用实例初始化方法、私有方法、父类方法:INVOKESPECIAL
- 调用类静态方法:INVOKESTATIC
- 返回VOID类型:RETURN
- 同步指令:
- ACC_SYNCHRONIZED标志同步方法
- MONITORENTER,MONITOREXIT
字节码通过类加载过程加载到JVM环境中后,主流的JVM默认执行方式为: JIT编译执行与解释执行 混合
JIT能动态识别高频的方法调用,即热点代码,将热点代码转换成机器码直接交给CPU执行
类的加载过程
溯源委派加载模型(双亲委派模型):
类加载器的三层结构:
1.BootStrap类加载器,由操作系统本地代码实现,负责装载最核心的JAVA类:Object,System,String
2.平台类加载器,负责加载扩展的系统类,如XML、加密相关的功能类
3.应用类加载器,加载用户定义的类
双亲委派模型即低层类加载器询问高层类加载器是否可以自行加载当前类、、
自定义类加载器的步骤:继承ClassLoader–>重写findClass()方法–>调用defineClass()方法
java内存布局:
本地方法栈、程序计数器、堆区、虚拟机栈、方法区(常量池、类信息、方法信息)
堆区(heap):新生代、老年代;新生代:Eden、S0、S1
垃圾回收算法:
- 标记-清除算法(会产生大量的空间碎片)
- 标记-整理算法
- mark-copy算法,主流的YGC算法
垃圾回收器:Serial,CMS,G1
异常和日志
- 异常分类(都是Throwable的子类):
- Error
- Exception
- checked异常
- unchecked异常,运行时异常,都继承自RuntimeException
- finally中的语句在return表达式运行后执行。
- lock方法在try之前,避免加锁失败导致finally调用unlock抛出异常
- 代码规约推荐日志文件至少保存15天
- 打印日志时先要判断日志级别,打印的内容要使用占位符
- 记录异常时一定要输出异常堆栈
- 日志框架:日志库(log4j,log-jdk,logback),日志适配器,日志门面(slf4j,commons-logging)
数据结构
- linkedList的本质是双向链表
- HashMap是线程不安全的,ConcurrentHashMap是线程安全的,推荐使用ConcurrentHashMap。
- TreeMap是Key有序的Map类集合
- ArrayList的默认大小是10,HashMap的默认大小是16,在创建以上集合时要明确集合的大小,避免被动扩容和数组赋值的额外开销
- 数组转列表Arrays.asList返回的是一个Arrays类里的一个内部类,不能进行add/remove/clear操作,同时体现了适配器模式,list的操作本质上还是对数组的操作
- 使用集合的tiArray(T[] array)方法时,要传入类型完全一样的数组,并且它的容量大小是list.size()
- 非泛型集合可以赋值给任何泛型限制的集合
- <? extends T>可以赋值给任何T或T的子类的集合,取出来的类型会被强制转换为T,除null以外,任何元素都不能添加进<? extends T>的集合内
- <? super T>可以赋值给任何T或T的父类集合,取出来的类型会被强转为Object,可以向该集合添加T以及T的子类,因为都可以强转为T的父类
- 判断两个对象是否相等时,使用Objects的equal方法,放置NPE问题
- Map类集合是与Collection集合平级的一个接口
- AVL树是一种平衡二叉树,增加和删除节点后通过树形旋转重新达到平衡
- 红黑树,保证从根节点到页尾的最长路径不超过最短路径的2倍,有红必有黑,红红不相连
- 频繁的插入和删除,红黑树更为适合,面对低频修改,大量查询,AVL树更适合
- HashMap使用hashCode和equals实现去重,TreeMap使用Comparable或者Comparator实现Key的去重
- TreeMap的插入操作先按照二叉查找树的特性进行操作,后续重新着色和旋转
- 多线程操作需要添加同步互斥操作时,使用ConcurrentHashMap或者Collections.synchronizedMap(TreeMap)包装成同步集合
- Integer的hashcode就是自身的值
并发与多线程
- 创建线程的三种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- Callable与Runnable接口的不同:
- Callable接口实现后可以通过call()获得线程的返回值
- call()可以抛出异常,而Runnable只有通过setDefaultUncaughtExceptionHandler()的方式才能在主线程中捕获子线程异常
- 线程安全的核心理念就是:要么只读、要么加锁
- CountDownLatch、Semaphore、CyclicBarrier逐步淘汰了使用Object的wait()和notify进行同步的方式
- 线程对共享变量的操作都是在本地内存区域内操作副本,执行结束后再同步到堆内存中
- 线程工厂必须对线程池创建的线程进行明确标识
- 引用类型:强引用、软引用、弱引用、虚引用
- 多线程共享时有非常高的概率发生错误,推荐使用ThreadLocal使每个线程单独拥有这个对象
- ThreadLocal的主要问题是会产生脏数据和内存泄漏
- 每次使用完ThreadLocal后,必须使用remove()方法清理
单元测试
- 宏观上,单元测试要符合AIR原则,即自动化、独立性、可重复,微观上,代码层面要符合BCDE原则
- 单元测试中不允许使用System.out来进行人工验证,而必须使用断言验证
- 单元测试不是集成测试,不负责检查跨类或者跨系统的交互逻辑
- JUnit不建议使用超过3级的嵌套用例
- AssertJ使用java8后的流式断言简化了代码
代码规约
- 在共识的规约面前,我们应该放下成见,牺牲小我,成就大我
- 要让所有的人觉得规约的制定是一个共同参与、共同讨论的过程,规约是共同遵守的约定
- 数据库存储一对多的实现方式推荐使用JSON方式
- 学习方法论: 记忆-理解-表达-融汇贯通
结语
本章的所有内容只是对《码出高效》这本书的一个属于我能理解的范围内,我觉得有收获的内容的一个摘抄,对于我本人而言,还有很多内容由于超出了我目前的水平,所以目前不能很好的理解并记录,这本书范围很广,很多生产环境下开发工程师会遇到的问题都做出了解释与描述,并从源代码、低层原理的角度去分析解决方法,不过不知道孤尽大神是拥有广大理工男的通病–不擅长文字表达或者其他什么原因,或者就是因为我水平还不行,很多地方我其实也是看得一头雾水,还有很多地方咬文嚼字才最终获得知识的内容。不过整体来说,这本书给我的收获是巨大的,一是告诉了我真正生产环境中可能会遇到的问题以及解决办法,二是从底层源代码的角度为我解释了一些JAVA中常用的代码知识,进一步夯实了我的基础,也告诉了我下一步的学习方向。最近准备重读码农翻身和深入理解JAVA虚拟机,有的书就是这样,不同的水平能从文字里读到的东西也是不同的,慢慢的技术书对于我而言就不再是半懂非懂的天书了,而是能获得不同感悟的宝贝。女朋友也和我和好了,现在真的很开心。hiahiahia~