《码出高效:java开发手册》读书笔记

  1. 二进制的原码、补码、反码、移码

原码:就是早期用来表示数字的一种方式: 一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码

反码:正数的反码就是原码,负数的反码等于原码除符号位以外所有的位取反

补码:正数的补码与原码相同,负数的补码为 其原码除符号位外所有位取反(得到反码了),然后最低位加1.

移码:(又叫增码)是符号位取反的补码

正数的反码和补码都与原码相同。

    负数的反码为对该数的原码除符号位外各位取反。

    负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1  

各自的优缺点:

    原码最好理解了,但是加减法不够方便,还有两个零。。

    反码稍微困难一些,解决了加减法的问题,但还是有有个零

    补码理解困难,其他就没什么缺点了

  1. 浮点数

浮点数由符号位、有效数字、指数三部分组成。

说明:

0位用s表示。s=1表示负数,s=0表示正数。s即符号位(Sign)。

[1,7]位用E表示。称为指数。E即指数(Exponent

[8,31]位用M表示。称为尾数。M即尾数(Mantissa

E0,则尾数附加位为0。否则尾数附加位为1

一个Float浮点数的值为:Value = (-1)^s *1+M*2^(E-127)

浮点数值 = -1*2^(129-127)*(2^0+2^-2)

float2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字

  1. 乱码产生的原因

最开始的计算机都是国外人员使用,在进行编码的时候,只需要表示24个英文字母、一些符号就行,没有多少,所有使用28次方-1,128中字符就足够了,其中一位作为奇偶校验位。这就是ASCLL码,但是汉字那么多,仅仅靠ASCLL码肯定不行,必选增加,所有有了GB2312(看名字知道是国人用的)以及后来的GBK(国标扩展)还有后来的GB18030都是国人的自己的编码表,最后由国际组织统一发布了UniCode 就是现在常用的UTF-8UTF-16等,可以理解成一种压缩方式。

  1. TCP/IP协议

在程序发送数据的时候,应用层按照既定的协议打包数据,随后又传输层加上双方的端口号,由网络层加上双方的IP地址,又链路层加上双方的mac地址(根据ip路由表决定下一网络在哪,但是最终需要通过mac地址(外层包装)来决定传送给谁)并将数据拆分成数据帧,经过多个路由器和网关后,达到目标机器。简单的说就是按照“端口-IP地址-MAC地址”这样的路径进行数据的封装和发送,解包的时候反过来操作即可!

  1. TCP三次握手协议

为什么是三次?因为只用通过三次握手才能完全确认链接正常!

其中TCP报文SYN和ACK标志位很重要。SYN(Synchronize Sequence Number)用作建立链接时候同步信号,ACK(Acknowledgement)对于收到的数据进行确认,所收到的数据由确认序列表示!

如果只使用两次握手,可能会造成脏链接,因为TTL( Time To Live- IP包被路由器丢弃之前允许通过的最大网段数量,这个时间设置是为了防止信息在网络上无限期的存在)的存在时间可能超过TCP请求超时时间,如果两次握手可以建立链接。那么当超时请求链接A->B,B同意建立链接,返回数据B-A,链接建立!如果是三次握手,B会向A同意却请求链接,但是A的SYN_SEND状态错误,直接丢弃链接!

断开TCP链接需要4次握手协议,举个简单的例子,A和B都在吃饭(链接转态),两人预定一起去玩游戏,A已经吃完了,A->B说一会去shopping,B->A,我吃饭了就去。吃完了告诉你。B吃完了,B->A ,我吃完了,走吧!A->B好的走,有其他安排给我说,别放我鸽子!A等待一会,没有消息!链接断开!

  1. 连接池

数据库层面的请求应答时间必须在100ms以内,秒级的SQL查询通常存在巨大的性能提升空间,主要有一下几种方案;

    1. 建立高效且合适的索引
    2. 排查链接资源未显示关闭的情形
    3. 合理拆分多个表join的sql,若超过三个表则禁止join。同时最好保持关联字段数据类型一致,且应确保关联字段有索引
    4. 使用临时表
  1. 信息安全

Sql注入:防止方法:过滤特殊字符,禁止使用字符串拼接的SQL语句,严格使用参数绑定传入的SQL查询;合理使用数据库访问框架提供的防止SQL注入的机制,例如mybatis的中#{},不要使用${};

XSS=Cross-Site Scripte(和CSS冲突了,所有叫XSS),XSS是在正常用户请求的HTML页面中执行了黑客提供的恶意代码

实例 :http://xss.demo/self-xss.jsp?userName=张三<script>alert("张三")</script>

&errorMessage=XSS示例<script src=http://hacker.demo/xss-script.js/>

预防方法:XSS字段过滤

CSRF 跨站请求伪造(cross-site Rquest forgery),CSRF是黑客直接盗用用户浏览器中的登录信息,冒充用户去执行黑客指定的操作,实例:

https://net-bank.demo/transfer.do?targetAccount=12345samount=100

防止方法:利用浏览器的同源(如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的)限制,请求中验证cookie,人机交互(调用转账交易时候,验证短信验证)

7.1HTTPS和HTTP

HTTPS在交换密钥阶段使用非对称密钥加密方式,之后建立通信交换报文阶段则使用对称密钥加密方式。https示意图

 

https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

 

https的的请求过程:首先server端使用加密算法生成公匙和私匙,server用公匙去CA机构进行认证,获得数字证书。Client经过3次握手协议后,通过http报文协商使用那种加密算法,这时候获得Service的数字证书(带公匙),client去验证数字证书,正常则client在生成一个对称密匙,之后将url请求用对称密匙加密在用公匙加密,发送给sevice。Service使用私匙进行解密获得对称密匙,之后就对信息进行对称密匙加密进行传输。最后tcp4次挥手断开链接!

  1. Java内部类

分为:静态内部类、成员内部类、局部内部类、匿名内部类

编译后的class文件中名称!

在jdk中定义包内可见的静态内部类非常常见,这样做有很多好处

       作用域不会扩散到包外

       可以通过“外部类.内部类”的方法直接进行访问

       内部类可以访问外部类中的所有静态属性和方法

下面是ConcurrentHashMap中定义的Node静态内部类

  1. this 和 super

this和super都是在实例化阶段调用,所以不能在静态方法和静态代码块中使用this和super关键字(因为静态方法在使用前不用创建任何实例对象,当静态方法调用时,this所引用的对象根本没有产生。

在创建类对象时候,会先执行父类和子类中的静态代码块,然后在执行父类和子类的构造方法。且静态代码块只运行一次,第二次实例化时候不会运行。

  1. 类关系

继承(extends)(is-a)

实现(implements)(can-do)

组合(类是成员变量)(contains-a)

聚合(类是成员变量)(has-a)

依赖(import类)(use-a)

  1. 序列化

将数据对象转换成二进制流的过程称为对象的序列化,反之称为反序列化!

常见的序列化方式三种

11.1   java原生序列化,通过实现Serializable,同时建议是在SerializableUID值,避免每次序列在进行生产。

11.2  Hessian序列化

       是一种支持动态类型、跨语言、基于对象传输的网络协议。比java原生序列化强很多。Hessian是把复杂对象所用属性储存在一个Map中进行序列化。所以在父类、子类存在同名的成员变量的情况下,Hessian序列化时候,先序列化子类,然后序列化父类,因此反序列化结果导致子类同名成员变量被父类的值覆盖。

11.3  JSON序列化   

       JSON是一种轻量级的数据交换格式。JSON序列化就是将数据对象转换为JSON字符串,序列化过程中抛弃了类型信息,所有反序列化时候只有提供类型信息才能准确的反序列化。

Transient 关键字添加后会是本变量不会被序列化。

  1. 泛型

泛型的本质是参数化类型

约定成俗的表示方式有:

E表示Element,用于集合中的元素;T代表the Type of object,表示某个类。

K表示Key,V表示Value,用于键值对元素。

  1. 保证类型

在包装类型中,除了Float和Double之外(没法缓存,太多了)其他包装类型都用缓存。

  1. 命名规则

类名采用大驼峰(所用首字母大写)命名法,方法名称采用小驼峰(除了首字母之外其他字母都大写)

14.1常量指的是作用域范围内保持不变的量,用final修饰。

全局常量:public static final

类内常量:private static final

常量的定义可以使用Enum枚举表示:

14.2单个方法的长度,代码部分最好不要超过80行。

  1. JVM

Java代码一次编译,到处执行的关键在于jvm(java虚拟机),它能够将编译好的字节码class文件通过类加载器加载到jvm虚拟机,jvm会对class字节码解释执行(主流jvm是JIT执行和解释混合执行)经过jit或许解释后生成对应机器码可以被机器识别。不同的jvm可以生产对象不同机器的机器码,例如win的和linux的机器码。

JIT(java in time)是一种动态编译技术,可以理解为一直缓存技术,会将热点代码进行缓存,这样执行的更快。不用每次都执行!

也就是如下:

1.Java源文件—->编译器—->字节码文件

2.字节码文件—->Jvm—->机器码

任何程序都必须加载到内存才能和CPU进行交流,同样class文件必须加载到内存才能进行实例化,类加载器就是将class文件加载到内存中。加载类使用的parents Delegation Modle ,可以翻译成双亲委派模型,最好翻译溯源委派加载模型。意思是说jvm在对一个对象初始化的过程中,jvm会初始化继承树上还没有被初始化过的所有父类,并且会执行链路上所有未执行过得静态代码块、静态变量赋值语句等。Jvm的内存模型:

Jvm各个内存布局模块说明:

    1. Heap(堆区)

              堆区市OOM故障发生的主要地点。它储存着几乎所有的实例对象,当无节制的创建大量对象(读取大量数据到内存,实际上是以实例储存,相当于对象),就是超过堆区最大的储存空间就会报错!可以通过-Xms256m -Xmx1024m调整,分别代表最小堆容量和最大堆容量。生产环境中为了避免堆空间不断的扩容和收缩,JVM中Xms和Xmx设置成一致!

             堆中分新生代(分Eden区和Survivor区(s1 和 s2))和老年代。新创建的对象放到Eden区,当Eden区填满之后会触发Young Garbage Collection(YGC,垃圾回收),没有被引用的对象之间被清除,依然存货的对象放入Survivor区域。每次YGC之后将存货的对象放入Survivor区未使用的那个区域如s1,s2区域全部清空,内对象移动到s1并且每个对象的计数器加1,如果超过阀值直接移动到老年代。每个对象都有计数器,每次YGC时候加1,-XX:MaxTenuringThreshold参数可以配置计数器的阀值,如果阀值为1,则新生代直接进入老年代。老年代也接纳新生代无法存放的超大对象。YGC之后的对象Survivor区域放不下直接放到老年代,老年代也没法放下触发Full Garbage Collection,即FGC,仍然不能放下,直接造成OOM异常。如果想知道堆中出错的时候堆内存信息,可以设置-XX:+heapDumpOnOutOfMemoryError,让JVM遇到OOM时候打印输出信息!不同的JVM实现不同的回收机制,堆内存划分方式不一样。本书JDK11,JVM为HotSpot

    1. 元空间(Metaspace) ==方法区

在jdk8版本中,元空间的前身perm(永久代)已经淘汰,jdk7中也只有HotSpot JVM采用永久代,且大小固定,很难调优。主要存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息。如果动态加载过多的类也可能造成Perm的OOm,解决该问题可以设置参数

     -XX:PermSize=5M -XX:MaxPermSize=7M

由于perm存在问题,jdk已经移除,改为元空间。Perm区的String常量移动至堆内存,其他类元信息、静态属性等移动到元空间内。

    1. JVM Stack(虚拟机栈)

栈是一个先进后出的数据结构。线程私有。每个方法从开始调用到执行完成,就是栈帧从入栈到出栈的过程。StackOverFlowError表示请求的栈溢出,导致内存耗尽,主要出现在递归方法中!

              栈帧组成包括:局部变量表、操作栈、动态连接、方法返回地址等。

      1. 局部变量表

存放方法参数和局部变量的区域。操作指令STORE就是将操作栈中计算完成的局部变量写会局部变量表的储存空间。

      1. 操作栈

操作栈是一个初始化转态为空的桶式结构栈。在方法执行过程中会有各种指令往栈中写入和提前信息。

      1. 动态连接

每个栈帧都都包含当前方法的引用。

      1. 方法返回地址

正常返回:执行到任何方法的字节码指令。如:RETURN,IRETURN等。

异常返回:无论何种方式都将返回当前背调用的位置。

    1. Native Method Stacks(本地方法栈

线程私有。本地方法栈主外,而JVM栈主内。

本地方法栈是为本地操作系统(Native)方法。这部分不再被JVM控制。本地方法可以通过(JNI)来访问虚拟机的运行时数据区,甚至是寄存器。当大量的本地方法执行的时候,必将削弱JVM性能。对于内存不足的情况,本地方法栈会抛出native heap OutOfMemory。最著名的本地方法:System. currentTimeMillis().

    1. Program counter register (程序计数寄存器)

每个线程执行过程中都会当前线程的程序计数器和栈帧。程序计数寄存器用来储存这些信息。

存放cup执行时候,未执行完成的类信息的储存地方。

  1. 最简单的Object object=new Object();内部过程
    1. 确定类元信息是否存在

当JVM接收到new信息时候,首先在metaspace内检查需要创建的类元信息是否存在,不存在,在双亲委派小,是用类加载器进行找到对应额.class文件,找不到报ClassNotFoundException异常,存在进行加载,生成对应的Class类对象。

    1. 分配对象内存

计算对象占用空间,引用变量分配4个字节,在堆中划分一块内存给新对象,分配空间时候同步进行,保证分配的原子性!

    1. 设定默认值

设置各种0值。

    1. 设置对象头

设置对象的哈希码、GC信息、锁信息、

    1. 执行init方法

初始化成员变量,执行实例化代码,调用构造方法,并在堆内对象的首地址给引用变量。

  1. 垃圾回收GC

垃圾回收的目的是清理不在使用的对象,自动释放内存。

判断对象是否应该被回收?

JVM引入了GC Roots(一个对象可以有多少Root,一般有一下几种:Class-系统加载的对象,Thread-活着的线程,JNI Local-JNI方法的的local变量或参数,JNI Global-全局JNI变量等) 概念,如果一个对象与GC Roots之间没有直接或者间接的引用关系,就判定改对象应该被回收。

垃圾回收相应的算法有哪些?

基础的“标记-清除算法”,

较好的“标记-整理算法”,

优秀的“标记-复制算法”,

垃圾回收器(Garbage Collector)是实现垃圾回收算法并应用在JVM环境中的内存管理模块,当前有很多种,简述一下Serial、CMS、G1。

Serial 主要应用于YGC阶段,采用串行单线程的方式完成GC任务。

CMS是回收停顿时间比较短,目前常用的垃圾回收器!采用“标记-清除算法”,

G1是HotSpot在JDK7推出的新一代垃圾回收器,采用“标记-复制算法”,

  1. 异常与日志

Java中所有的异常都是Throwable的子类,分为Error(致命的)和Exception(非致命的)

防止NPE异常:

  1. 数据结构与集合

数据结构分类:

线性结构:0至1个直接前继和直接后继。代表有:顺序表、链表、栈、队列等

树结构:0至1个直接前继和0至n个直接后继。

图结构:0至n个直接前继和直接后继,n>=2.代表有:单图、多重图、有向图、多重图。

哈希结构:没有直接前继和直接后继。哈希结构通过某种特定的哈希函数将索引和存储关联起来。

List集合:

ArrayList:是容量可变的非线程安全的集合。内部使用数组进行储存,扩容时候会创建更大的空间,将原集合复制进去。支持快速的随机访问,但是插入或者删除速度较慢,因为可能会移动其他元素。

LinkedList:本质是双向的链表,与ArrayList相比,随机访问较慢,但插入和删除更快。

Queue集合:

队列是一种先进先出的数据结构。只允许一端插入一端获取;自从BlockingQueue(阻塞队列)问世,队列地位大大提升。通常作为数据缓存区(buffer)。

Map集合:

是以key-value键值对作为。HashMap是先线程不安全的。多线程采用-ConcurrentHashMap。TreeMap是key有序的Map类集合。HashMap的key、value都可以为空。

Set集合:

Set是不允许出现重复元素的集合类型。常用的有HashSet、TreeSet和LinkedHashSet三个集合类。HashSet从源码分析是使用HashMap来实现的,只是Value固定为一个静态对象。

ArrayList初始长度为10,HashMap的初始值为16.

长度较长的集合,应给定一个固定初始值。不然假如1000个HashMap的长度的元素,需要扩容七次。很耗资源。

  1. 数组与集合

数组与集合的不同点:数组是协变的,而集合是非协变的。

                     相同点:都是用另一个类型构建的类型

参考来源:https://www.jb51.net/article/156863.htm

class Soup<T> {

 public void add(T t) {}

}

class Vegetable { }

class Carrot extends Vegetable { }

协变:如果Soup<Carrot>是Soup<Vegetable>的子类,则称泛型Soup<T>是协变的

不变:如果Soup<Carrot>和Soup<Vegetable>是无关的两个类,则称泛型Soup<T>是不变的

逆变:如果Soup<Carrot>是Soup<Vegetable>的父类,则称泛型Soup<T>是逆变的。

实例:

Soup<Vegetable> soup = new Soup<Carrot>();

soup.add(new Tomato());

 

Vegetable[] vegetables = new Carrot[10];

vegetables[0] = new Tomato(); // 运行期错误

 

数组协变性,是Java的著名历史包袱之一。使用数组时,千万要小心!实际上上面两个举例的代码都不能正常运行。集合是直接编译期出错,而数组在运行期出错(更麻烦,所以说是个bug

泛型是不变的,但某些场景里我们还是希望它能协变起来。可以这样:

public void drink(Soup<? extends Vegetable> soup) {}

但是,这种方法有一个限制。编译器只知道泛型参数是Vegetable的子类,却不知道它具体是什么。所以,所有非null的泛型类型参数均被视为不安全的。说起来很拗口,其实很简单。直接上代码。因为soup的具体类型信息是在运行期才能知道的,编译期并不知道。

public void drink(Soup<? extends Vegetable> soup) {

 soup.add(new Tomato()); // 错误

 soup.add(null); // 正确

另一种方式:

public void drink(Soup<? super Vegetable> soup) {}

这时,Soup<T>中的T必须是Vegetable的父类。

public void drink(Soup<? super Vegetable> soup) {

 soup.add(new Tomato());

}可以正常执行!

总结一下:使用entends的时候,可能有多个不知道具体是那个类,所以只能增加null;而使用super的时候,只是本身和本身的子类可以加进去(肯定包含)。

所有的List<? Super T>集合可以执行get操作,但是因为类型擦除,不知道具体的类型,只能返回Object,List<? Extends Cat> 可以返回带类型的元素,但只能返回Cat自身及父类的对象,因为子类类型被擦除了。

 

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends) 

如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super) 

如果既要存又要取,那么就不要使用任何通配符。

Arrays.asList(数组)返回的是不是我们通常使用的ArrayList,而是Arrays类中的内部类。改类只用set方法,进行值操作。

    1. 集合的fail-fast错误监测机制

产生的原因:集合在进行迭代操作中的next()、remove()方法时候,都会调用checkForComdificationf方法,主要判断modCount == expectedModCount。集合每次修改(add,remove等)都会进行计数,叫做:modCount;而expectedModCount 是在Itr中定义的:int expectedModCount = ArrayList.this.modCount。不会改变。改变的就是modCount。原因是在多线程(单线程修改也会)执行时候,modCount改变,而集合expectedModCount没有改变。造成异常。

(其他说明有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历执行next方法时,通告checkForComodification方法发现expectedModCount  = N  ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制)

  1. Tree

高度:从某条节点出发到叶子节点最长简单路径的条数称为高度。

深度:从根节点出发到某节点的条数,称为节点的深度。

  1. Map集合

建议:除了局部方法和绝对线程安全的情形下,优先推荐使用ConcurrentHashMap,相对于HashMap两者性能相差无几(put3百万的数据,ConcurrentHashMap比HashMap还有平均快一秒),单前者解决了高并发下的线程安全问题。HashMap的死链问题及扩容数据丢失是慎用HashMap的主要原因。

       数据丢失的原因:并发赋值时候被覆盖;(新建数据防止到solt(哈希槽)的第一位,方便下次快速查找,当多个线程同时执行到这时候,后来元素会被覆盖)

                                   已遍历区间新增元素会丢失;

                                   “新表”被覆盖;

                                   迁移丢失。

       死链问题:指数据循环引用。CUP飙升。

       ConcurrentHashMap在jdk8进行了脱胎换骨的改造。在数据长度小于60的时候,是数组+链表的数据结构。大于60长度是数组+红黑树的数据结构。在多线程并发的时候使用。使用方法和HashMap相同。

  1. 线程安全

线程的生命周期:new(新建转态)、runnable(就绪转态)、running(运行转态)、blocked(阻塞状态)、dead(终止转态)

线程安全的核心理念是:要么只读,要么加锁。合理利用JDK提供的并发包.

  1. Junit测试

测试类名称:通常是test+类名称;或者更容易阅读的should..when结构,类似于shouldGetTicketInfoWhenAllParametersVaild

Junit的断言能够更好的服务于测试,断言封装了常用的逻辑判断,类似于StringUitls.举例说明有:assertSame(判断连个类是否相等)

AssertJ(断言的升级版本),包含许多更好的断言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值