Java

87 阅读25分钟
1 GC 运行时内存分配
Text.Java->编译生成 Text.class->类加载器classloader->加载到jvm虚拟机
对堆内存中对象进行识别如果有引用就成为活对象,如果没有被引用就是死对象会对它进行不定时回收

判断对象是否是存活

通过GC Roots的引用可达性来判断,
引用计数算法->一个对象被创建调用后 该变量会不断+1 当引用失效会-1

通过对象持有不同的引用进行回收

强引用  到蹦都不会回收
软引用  在保存能运行的情况下回收
弱引用  内存达到一定程度就会回收
虚引用  垃圾回收一运行就会执行
有引用队列,gc扫过会把对象放到队列里面

垃圾回收算法

1 标记回收算法: 标记处需要回收的对象,统一回收标记的对象
    效率不高 产生大量的内存碎片
2 复制算法: 将内存分为两块,一块用完复制存活对象到另外一块内存中清理另一块内存
    浪费一半内存,代价大
3 标记压缩算法: 标记需要回收的对象,将存活对象压到内存的某一端 避免内存碎片
4 分代收集算法: 
    新生代分2个区
    Eden新对方放在这个区
    survivor
    新生代存活低用复制,老年代生命周期较长 用标记压缩算法
小结
    1 新对象放在 新生代的Eden区
    2 GC扫过还存在->放到新生代的survivor(s0)->当s0满了 吧s0存活的对象放到s1 清空s0
    3 当在新生代存活一定次数,复制到老年代
    4 在老年代停留一定程度移动到持久代

Java中可以作为GC Root的对象有哪些

虚拟机栈中引用的对象
本地方法栈引用的对象
方法区中的静态变量和常量引用的对象

那些对象会被垃圾回收

1 所有实例没有获得线程访问
2 没有被其他人任何实例访问
3 引用类型 弱引用 虚引用

最优的数据类型

haspmap和arraymap
haspmap使用的创建对象  arraymap使用的复制
2 jvm
线程不共享数据区
    栈:每个方法执行,同步春节一个内存快,用来储存方法运行过程中的信息,每个方法被调用的过程对应一个栈帧在虚拟机中从入栈到出栈
    本地方法区: 执行Java方法使用栈,执行native方法使用本地方法区
    程序计数器: 保存当前线程执行的字节码位置,每个线程有个独立的计数器
线程共享数据区
    堆: 存放对象的实例,垃圾回收不定时回收
    方法区: 用与保存虚拟机加载的类信息,常量,编译器编译后的代码 ->static变量和方法

jvm组成

1 类加载器
2 运行时数据区
3 执行引擎
4 本地库接口
执行之前先把java代码转成字节码->jvm把字节码通过类加载器加载到内存中运行时数据区->通过执行引擎将字节码翻译成底层系统指令交给cpu去执行->这个过程中需要调用其他语言的接口就是本地库接口

java虚拟机和Dalvik虚拟机的区别

1 java虚拟机基于栈需要使用指令对栈进行操作所以指令更多 java虚拟机运行的是java的字节码  处理的是.class文件  所有应用在一个虚拟机中
2 Dalvik虚拟机基于寄存器	处理的是dex文件		每个应用单独一个虚拟机

堆是先进先出,栈是先进后出

1.栈内存存储的是局部变量而堆内存存储的是实体;
2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

插桩

可以在2个地方对代码进行改造
    1 a.java 转换成 a.class 
    2 a.class转换成 class.dex
    在位置2 的地方
比如每个activity打开打印一个log
    1 遍历项目中所有的.class文件,as使用gradle编译项目中的.Java文件
    2 遍历到目标activity生成的.class文件之后,通过ASM动态注入需要被插入的字节码
ASM开源框架 三方工具ASM Bytecode Outline生成字节码
3 class class文件结构
字节码文件是有十六进制组成,对于jvm读取数据的时候,它会以两个十六进制值为一组,即一个字节进行读取
class文件是一组以8位字节为基础单位的二进制流,紧凑排列没有分隔符,整个class文件中储存的内容都是必要数据

无符号数和表

无符号 属于数据基本的数据类型,以U1.U2.U3代表1个字节.2个字节.3个字节的无符号数,无符号数可以用来描述数字.索引引用.数量值或者按照UTF-8构成的字符串
表 是有无符号数或者其他表作为数据结构构成的复合数据类型,用于描述有层次关系的复合结构的数据,整个class文件其本质上就是一张表

常量池

常量池是class文件中的资源仓库,其他的几种结构或多或少都会最终指向到这个资源仓库之中
字面量
    比较接近于Java语音层面的常量概念,如文本字符串,声明为final的常量值等
符号引用
    符号引用属于编译原理方面的概念,包括三类常量
        1类和接口的全限定名 2字段的名称和描述符 3方法的名称和描述符
    
3 类加载 类加载器
启动类加载器,扩展类加载器和应用加载器

类加载过程

使用的是 classloader
加载 在内存中生成.class文件
验证 保证class字节流符合虚拟机要求->文件格式验证,元数据验证,字节码验证,符号引用验证
准备 为静态变量分配空间,并设置初始值
解析 将常量池中的符号引用 代替直接引用,就是直接指向目标的指标
初始化 完成静态块执行与静态变量的复制,执行静态代码

Java 对象生命周期

1 created 创建
为对象分配储存空间->构造对象
2 inUse 应用
对象被一个强引用持有
3 invisible 不可见
不再持有该对象的任何强引用
4 unreachable 不可达
5 collected 收集
6 finalized 终结
等待垃圾回收器 回收该对象空间
7 deallocated 对象空间重新分配

双亲委派模式

主要实现是在 ClassLoader.java中的loadclass()
dexClassLoader->pathClassLoader->bootClassloader
流程
    比如 Test t=new Test();
    判断该class是否已加载,加载就返回
    jvm 首先使用APPClassLoader 来加载Test类-> APPClassLoader吧加载任务委派给他的父加载器 扩展类加载器(ExtClassLoader)
    扩展类加载器的父加载器为null->直接把加载任务委派给 启动类加载器(BootstrapClassLoader)
    启动类加载器在 jdk/lib目录下没有找到Test类 返回null
    父加载器和启动类加载器都没有加载成功,最后 appClassLoader调用自己的findClass方法加载test

类加载器收到加载类的请求,不会直接去加载类,而是先把这个请求委派给父加载器去完成,依次会传递到最上级也就是启动类加载器
父加载器会检查是否已经加载过该类,如果没有加载 就去加载,加载失败交给子加载器,一直到最底层,都没有加载就会抛出异常
这么设计的原因
    层级关系可以避免类的重复加载
    为了防止危险代码的植入,所以已经加载过的类就不需要去加载了

classLoader

启动类加载器 bootstrapClassLoader
扩展类加载器 ExtClassLoader
系统加载器 AppClassLoader
面向用户的类加载器,自己编写的代码以及使用的第三方jar包由他加载

安卓类加载器

dex文件是将class文件重新打包,用的是BaseDexClassLoader来加载的, 我们一般使用的是它的子类 PathClassLoader和DexClassLoader
PathClassLoader 用来加载系统apk和被安装到手机中的apk内的dex文件
dexClassLoader 没有限制,可以加载任何的dex
可以动态加载jar通过URLClassLoader
jvm识别一个是由classloaderid+packageName+className
4 集合 list(有序 元素可以重复)
ArrayList 基于数组的数据结构  
    索引读取快 最后插入和删除元素快  中间插入和删除慢	
    查询快,增删慢
    如果储存满了就重新创建一个自身1.5的数组将之前的数据放进去在返回  
    插入 会把插入位置后面的数据复制到+1的位置
ArrayList和vector的扩容
    都有一个初始的容量大小,当更多的元素加入到集合中,期大小将会动态的增长,vector每次增加原来的一倍,ArrayList每次增加0.5倍
LinkedList 基于链表的数据结构 
    头尾插入和读取快(有双指针指针) 中间插入慢(中间需要循环找位置)  
    查询慢,增删快
    前后双指针
    速度慢 占内存 因为除了存储数据之外还需要储存节点的前后节点信息   ArrayList就是存数据
SparseArray 底层2个数组
    1int数组存key  1object数组存value 默认容量10, 
    存和取都是二分查找,存: 如果有值就往后移动 如果存的是object就直接存,删除 :把要删除的赋值成 object

set(元素无放入顺序,元素不可重复)

HashSet 采用的哈希表 如何保证不重复,底层采用HashMap来实现,比较hash值  无序的可以存null
Treeset 采用的二叉树结构 自动排好序 不允许为null 可以重复
set 数据不重复通过什么判断

Map

HaspMap 采用hashcode对内容查找   HashMap是由数组+链表+红黑树  装载因子0.6-0.75,默认大小16,每次两倍扩容
数组 每个键值对都是一个对象存储在数组中 也是主干
链表 数组长度有限,可能会出现冲突所以使用链表在避免冲突,
    数组中每个元素对应一个链表的头节点通过指针指向下一个节点,
    出现冲突 添加新增对象指向原来对象(形成一个单链表进行存储)
put() 将key插入到数组中,通过hash()确定对象的具体插入位置(hash中会获取可以的hashCode),调用putVal()计算出最终的位置
    判断数组是否为空(空就创建)->根据key计算hashCode得到索引(没有就创建节点)->判断key是否相同(相同就覆盖)->判断列表长度>8(红黑树) <8(链表)
hasp冲突
    索引位置存在相同的haspcode和相同的key,新的覆盖旧的
    haspcode相同,key不同,出现冲突,储存的就不是一个对象而是一个对象链,(新增对象指向原来对象形成一个对象链)
    通过equals()判断要获取的key,如果冲突了就是一个链表会循环获取
map保证线程安全
    hashTable整张表进行加锁
    ConcurrentHashMap
        1.7 是吧表分成了16分每一份都有一个锁,
        1.8取消了分段锁,采用cas+synchronized 来保证并发安全,只锁住了链表或者红黑树的首节点
TreeMap 保持某种固定的顺序 红黑树
arrayMap 储存数据用的是复制 效率高 采用的是二分法查找
linkedHashmap 是有数组和双向链表的数据结构实现的双向链表可以实现访问顺序和插入顺序
代替HashMap的使用
    SparseArray 内存消耗小不需要自动装箱, Key不能是对象类型 速度慢
    ArrayMap  也是实现Map接口 内存消耗小 key和Value不能是基本数据类型
HaspTable 和concurrenthashMAP
    都是线程安全 HaspTable锁的是整张表  concurrenthashMAP分端锁
arrayMap HashMap HashTable 区别
    arrayMap 储存数据用的是复制 效率高 采用的是二分法查找
    HashMaP 存储数据的时候是创建一个HashMapEntity对象放到数组中
    HaspTable 线程安全 key和value不能为null

其中Vector、HashTable、Properties是线程安全的

设计模式

单利 静态变量,静态代码块 简单判断非空,
代理 不能直接引用另一个对象的场景,可以通过代理模式对被代理对象的访问行文进行控制
责任链 每个节点完成对象的某一种处理
构造 一个对象有很多复杂的属性,需要根据不同情况创建不同的具体对象

建造者 初始化对象需要大量的变量组成,自定义的dialog
策略 多个域名
工厂 给调用者生成对象,fragment生成
观察者 列表点击回调
组合模式 自定义组合view
适配器 多个不匹配的对象放到一起进行适配 adapter


5 线程 安卓启动默认开启3条。一条主线程,两条binder线程
线程的创建
需要开辟虚拟机栈,本地方法栈,程序计数器等线程私有的内存空间,线程销毁是需要回收这些系统资源

volatile

可见性 get和set的时候加 了读写内存屏障,在数据可见性上保证数据同步,++是非原子性操作数据会出现不同步
不能确保原子性 需要进行加锁保证原子性
保证有序性 防止指令重排
修饰单利
    1分配内存 2初始化成员变量 3分配内存
    不能保证按顺序执行 所有需要加上

线程状态

新建(new thread)
就绪(runnable) 等待CPU资源
运行(running)	获得CPU资源正在执行任务
死亡(dead)	
堵塞(blocked)

终止方法

1 用个变量
2 stop 不推荐
3  interrupt 不会直接终止
4 try{}catch(){}抛异常终止

判断线程是否终止

1 Thread.interrupted  
如果已中断 第一次调用返回true(去掉标记)-> 第二次调用返回false
2 thread.isInterrupted()
已中断 返回true

run()和start()方法的区别

start是启动新创建的线程 在start内部调用了run方法和直接调用run方法是不一样的  ,当直接调用run方法只会是在原来的线程中调用没有新的线程启动. start方法才会启动新线程

condition cyclerbarrie countdownloatch 三个实现原理

condition的await()、signal()  必须在lock保护之内,代替Object的wait()、notify() 更加安全和高效
    final Lock lock = new ReentrantLock();
	final Condition condition = lock.newCondition();
cyclerbarrie lock锁加计数器
    等待多个线程完成在往下执行
    只要是有一个同步锁和拦截器组成
        //同步操作锁
        private final ReentrantLock lock = new ReentrantLock();
        //线程拦截器
        private final Condition trip = lock.newCondition();
    内部有个计数器,每个线程到达屏障点调用await将自己堵塞,计时器-1,减到0唤醒全部线程
    先到围栏的等待后面的线程,提供了两种等待的方法,
        及时等待和非及时等待,都调用了dowait() 传递的参数不同
    dowait()执行的操作
        1 检查当前围栏是否被打翻 (通过判断内部类中的broken变量)
        2 判断当前线程师父父中断
            如果中断 ->打翻当前围栏->唤醒拦截的所有线程->抛出中断异常
        3 将计数器的值减1
        4 计数器为0->唤醒所有的线程
        5 调用nextGeneration()将围栏转到下一代->在本方法中会将所有的线程唤醒,将计数器重置
    reset()重置围栏
        先打破当前围栏->切换围栏到下一代
countdownloatch 计数器 堵塞线程

线程和协程的区别

 一个线程可以有多个协程,一个进程可以单独有多个协程
线程是同步的,协程是异步的
协程可以保留上一次调用时的状态

线程的等待和唤醒机制(wait,notify,notifyAll)

wait()将线程封装成waitObject函数并将其插入到等待队列中
notify()会将线程从等待队列中移动到_EntryList队列,等持有锁的线程执行完毕在唤醒等待队列里面的
6 线程锁 synchronized _EntryList集合中存放的是没有抢到锁,而被阻塞的线程
_WaitSet集合中存放的是调用了wait方法后,处于等待状态的线程
双向链表->新来的插入尾部
synchronized锁升级流程
第一种:只有一个线程是偏向锁->多个线程访问进入轻量级锁->多次拿不到锁->重量级锁
第二种:只有一个线程是偏向锁->重度竞争调用wait->重量级锁 

Java 虚拟机对 synchronized 的优化

减少 重量级锁的使用次数,并最终减少线程上下文切换的频率
锁自旋
    线程频繁的堵塞和唤醒,会对CPU造成压力->锁自旋默认关闭通过参数开启->让线程等待一段时间,不会被立即挂起,看持有锁的线程会不会很快释放锁(执行一段无意义的循环)
偏向锁
    锁对象的对象头有个Threadid字段,->下次获取锁的时候检查id是否和自己的线程id相同->相同就是已经获取了锁
    优点: 加锁和解锁不需要额外的消耗
    场景: 只有一个线程,访问同步块场景
轻量级锁
    多个线程执行同一个代码块->线程在不同的时间交替请求这把锁->不存在锁竞争->锁保持在轻量级的状态
    优点: 竞争的线程不会堵塞
    场景: 追求响应时间,同步块执行速度快
重量级锁
    优点: 线程竞争不适用自旋,不会消耗CPU
    场景: 追求吞吐量,算的慢

悲观锁,乐观锁

悲观锁->是一种悲观思想,总认为最坏的情况可能出现,数据可能会被其他人修改,悲观锁在持有数据时会吧资源或者数据锁住, Synchronized 和 ReentrantLock也是悲观锁
乐观锁->认为资源和数据不会被别人修改,读取不会上锁,使用版本号机制和cas实现

CAS 乐观锁 流程

比较和替换,是一种通过硬件实现并发安全的技术,通过CPU的cas指令对缓存加锁或总线加锁的方式来实现多处理器直接的原子操作
new AtomicReference<>()具备了 cas的属性
计数器state 0 获取1 代表是否获取了锁->读取当前值->计算正确结果->比较读取的值和结果是否相等->不相等继续读取当前值->相等就更新
一个线程先拿到锁->下一个线程就进入队列等待,让线程堵塞->解锁,从队列中拿出一个线程 并唤醒->从队列中删除
ReentrantLock 读写锁 ReentrantReadWriteLock
使用Java的等待通知机制
当写操作开始时,晚于写操作的读操作都会等待
当写操作完成进行通知 后面的读操作才会执行
使用concurrent包中的读写锁->读的时候获取读锁,写的时候获取写锁

AQS

同步是一套实现多线程同步功能的框架
AQS在ReentrantLock的工作原理
    ReentrantLock没有直接获取aqs而是通过内部类Sync继承AQS
    AQS在ReentrantLock中两种实现 NonfairSync和FairSync 分别是非公平锁和公平锁
    AQS有个 node先进先出的双端队列,指示器 state
获取锁的流程
    模板方法acquire()通过调用子类的实现的tryAcquire获取锁
    如果获取锁失败,通过addWaiter将线程构造成node节点插入同步队列队尾
    在acquriQueued()以自旋的方法尝试获取锁
AQS有个state标记位,值为1表示有线程占用.其他线程需要进图到同步队列等待
当获得锁的线程需要等待某个条件时,会进入等待队列,等待队列可以有多个,当等待队列条件满足,线程会从等待队列重新进入同步队列

进行获取锁的竞争

公平的方式
    线程获取锁之前会判断是否有比当前线程等待时间长的
tryAcquire() 方法主要目的是尝试获取锁;
addWaiter() 如果 tryAcquire() 尝试获取锁失败则调用 addWaiter 将当前线程添加到一个等待队列中;
acquireQueued() 处理加入到队列中的节点,通过自旋去尝试获取锁,根据情况将线程挂起或者取消。
Sync 在 ReentrantLock 有两种实现:NonfairSync 和 FairSync,分别对应非公平锁和公平锁
NonfairSync非公平锁
    通过cas修改state成功->表示获取锁成功
    修改失败->表示锁被其他行程持有->进入acquire方法
FairSync公平锁
释放锁

锁 synchronized和ReentrantLock区别

synchronized 修饰实例方法  修饰静态类方法,修饰代码块
1 都具有重入锁特性.已经获得了锁所以可以在此获取
2 synchronized有优先级不是公平锁 ReentrantLock是公平锁
    ReentrantLock通过 构造函数传入true变成公平锁 ReentrantLock(true)
    公平锁就是 通过同步队列实现多线程按照申请顺序获取锁
3 synchronized出现异常主动释放  ReentrantLock出现异常需要手动调用
4 synchronized锁方法锁代码块 ReentrantLock lock.lock如果被锁定会在此等待 lock.unlock释放锁
5 ReentrantLock可以让等待锁的线程终止
6 ReentrantLock可以知道有没有成功获取锁

死锁的4个条件

请求并持有: 已获得资源的线程不放开,另一个请求资源的堵塞
互斥: 任务使用的资源中最少有一个是不能共享的
不剥夺: 已获得资源,在未使用玩之前,不能强制剥夺 
循环等待: 进程之间星城一种头尾相接的循环等待资源
7 线程池 线程池参数
corePoolSize: 核心线程数量
maximumPoolSize: 最大容纳线程数
keepTime:线程的空闲时间
unit: 时间单位
workQueue: 等待队列,任务大于核心线程数,就放到队列中
threadFactory: 线程工厂,用来创建线程,传null使用默认的
handler: 执行拒绝策略的对象

线程池流程

1 当没有达到核心线程数时,创建一个新线程提交任务,无论之前线程是否空闲
2 如果达到核心线程数,会放到等待队列中,等空闲了从队列中取出
3 线程大于核心线程数,队列已满,会创建先的线程执行任务
4 如果都满了,会执行拒绝策略

线程池

1 降低消耗重用已存在的线程 有任务无需等待新线程创建
2 堵塞队列
    ArrayBlockingQueue基于数组实现先进先出
    LinkedBlockingQueue基于链表先进先出
    SynchronousQueue内有任何容量
    PriorityBlockingQueue具有优先级
3 两种方式开启execute和submit
4 execute没有返回值,submit有返回值可以获取返回数据和判断是否被执行
5 关闭线程池shutdown和shutdownNow
6 流程 提交任务->核心线程是否已满(创建新线程执行)->队列是否已满(任务添加到队列中)->线程池是否已满(创建新线程执行)
易服用,减少频分创建和销毁的时间
功能强大:定时 任务队列 并发数控制

线程调度

原理
    只有一个线程占用CPU,处于运行状态
    多线程并发:轮流获取CPU使用权
    jvm负责线程调度:按照特定机制分配cpu使用权
模型
    分时调度模型: 轮流获取,均分cpu使用时间
    抢占式调度: 根据优先级

拒绝策略

1 abortPolicy 默认策略 8次以上直接抛出异常
2 callerRunsPolicy 不会抛出异常 将任务回馈至发起发比如main线程
3 discardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列尝试再次提交任务
4 discardPolicy 直接丢弃任务,不给予处理也不抛出异常
8 网络 物理层->数据链路层->网络层->传输层->会话层->表示层->应用层
socket tcp/ip udp http之间的关系
tcp udp区别	
    http 应用层
    udp 传输层 只负责发送数据不保证数据是否到达
    tcp 传输层 可靠 有序面向字节流	
        3次握手 A我想发送数据 B我准备好了 你准备好了吗 A我准备好了 开始
        4次挥手 A发送fin告诉B没有数据,B回复A发送ack说明知道了 
                B发送fin告诉A没有数据,A等待2msl(TIME_WAIT) 发送ack断开
        TIME_WAIT存在的意义:     
            1防止最后一个ack由于网络原因丢失->服务端等不到ack会重新发送fin,客户端再次发送ack
            2 保证迟来的tcp有足够的时间被识别和丢弃
        tcp链接漏洞 syn洪水攻击
            第一次握手需要客户端的IP,伪造这个ip大量请求
            解决办法->第一次握手成功分配tcb资源,延迟tcb资源分配或者使用防火墙
http 应用层 基于tcp协议链接
    是客户端主动发送请求给服务器同事服务器做出回应数据服务器端不能主动发送请求给客户端 短连接 
Socket 本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议链接成功后客户端和服务器端都可以主动发送数据 
socket和tcp
	socket是对tcp的封装 socket本身是一个接口
tcp/ip协议, http协议,socket三者之间的关系:
	实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的
	而Socket本身不算是协议,它只是提供了一个针对TCP或者UDP编程的接口。

https握手过程(默认端口443)

三要素
    加密->通过对称加密算法实现
    认证->通过数字签名实现(私钥只有合法的发送发持有,他人伪造的无法通过验证)
    报文完整性->通过数字签名实现(使用了消息摘要,他人篡改的消息无法通过验证)
过程
    1客户端发起HTTPS请求
    2服务端接受到请求->返回证书(证书包含公钥)
    3客户端判断证书是否合法->合法就生成随机数->用公钥加密随机数->将随机数传送给服务端
    4服务端通过私钥解密随机数->通过随机数的对称加密对数据加密->加密数据传输给客户端
    5客户端用本地随机数进行解密

1客户端发起HTTPS请求
2根据服务器配置
    公钥给别人加密 私钥自己解密
3传送公钥给客户端
4客户端解析证书
    验证公钥是否有效,没有问题就生成一个随机值然后用证书对这个随机值进行加密
5传送加密信息
    传送随机值,然服务端收到这个随机值,以后通信通过这个随机值进行加密解密
6服务端解密信息
7传输加密后的信息
    服务端加密传给客户端
8客户端解密信息


完整的http过程

dns域名解析->3次握手建立tcp连接(一般是80端口)->客户端发起http请求->服务端响应->客户端拿到服务端返回的数据	

dns劫持和http劫持

dns是将域名和IP地址相互映射的一个分布式数据库
dns劫持:将原IP地址转入到修改后的指定IP,造成网址不能访问或者访问假网址
http劫持:1中类型dns劫持返回假的地址,1中是在HTML中加入js或者广告
    防止http劫持->https或者使用对称加密

http1.0和http1.1区别

1 缓存处理->http1.1有更多的缓存选择
2 http1.1支持只请求资源的某个部分,支持断点续传
3 http1.1新增错误通知
4 http1.1支持长连接,不必每次请求都重新创建

http2.0新特性

多路复用,即链接共享,通过request的id归属到各自不同的服务端
header压缩

tcp粘包/分包

分包: 写入的字节大小大于套接字发送缓冲区的大小,会发送拆包
粘包: 写入数据过小,网卡多次发送数据
解决:1消息定长 2添加字符分割 3将消息分为消息头和消息体

对称加密和非对称加密

对称加密: 加密和解密用的同一个密码,速度快 适合大文件,不安全密码文容易被捕获
非对称加密: 有成对的公钥和私钥,速度慢 适合小文件,安全 单向的,私钥计算推出公钥
9 string设计成不可变的
当一个字符串已经被创建并且该字符串在字符串池中,该字符串的引用会立即返回给变量,再不是重新创建->如果不是不可变的,那改变一个引用的字符串将会导致另一个引用出现脏数据
允许字符串缓存哈希码
string的不可变性保证哈希码始终唯一,不必每次使用时都重新计算一次哈希码,效率高

泛型

可以编写模板代码来适应任意类型,减少重复代码
不必对类型进行强转,减少出错机会
泛型擦除:Java在编译期间,所有的泛型会被擦除
主要是兼容之前没有泛型的代码
泛型擦除后retrofit是怎么获取类型的
    retrofit是通过getGenericReturnType来获取类型信息的->jdk class method field类提供了一系列获取泛型类型的方法->侧泛型,会被class文件以signature的形式保留在class文件的constant pool中
gson获取泛型
    侧泛型包括
        泛型类或泛型接口的声明
        带有泛型参数的方法
        带有泛型参数的成员变量
    获取方式
        利用子类会保存父类class的泛型信息的特点,通过匿名内部类实现了泛型参数的传递
PECS原则
    如果是生产者则使用Extend,如果是消费者则使用super
    为了实现集合的多态
    1.只读不可写时,使用List<? extends Fruit>:Producer2.只写不可读时,使用List<? super Apple>:Consumer

自动装箱和拆箱

Integer total = 99;//自动装箱 
int totalprim = total;//自动拆箱
装箱是吧基本数据类型转换为包装器类型
拆箱是将包装器类型转换为基本数据类型

序列化和反序列化

序列化 将对象转换为二进制形式,用于网络传输.数据持久化等
反序列化 从网络或者磁盘读取的字节数组还原冲原始对象
serializable 序列化 效率低在硬盘读写 会有大量的临时变量产生 适合用作持久化
Parcelable 序列化 效率快 使用麻烦 在内存中读写

equals和==

equals比较对象中的数据 
==是比较地址值