网络
HTTP
HTTP 协议是(超文本传输协议)是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP 是一个基于 TCP/IP 通信协议来传递数据,应用层。
http 请求-响应模型
特点
1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法:
常用的有 GET、HEAD、POST。
由于 HTTP 协议简单,使得 HTTP 服务器的程序规模小,因而通信速度很快。
2、灵活:HTTP 允许传输任意类型的数据对象。正在传输的类型由 Content-Type
加以标记。
3.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的
请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
4.无状态:HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能
力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导
致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答
就较快。
5、支持 B/S 及 C/S 模式。
URI 和 URL 的区别
URL:统一资源定位符
①协议(或称为服务方式) ②存有该资源的主机 IP 地址(有时也包括端口号) ③主机资源的具体地址。
www.aspxfans.com:8080/news/index.…
URI:统一资源标识符
①访问资源的命名机制 ②存放资源的主机名③资源自身的名称,由路径表示
URN:统 一 资 源 命 名,是 通 过 名 字 来 标 识 资 源
mailto:java-net@java.sun.com
URI = URL+URN,URI是抽象URL、URN是具体
HTTP 之请求消息 Request
请求:请求行、请求头部、空行和请求数据
请求行=请求类型 + 要访问的资源 + HTTP 版本
GET /562f25980001b1b106000338.jpg HTTP/1.1
请求头部=头部附加信息
Host img.mukewang.com
Accept image/webp,image/,/*;q=0.8
空行:请求头部后面的空行是必须的
请求数据主体:post的body
响应:状态行、消息报头、空行和响应正文
状态行=HTTP 协议版本号+状态码+状态消息
状态码:200成功404请求资源不存在...
HTTP/1.1 200 OK
消息报头=头部附加信息
空行:请求头部后面的空行是必须的
响应正文:html
GET 和 POST 请求的区别
1.GET 数据地址栏,POST在body
2.传输数据的大小:HTTP 协议没有对传输的数据大小进行限制。但是浏览器会有限制。GET约2K,2048 个字符(URL限制),POST约4M
3.POST 的安全性要比 GET 的安全性高。
4.GET 一般用于获取/查询资源信息,而 POST一般用于更新资源信息
TCP/IP 协议
1.OSI 七层模型
(1)应用层:应用程序通过这一层访问网络,常见FTP、HTTP、DNS 和 TELNET
(2)传输层:TCP 协议和 UDP 协议;
(3)网络层:IP 协议,ARP、RARP 协议,ICMP
(4)网络接口层:是 TCP/IP 协议的基层,负责数据帧的发送和接收。
1.IP 地址
255.255.255.255 4 组 0-255
2.域名
IP 地址的别名,DNS域名服务器解析翻译。
3.MAC 地址
物理地址、硬件地址
封装和分用 - 封包拆包
封装:当应用程序发送数据的时候,数据在协议层次当中从顶向下通过每一层,
每一层都会对数据增加一些首部或尾部信息,这样的信息称之为协议数据单元---PDU
分用:当主机收到一个数据帧时,数据就从协议层底向上升,通过每一层时,检
查并去掉对应层次的报文首部或尾部,与封装过程正好相反。
TCP 的三次握手
序列号 seq:用来标记数据段的顺序。随机产生,后续都是+1
确认号 ack:确认号指的是期望接收到下一个字节的编号;请求的+1
确认 ACK:仅当 ACK=1 时,确认号字段才有效。ACK=0 时,确认号无效
同步 SYN:连接建立时用于同步序号。SYN=1 表示这是一个连接请求,握手完成SYN=0。
终止 FIN:用来释放一个连接。
四次挥手
第一次:主动关闭端(客户端、服务器都可以,因为是全双工的),发生FIN=1(finish)seq值请求关闭连接。
第二次:服务器收到请求,说我收到了。回复ACK=1,seq=seq+1
第三次:服务器发起关闭请求FIN=1,服务器的seq值,然后进入CLOSE(正在关闭状态)
第四次:客户端回复我收到了ACK=1,seq=seq+1。并进入TIME_WAITING状态。这是还客户端没关闭,等待最多2*MSL(最大报文连接时长,最大2分钟,一般30秒)后关闭。时间到后自己就关闭了CLOSED。服务器收到客户端回复之后,自己就关闭了CLOSED。
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为是全双工的工(即客户端和服务器端可以相互发送和接收请求) ,第一次客户端说我不在发消息了;服务器说我收到了。(这时服务器可能还会再发消息)
第三次服务器说我不发消息了,(那么双方都不发消息了,这时才开始关闭)。
为什么需要TIME-WAIT状态
1.可靠的终止TCP连接。 不能确保服务器收到了,最后服务端给的回复(第四次挥手)。所以以防万一,还是等你一下,如果没响动一段时间,我在关闭。
2.让迟来的TCP报文,有足够的时间识别丢弃。
为什么不能用两次握手进行连接?
既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
两次握手容易死锁。客户端没收到服务端响应,一个在那死等,一个以为连接已经建立。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 还设有一个保活计时器,时间到了2小时,没有响应,就发送心跳包,不回复就断开连接。
一次完整http请求的过程
1、首先进行DNS域名解析(本地浏览器缓存、操作系统缓存或者DNS服务器)
(输入:http://www.baidu.com,先在本地缓存找IP地址,没找到去操作系统缓存找,没找到去DNS服务器找)
2、三次握手建立 TCP 连接
(得到192.11.21.1 Ip地址,三次握手建立Tcp连接)
3、客户端发起HTTP请求
4、服务器响应HTTP请求,
5、客户端解析html代码,并请求html代码中的资源
6、客户端渲染展示内容
7、关闭 TCP 连接
(http1.0的时候每次访问都是1-7,会变比连接。http1.0之后,1-6之后TCP连接不会关闭。可以重复3-6步。 http2.0有IO多路复用)
TCP 和 UDP 的区别
TCP 提供的是面向连接,可靠的字节流服务,全双工。(三次握手)超时重发,丢弃重复数据,检验数据,流量控制
UDP 是一个简单的面向数据报的运输层协议。它不提供可靠性,只是把应用程序传给 IP 层的数据报发送出去,但是不能保证它们能到达目的地。
TCP 是面向连接的,UDP 是面向无连接的
TCP 数据报头包括序列号,确认号,等等。相比之下 UDP 程序结构较简单。
TCP 是面向字节流的,UDP是基于数据报的
TCP保证数据正确性,UDP 可能丢包
TCP保证数据顺序,UDP 不保证
UDP:直播,游戏;TCP:其他所有
一个 TCP 连接上面能发多少个 HTTP 请求
Http1.0的时候,每次连接发送接收完事就断开了。所以只有一个。
Http1.1之后,一次TCP请求之后,连接不会关闭,还能发多个HTTP请求。
Http2.0之后,有多路复用了。
Java
HashMap
HashMap 是基于 Map 接口实现的一种键-值对<key,value>的存储结构,允许 null 值,同时非 有序,非同步(即线程不安全)。HashMap 的底层实现是数组 + 链表 + 红黑树 (JDK1.8 增加了红黑树部分)。它存储和查找数据时,是根据键 key 的 hashCode 的值计算出具体的存储位置。HashMap 最多只允许一条记录的键 key 为 null, HashMap 增删改查等常规操作都有不错的执行效率,是 ArrayList 和 LinkedList 等数据结构的一种折中实现。
默认底层 hash 表数组的大小为 16
float loadFactor;负载因子(默认是 0.75)
int threshold;下一次扩容时的阈值(阈值 threshold = 容器容量 capacity * 负载因子 load factor)。
Node<K,V>[] table; 底层数组,充当哈希表的作用,此数组长度总是 2 的 N 次幂。
HashMap的底层实现原理?如存储 put(K key, V value),查找 get(Object key),删除 remove(Object key),修改 replace(K key, V value)等操作。
put
先调用 hash(key)方法计算出 key 的 hash 值
(hash 值 % hash 表长度)计算出存储位置 table[i]
get
先调用 hash(key)方法计算出 key 的 hash 值
remove
remove跟get是一样的
1.先调用 hash(key)方法计算出 key 的 hash 值, (hash 值 % hash 表长度)计算出存储位置 table[i]。
2.判断table[i]是否有值。
3.链表,第一个节点是不是要找的值,是直接删
4.判断链表是否红黑树,是走红黑树删除
5.不是遍历单链表,找到,然后删掉
replace
跟get一样,先找到那个节点,getNode()方法获取对应 key 所映射的 value 值
记录元素旧值,将新值赋值给元素,返回元素旧值,如果没有找到元素,则返回 null。
hash 冲突
如果新添的值,hashCode%length得出来的值之前已经有数据存在这里面了。(有链表了)
此时会将新节点放到原来的链表的头部,然后插入。这叫头插法或者头部插入法。
而这种插入的时候原来位置处已经有链表了,这叫 hash 碰撞或者说Hash冲突
hash 冲突解决:链地址法。链表大于8就转成红黑树,提高查找效率
HashMap 是使用链地址法解决 hash 冲突的,当有冲突元素放进来时,会将此元素插入至此位置链表的最后一位,形成单链表。当存在位置的链表长度 大于等于 8 时,HashMap 会将链表 转变为 红黑树,以此提高查找效率。
HashMap 的容量为什么一定要是 2 的 n 次方
hash % length
1111&其他值,得到的数分布更加均匀,每一位的概率都一样
就算设置成7也会是最近的2 的 n 次方数,8
负载因子
负载因子表示哈希表空间的使用程度。
默认0.75,也就是tab长度16时。16*0.75=12,当存的数大于12的时候,hash碰撞会很严重,得扩容了。
60% - 75%最佳
HashMap 和 HashTable
1.HashMap线程不安全 ,HashTable线程安全
2.HashMap key,value可以为null,HashTable 不可为null
3.HashMap 默认16,扩容2,HashTable 默认11,扩容2+1
HashMap 不是线程安全的,那如果多线程下,它是如何处理的?
HashMap 不是线程安全的,如果多个线程同时对同一个 HashMap 更改数据的话,会导致数据不一致或者数据污染。
如果出现线程不安全的操作时,HashMap会尽可能的抛出 ConcurrentModificationException 防止数据异常,当我们在对一个 HashMap 进行遍历时,在遍历期间,我们是不能对 HashMap 进行添加,删除
等更改数据的操作的,否则也会抛出 ConcurrentModificationException 异常,此为 fail-fast(快速失败)机制。
如果想要线程安全,可以考虑使用ConcurrentHashMap。
扩容时多线程也可能会死循环。
我们在使用 HashMap 时,选取什么对象作为 key 键比较好
不可变对象,即hashCode不会改变的对象。String,Integer这样的。
HashSet
HashSet是使用HashMap来实现的。
add
直接调用 HashMap 的 put()方法,把元素本身作为 key,把 PRESENT 作为 value,
也就是这个 map 中所有的 value 都是一样的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
remove
返回是否删除成功
return map.remove(o)==PRESENT;
contains
检查元素是否存在
public boolean contains(Object o) {
return map.containsKey(o);
}
遍历元素
public Iterator<E> iterator() {
return map.keySet().iterator();
}
(1)HashSet 内部使用 HashMap 的 key 存储元素,以此来保证元素不重复;
(2)HashSet 是无序的,因为 HashMap 的 key 是无序的;
(3)HashSet 中允许有一个 null 元素,因为 HashMap 允许 key 为 null;
(4)HashSet 是非线程安全的;
(5)HashSet 是没有 get()方法的;
JMM
因为内存交互,IO操作,从内存读数据,等跟CPU的速度不是一个量级的,数据从内存->CPU,跟CPU计算。所以得设计一个高速缓存,在做缓冲。
缓存一致性
基于高速缓存的存储系统交互很好地解决了处理器与内存速度的矛盾,但是也为
计算机系统带来更高的复杂度,因为引入了一个新问题:缓存一致性。
多核CPU每个都有自己的高速缓存,跟主内存RAM很容易出现数据不一致。
代码乱序执行优化问题
为了提高运算效率,CPU执行代码是乱序的,在多核中,可能一核执行一部分逻辑,所以容易引发问题。
为了解决上面问题:
屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果,JMM被设计出来了。
主内存里存了所有变量,每个线程又有各自的工作内存,存了变量副本。
但是这导致两个问题,
1 工作内存数据一致性,线程安全问题
2 指令重排序优化
JMM的8个指令:
作用于主内存的变量:
lock (锁定) 标识为线程状态。
unlock (解锁) 解锁后的变量才可以被其他线程锁定。
read (读取) 变量从主内存传输 -> 线程的工作内存中,以便随后的 load 动作使用。
write (写入) 变量从工作内存传输 -> 主内存中。store操作后
作用于工作内存的变量:
load (载入) read后的变量,存到工作内存的变量副本中。
use (使用) 变量从工作内存->执行引擎;虚拟机需要使用字节码指令时就会执行这个操作。
assign (赋值) 执行引擎->工作内存。
store (存储) 工作内存->主内存。write之前。
原子性:一组操作要么全部执行,要么就都不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性外面看起来代码是顺序执行的
内存屏障
Java 中如何保证底层操作的有序性和可见性?可以通过内存屏障。
内存屏障是被插入两个 CPU 指令之间的一种指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障有序性的。
volatile 和synchronized用的就是内存屏障
volatile在每个操作前后都会插入内存屏障。
final
一旦初始化完成,final 变量的值立刻回写到主内存。
垃圾回收算法(JVM)
ART 的机制与 Dalvik 不同。
在 Dalvik 下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转
换为机器码,这会拖慢应用的运行效率。
而在 ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,极大的提高
了程序的运行效率,同时减少了手机的耗电量,使其成为真正的本地应用。这个过程叫做预
编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
优点:
系统性能的显著提升。
应用启动更快、运行更快、体验更流畅、触感反馈更及时。
更长的电池续航能力。
支持更低的硬件。
缺点:
费内存
应用的安装时间会变长。
直接内存
NIO,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存。
避免在 Java 堆和Native 堆中来回的数据耗时操作。
对象的访问定位
句柄访问、直接指针访问
判断是否应该回收 引用计数法、可达性分析法
GC Roots 的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象
finalize()
如果要被GC,finalize()可免死一次。
finalize() 方法只会被系统自动调用一次。
垃圾回收算法
标记清除算法、复制算法、标记-整理算法
先行发生原则
happens-before 原则。这个原则是判断数据是否存在竞争、线程是否安全的主要依据。先行发生是 Java 内存模型中定义的两项操作之间的偏序关系。
线程的实现
内核线程 = 系统提供的线程
轻量级线程 = 常说的线程
每个轻量级进程都有一个内核级线程支持。
线程调度:协同式线程调度(线程执行时间由线程自身控制,不常用)、抢占式线程调度
虚拟机类加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,终形成可以被虚拟机直接使用的 Java 类型。
在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。
类加载的过程
生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
加载:
1.通过类名获取二进制的字节流
2.将字节流的静态存储结构转化为方法区的运行时数据结构
3.在堆中生成一个代表这个类的 Class 对象,作为方法区中这些数据的访问入口。
验证:文件格式/元数据/字节码/符号引用的验证
准备:为类变量分配内存并设置初始值。
解析:虚拟机将常量池中的符号引用转化为直接引用的过程。
初始化:①声明类变量是指定初始值②使用静态代码块为类变量指定初始值
类加载的三种方式
(1)JVM执行main()方法
(2)Class.forName()
(3)ClassLoader.loadClass()
双亲委派原则
当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。
自定义类加载器
(1)遵守双亲委派模型:继承 ClassLoader,重写 findClass()方法。
(2)破坏双亲委派模型:继承 ClassLoader,重写 loadClass()方法。 通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。
反射
JAVA 反射机制是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;
这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。
多线程和线程池
synchronized对象锁,锁的是对象。
设计模式
六大基本原则
单一职责原则:职责。 一个类只负责一项职责
里氏替换原则:继承。 父类中的方法可以用子类中的替换(子类是父类的扩展,而不是改变)。
依赖倒置原则:抽象。 面向抽象编程,细节应该依赖抽象,抽象不能依赖细节。(高层 不-> 低层)。
接口隔离原则:接口。 接口得是最小接口,不要加进无关的方法。
开闭原则
迪米特法则:耦合。最少了解 A->B->C不能 A->B A->C
断点续传
RandomAccessFile 定义position记录中断的位置,将seek指针指到position。
Java 四大引用
强 内存不足,OOM,也不回收。
软 内存不够,回收。
弱 一次GC,回收
虚 随时暴毙
泛型
“参数化类型”
接口和抽象
类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。
接口用于抽象事物的特性,抽象类用于代码复用。