1.10、Java面经 内容太杂不详细 没用
1.1java 的 8 种基本数据类型 装箱 拆箱
https://blog.csdn.net/daidaineteasy/article/details/51088269
1.1.1.8 种基本数据类型
Byte short int long float double boolean char1.1.2.装箱和拆箱
自动装箱是 Java 编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比
如:把 int 转化成 Integer,double 转化成 Double,等等。反之就是自动拆箱。
原始类型: boolean,char,byte,short,int,long,float,double
封装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
1.1.3.String 转出 int 型,判断能不能转?如何转?
答:可以转,得处理异常 Integer.parseInt(s) 主要为 NumberFormatException:1)当
你输入为字母时,也就是内容不是数字时,如 abcd 2)当你输入为空时 3)当你输入超出
int 上限时
Long.parseLong("123")转换为 long1.1.4 short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1
+=1;有什么错?
1) 对于 short s1=1;s1=s1+1 来说,在 s1+1 运算时会自动提升表达式的类型为 int,
那么将 int 赋予给 short 类型的变量 s1 会出现类型转换错误。
2) 对于 short s1=1;s1+=1 来说 +=是 java 语言规定的运算符,java 编译器会对它
进行特殊处理,因此可以正确编译。
1.1.5.
Int 与 Integer 区别
https://www.cnblogs.com/guodongdidi/p/6953217.html1.1.6.字节字符区别
字节是存储容量的基本单位,字符是数子,字母,汉子以及其他语言的各种符号。
1 字节=8 个二进制单位:一个一个字符由一个字节或多个字节的二进制单位组成。
1.1.7 java 基本类型与引用类型的区别
基本类型保存原始值,引用类型保存的是引用值(引用值就是指对象在堆中所
处的位置/地址)
1.2 重写重载封装继承多态一.继承的好处和坏处
好处:1. 子类能自动继承父类的接口
2. 创建子类的对象时,无须创建父类的对象
坏处:1. 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏
独立性
2. 支持扩展,但是往往以增加系统结构的复杂度为代价
3. 不支持动态继承。在运行时,子类无法选择不同的父类
4. 子类不能改变父类的接口
二.重载、重写
Java 中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不
同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方
法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。
https://blog.csdn.net/cey009008/article/details/46331619
重写(
override)又名覆盖:
1.不能存在同一个类中,在继承或实现关系的类中;
2. 名相同,参数列表相同,方法返回值相同,
3.子类方法的访问修饰符要大于父类的。
4.子类的检查异常类型要小于父类的检查异常。
重载(
overload)
1.可以在一个类中也可以在继承关系的类中;
2.名相同;
3.参数列表不同(个数,顺序,类型) 和方法的返回值类型无关。
三.Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?
Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法
是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
java 中也不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使用,
如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖。
1.3 Stack Queue
1.3.1 PriorityQueue
PriorityQueue 是一个基于优先级堆的无界队列,它的元素是按照自
然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素
排序的比较器。PriorityQueue 不允许 null 值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue 不是线程安全的,入队和出
队的时间复杂度是 O(log(n))。
堆树的定义如下:
(
1)堆树是一颗完全二叉树;
(
2)堆树中某个节点的值总是不大于或不小于其孩子节点的值;
(
3)堆树中每个节点的子树都是堆树。
1.原理
https://www.cnblogs.com/CarpenterLee/p/5488070.html
孩子节点的下标例如下标 2 5 6
(
5-1)/2=2 (6-1)/2=2 每个父节点的值都
比孩子节点的值要比孩子节点的值要小。
1. 添加元素 add()和 offer()
原理:添加元素位于末尾,同时队列长度加 1,然后这个元素与它的父节点进行比较,
如果比父节点小那么就与父节点进行交换,然后再与交换后的位置的父节点进行比较,
重复这个过程,直到该元素的值大于父节点结束这个过程。
区别: add(E e)和 offer(E e)的语义相同,都是向优先队列中插入元素,只是 Queue 接口规
定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回 false。对于
PriorityQueue 这两个方法其实没什么差2. 寻找队列的头部元素 element()和 peek()头部元素 时间复杂度为 1
element()和 peek()的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小
的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回 null。根据小顶堆
的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0 下标处
的那个元素既是堆顶元素。所以直接返回数组 0 下标处的那个元素即可。
4.删除元素 remove() 和 poll()
区别:remove()和 poll()方法的语义也完全相同,都是获取并删除队首元素,区别是当方法
失败时前者抛出异常,后者返回 null。由于删除操作会改变队列的结构,为维护小顶堆的
性质,需要进行必要的调整。
原理:该方法的作用是从 k 指定的位置开始,将 x 逐层向下与当前点的左右孩子中较小的
那个交换,直到 x 小于或等于左右孩子中的任何一个为止3. 最大堆 获取数组中最小的几个数 最小堆 获取数组中最大的几个数上述代码是构建最大堆,最大堆的栈顶是堆的最大的元素,最大的元素比数组中任意一个
元素小,说明了最大堆这些元素是数组中最小的几个元素。
上述代码为何需要重现写比较器函数 compare()???
答:需要查看优先队列的源码,如下源码所示,添加元素需要比较新的元素与父节点的元
素,
如果比较器比较结果大等于 0,那么结束添加过程添加完成,说明在构建最大堆时候,要想
使得元素是对父节点的元素小才结束循环,那么必须重新写比较器函数,调换两者的比较
顺序即可。
最小堆,与上述过程相反。1.7 Concurrent 包
Concurrent 包里的其他东西:ArrayBlockingQueue、CountDownLatch 等等1.8 面向对象
面向对象特点、C++Java 面向对象设计的不同、
面向对象的设计规范
对面向对象的认识
面向对象的特征
1.9 String StringBuffer StringBuilder hashcode equal
一.String StringBuffer StringBuilder 的区别二.String 不可变?
String 中的 hashcode 以及 toString使用场景
四.String,是否可以继承,“+”怎样实现,与 StringBuffer 区别
1.10 java 文件读取1.11 Java 反射
Java 反射的概念和应用场景
反射机制中可以获取 private 成员的值吗(没有 set 和 get 函数)可以
反射的所有包,怎实现反射
反射的定义
Java.long.reflect 里常用方法1.12 JDK NDK JRE JNI
1.JDK 与 JRE 的区别
Java 运行时环境(JRE)。它包括 Java 虚拟机、Java 核心类库和支持文件。它不包含
开发工具(
JDK)--编译器、调试器和其他工具。
Java 开发工具包(JDK)是完整的 Java 软件开发包,包含了 JRE,编译器和其他的工具(比如:
JavaDoc,Java 调试器),可以让开发者开发、编译、执行 Java 应用程序。
2. 版本特性
JDK 中哪些体现了命令模式
JDK 发展
了解 NDK 吗?他和 JDK 有什么区别呢?
使用过 JNI 么?NDK 技术使用过哪些呢?
1.13 static 和 final 的区别
final 的好处:1.
final 关键字提高了性能。JVM 和 Java 应用都会缓存 final 变量。
2.
final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
3.
使用 final 关键字,
JVM 会对方法、变量及类进行优化。
一、
static 方法是否可以覆盖?
static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译
时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
二.是否可以在 static 环境中访问非 static 变量?
static 修饰的变量并发下怎么保证变量的安全
static 修饰的变量什么时候赋值
static 什么时候使用
class A {
private static A a=new A();
static{
System.out.print("static");
}
public A(){
System.out.print("A");
}
}
public class B extends A{
public B(){
System.out.print("B");
}
public static void main(String[] args) {
B b=new B();
}
}
上述运行结果 AstaticAB
class A {
private static A a=new A();
static{
System.out.print("static");
}
{
System.out.print("A");
}
}
public class B extends A{
public B(){
System.out.print("B");
}
public static void main(String[] args) {
B b=new B();}
}
上述结果也是 AstaticAB 匿名构造器 构造器先与静态代码块执行,静态代码块只执行一
次
class A {
public A(){
System.out.print("A gouzhao");
}
private static A a=new A();
static{
System.out.print("static");
}
{
System.out.print("A1");
}
}
public class B extends A{
public B(){
System.out.print("B");
}
public static void main(String[] args) {
System.out.println("0000");
B b=new B();
}
}
运行结果:A1A gouzhaostatic0000
A1A gouzhaoB
解释:静态变量,静态代码块先于 System.out.println(“0000”)执行,静态的东西只执行一
次,相当于全局变量。
1.14 map,list,set 区别
有哪些类,这些类有什么区别
1.16 Session 和 COOKIE
1.session 和 cookie 区别cookie 是 Web 服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个 Web
服务器存储 cookie。以后浏览器在给特定的 Web 服务器发请求的时候,同时会发送所有为
该服务器存储的 cookie。下面列出了 session 和 cookie 的区别:
无论客户端浏览器做怎么样的设置,session 都应该能正常工作。客户端可以选择禁用
cookie,但是,session 仍然是能够工作的,因为客户端无法禁用服务端的 session。
在存储的数据量方面 session 和 cookies 也是不一样的。session 能够存储任意的 Java 对象,
cookie 只能存储 String 类型的对象。
服务器端 Session 的保存
Cookie 和 session 区别
session 在服务器上以怎样的形式存在 session 持久化
怎么设置 session 和 cookie 的有效时间
Session的实现原理和应用场景 Session原理; 既然Session是存储在服务器内存的,
如果服务器的负载量很高内存负荷不住要怎么办?
1.19 IO NIO BIO AIO select epoll什么是阻塞和非阻塞,什么是同步和异步,同步和异步是针对应用程序和内核的交互而言的,
同步指的是用户进程触发 IO 操作并等待或者轮询的去查看 IO 操作是否就绪,而异步是指
用户进程触发 IO 操作以后便开始做自己的事情,而当 IO 操作已经完成的时候会得到 IO 完
成的通知。而阻塞和非阻塞是针对于进程在访问数据的时候,根据 IO 操作的就绪状态来采
取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入
函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
一般来说 I/O 模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞 IO同步阻塞 IO:在此种方式下,用户进程在发起一个 IO 操作以后,必须等待 IO 操作的完成,
只有当真正完成了 IO 操作以后,用户进程才能运行。JAVA 传统的 IO 模型属于此种方式!
同步非阻塞 IO:在此种方式下,用户进程发起一个 IO 操作以后边可返回做其它事情,但是
用户进程需要时不时的询问 IO 操作是否就绪,这就要求用户进程不停的去询问,从而引入
不必要的 CPU 资源浪费。其中目前 JAVA 的 NIO 就属于同步非阻塞 IO。
异步阻塞 IO:此种方式下是指应用发起一个 IO 操作以后,不等待内核 IO 操作的完成,等
内核完成 IO 操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等
待或者主动的去询问 IO 是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统
调用来完成的,而 select 函数本身的实现方式是阻塞的,而采用 select 函数有个好处就是
它可以同时监听多个文件句柄,从而提高系统的并发性!
异步非阻塞 IO:在此种模式下,用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操
作真正的完成以后,应用程序会得到 IO 操作完成的通知,此时用户进程只需要对数据进行
处理就好了,不需要进行实际的 IO 读写操作,因为真正的 IO 读取或者写入操作已经由内
核完成了。
1.19.1 NIO 的原理NIO 的 DirectByteBuffer 和 HeapByteBuffer
.IO 哪个类可以 Byte 转 String。
java NIO 的实现原理,我给他将了阻塞非阻塞,同步异步,Buffer 与 Channel 以及
Selector 的运行机制,然后又问 NIO 主要解决的是什么问题1.20 ThreadLocal
当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变
量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
1.22 finalize finalization finally
一. finalize 用途
答:垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的 finalize()方
法 但是在 Java 中很不幸,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就
是说 filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。 那么 finalize()究竟
是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。Java 程序有垃圾回收器,所
以一般情况下内存问题不用程序员操心。但有一种 JNI(Java Native Interface)调用 non-Java
程序(C 或 C++),finalize()的工作就是回收这部分的内存。
二.finallyfinally 一定会被执行,如果 finally 里有 return 语句,则覆盖 try/catch 里的 return ,
比较爱考的是 finally 里没有 return 语句,这时虽然 finally 里对 return 的值进行了
修改,但 return 的值并不改变这种情况
三.finally 代码块和 finalize()方法有什么区别?
无论是否抛出异常,finally 代码块都会执行,它主要是用来释放应用占用的资源。
finalize()方法是 Object 类的一个 protected 方法,它是在对象被垃圾回收之前由 Java 虚拟
机来调用的。
.
1.23 public private default protected
访问修饰符作用域
不写时默认为 default。默认对于同一个包中的其他类相当于公开(
public),
对于不是同一个包中的其他类相当于私有(
private)。受保护(
protected)对子类
相当于公开,对不是同一包中的没有父子关系的类相当于私有。
不可以覆盖 private 的方法,因为 private 修饰的变量和方法只能在当前类中使
用,如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆
1.25 Object
hashcode() equals() toString() getClass()
wait notify() notifyAll()
finalize()
1.26 equls 和 == 的区别
https://blog.csdn.net/love_xsq/article/details/43760771StringBuffer 和 StringBuilder 特殊,==和 equal 都是比较地址
1.27 异常
Java 中的两种异常类型是什么?他们有什么区别?
答:Java 中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。
不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的
执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数
的外面。相反,受检查的异常必须要用 throws 语句在方法或者是构造函数上声明。
throw 和 throws 有什么区别?
throw 关键字用来在程序中明确的抛出异常,相反,throws 语句用来表明方法
不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者
才能够确保处理可能发生的异常,多个异常是用逗号分隔的。
java 提供了两种异常机制。一种是运行时异常(RuntimeExepction),一种是检查式异常(checked ex
ecption)。
检查式异常:我们经常遇到的 IO 异常及 sql 异常就属于检查式异常。对于这种异常,java 编译器要
求我们必须对出现的这些异常进行 catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆
catch 来捕捉这些异常。
运行时异常(未受检):我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从
来没有人去处理过 NullPointerException 异常,它就是运行时异常,并且这种异常还是最常见的异常
之一。ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
未检查异常和已检查异常
Java 的异常处理机制, 说说受检异常和运行时异常的区别并举例
Java 异常体系描述一下1.28 序列化序列化,以及 json 传输
1.30 comparable 接口和 comparator 接口
comparable 接口和 comparator 接口实现比较的区别和用法
1.定义
Comparable 接口:使用 Array 或 Collection 的排序方法时,自定义类需要实现 Java
提供 Comparable 接口的 compareTo(TOBJ)方法,它被排序方法所使用,应该重写这个方
法,如果“this”对象比传递的对象参数更小、相等或更大时,它返回一个负整数、0 或正整
数。使用 Comparator 接口的情景:在大多数实际情况下,我们想根据不同参数进行排序。比如,
作为一个 CEO,我想对雇员基于薪资进行排序,一个 HR 想基于年龄对他们进行排序。这
就是我们需要使用 Comparator 接口的情景。因为 Comparable.compareTo(Object o)方法
实现只能基于一个字段进行排序,不能根据需要选择对象字段来对对象进行排序。
Comparator 接口:可以实现两个对象的特定字段的比较(比如,比较员工这个对象的年龄),
该接口的 compare(Objecto1, Object o2)方法的实现需要传递两个对象参数,若第一个参
数小于、等于、大于第二个参数,返回负整数、0、正整数。
2. comparable 接口和 comparator 接口区别
Comparable和Comparator接口被用来对对象集合或者数组进行排序。
Comparable接口被用来提供对象的自然排序,可使用它来提供基于单个逻
辑的排序。
Comparator接口被用来提供不同的排序算法,可根据制定字段选择需要使
用的Comparator来对指定的对象集合进行排序。
1.33 接口和抽象类
1.接口和抽象类的区别
1,抽象类里可以有构造方法,而接口内不能有构造方法。
2,抽象类中可以有普通成员变量,而接口中不能有普通成员变量。
3,抽象类中可以包含非抽象的普通方法,而接口中所有的方法必须是抽象
的,不能有非抽象的普通方法。
4,抽象类中的抽象方法的访问类型可以是public ,protected和private,
但接口中的抽象方法只能是public类型的,并且默认即为public abstract
类型。
5,抽象类中可以包含静态方法,接口内不能包含静态方法。
6,抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的
访问类型可以任意,但接口中定义的变量只能是public static类型,并且
默认为public static final类型。
7,一个类可以实现多个接口,但只能继承一个抽象类。
1. Java 抽象类可以有构造函数吗?
可以有,抽象类可以声明并定义构造函数。因为你不可以创建抽象类的实例,所以构造函
数只能通过构造函数链调用(
Java 中构造函数链指的是从其他构造函数调用一个构造函
数),例如,当你创建具体的实现类。现在一些面试官问,如果你不能对抽象类实例化那
么构造函数的作用是什么?好吧,它可以用来初始化抽象类内部声明的通用变量,并被各
种实现使用。另外,即使你没有提供任何构造函数,编译器将为抽象类添加默认的无参数的构造函数,没有的话你的子类将无法编译,因为在任何构造函数中的第一条语句隐式调
用 super(),Java 中默认超类的构造函数。
2. Java 抽象类可以实现接口吗?它们需要实现所有的方法吗?
可以,抽象类可以通过使用关键字 implements 来实现接口。因为它们是抽象的,所以它
们不需要实现所有的方法。好的做法是,提供一个抽象基类以及一个接口来声明类型 。
这样的例子是,java.util.List 接口和相应的 java.util.AbstractList 抽象类。因为 AbstractList
实现了所有的通用方法,具体的实现像 LinkedList 和 ArrayList 不受实现所有方法的负担,
它们可以直接实现 List 接口。这对两方面都很好,你可以利用接口声明类型的优点和抽象
类的灵活性在一个地方实现共同的行为。Effective Java 有个很好的章节,介绍如何使用
Java 的抽象类和接口,值得阅读。
3. Java 抽象类可以是 final 的吗?
不可以,Java 抽象类不能是 final 的。将它们声明为 final 的将会阻止它们被继承,而这正
是使用抽象类唯一的方法。它们也是彼此相反的,关键字 abstract 强制继承类,而关键字
final 阻止类被扩张。在现实世界中,抽象表示不完备性,而 final 是用来证明完整性。底
线是,你不能让你的 Java 类既 abstract 又 final,同时使用,是一个编译时错误。
4. Java 抽象类可以有 static 方法吗?
可以,抽象类可以声明并定义 static 方法,没什么阻止这样做。但是,你必须遵守 Java
中将方法声明为 static 的准则,
5. 可以创建抽象类的实例吗?
不可以,你不能创建 Java 抽象类的实例,它们是不完全的。即使你的抽象类不包含任何
抽象方法,你也不能对它实例化。将类声明为 abstract 的,就等你你告诉编译器,它是不
完全的不应该被实例化。当一段代码尝试实例化一个抽象类时 Java 编译器会抛错误。
6. 抽象类必须有抽象方法吗?
不需要,抽象类有抽象方法不是强制性的。你只需要使用关键字 abstract 就可以将类声明
为抽象类。编译器会强制所有结构的限制来适用于抽象类,例如,现在允许创建一些实例。
是否在抽象类中有抽象方法是引起争论的。我的观点是,抽象类应该有抽象方法,因为这
是当程序员看到那个类并做假设的第一件事。这也符合最小惊奇原则。
8. 何时选用抽象类而不是接口?
这是对之前抽象类和接口对比问题的后续。如果你知道语法差异,你可以很容易回答这个
问题,因为它们可以令你做出抉择。当关心升级时,因为不可能在一个发布的接口中添加
一个新方法,用抽象类会更好。类似地,如果你的接口中有很多方法,你对它们的实现感
到很头疼,考虑提供一个抽象类作为默认实现。这是 Java 集合包中的模式,你可以使用
提供默认实现 List 接口的 AbstractList。
9. Java 中的抽象方法是什么?
抽象方法是一个没有方法体的方法。你仅需要声明一个方法,不需要定义它并使用关键字
abstract 声明。Java 接口中所有方法的声明默认是 abstract 的。这是抽象方法的例子
public void abstract printVersion();
现在,为了实现这个方法,你需要继承该抽象类并重载这个方法。
10. Java 抽象类中可以包含 main 方法吗?
是的,抽象类可以包含 main 方法,它只是一个静态方法,你可以使用 main 方法执行抽
象类,但不可以创建任何实例。1.34 Socket
Socket 通信具体原理
1.35 Runtime 类
Runtime:运行时,是一个封装了 JVM 的类。每一个 JAVA 程序实际上都是启动了一个
JVM 进程,每一个 JVM 进程都对应一个 Runtime 实例,此实例是由 JVM 为其实例化的。
所以我们不能实例化一个 Runtime 对象,应用程序也不能创建自己的 Runtime 类实例,但
可以通过 getRuntime 方法获取当前 Runtime 运行时对象的引用。一旦得到了一个当前的
Runtime 对象的引用,就可以调用 Runtime 对象的方法去控制 Java 虚拟机的状态和行为。
查看官方文档可以看到,Runtime 类中没有构造方法,本类的构造方法被私有化了, 所
以才会有 getRuntime 方法返回本来的实例化对象,这与单例设计模式不谋而合
public static Runtime getRuntime()
直接使用此静态方法可以取得 Runtime 类的实例
1.36 值传递与引用传递
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本
身 。一般认为,java 内的传递都是值传递. java 中实例对象的传递是引用传递
1.37 泛型 ?与 T 的区别
https://blog.csdn.net/woshizisezise/article/details/79374460
public static <T> void show1(List<T> list){
for (Object object : list) {
System.out.println(object.toString());
}
}
public static void show2(List<?> list) {
for (Object object : list) {
System.out.println(object);
}
}
public static void test(){
List<Student> list1 = new ArrayList<>();
list1.add(new Student("zhangsan",18,0));
list1.add(new Student("lisi",28,0));
list1.add(new Student("wangwu",24,1));
//这里如果 add(new Teacher(...));就会报错,因为我们已经给 List 指定了数据类
型为 Student
show1(list1);System.out.println("************分割线**************");
//这里我们并没有给 List 指定具体的数据类型,可以存放多种类型数据
List list2 = new ArrayList<>();
list2.add(new Student("zhaoliu",22,1));
list2.add(new Teacher("sunba",30,0));
show2(list2);
}
从 show2 方法可以看出和 show1 的区别了,list2 存放了 Student 和 Teacher 两种类型,同样可以输
出数据,所以这就是 T 和?的区别啦
1.38 枚举类型字节码层面理解 Enum
https://blog.csdn.net/javazejian/article/details/71333103
//反编译 Day.class
final class Day extends Enum
{
//编译器为我们添加的静态的 values()方法
public static Day[] values()
{
return (Day[])$VALUES.clone();
}
//编译器为我们添加的静态的 valueOf()方法,注意间接调用了 Enum 也类的 valueOf
方法
public static Day valueOf(String s)
{
return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
}
//私有构造函数
private Day(String s, int i)
{
super(s, i);}
//前面定义的 7 种枚举实例
public static final Day MONDAY;
public static final Day TUESDAY;
public static final Day WEDNESDAY;
public static final Day THURSDAY;
public static final Day FRIDAY;
public static final Day SATURDAY;
public static final Day SUNDAY;
private static final Day $VALUES[];
static
{
//实例化枚举实例
MONDAY = new Day("MONDAY", 0);
TUESDAY = new Day("TUESDAY", 1);
WEDNESDAY = new Day("WEDNESDAY", 2);
THURSDAY = new Day("THURSDAY", 3);
FRIDAY = new Day("FRIDAY", 4);
SATURDAY = new Day("SATURDAY", 5);
SUNDAY = new Day("SUNDAY", 6);
$VALUES = (new Day[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
1.39 java 注解类型1.40 字节流 字符流
https://www.cnblogs.com/huangliting/p/5746950.htmlInputStreamReader 类是从字节流到字符流的桥梁:它读入字节,并根据指定的编
码方式,将之转换为字符流。1.41 静态内部类 匿名类
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为 static。这通常称为
嵌套类(nested class)。Static Nested Class 是被声明为静态(
static)的内部类,它可以不依赖
于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。想要理解 static 应用于
内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对
象。然而,当内部类是 static 的时,就不是这样了。嵌套类意味着:
1. 嵌套类的对象,并不需要其外围类的对象。
2. 不能从嵌套类的对象中访问非静态的外围类对象。
如下所示代码为定义一个静态嵌套类
public class StaticTest{
private static String name = "woobo";
private String num = "X001";
static class Person{ // 静态内部类可以用 public,protected,private 修饰
// 静态内部类中可以定义静态或者非静态的成员
private String address = "China";
Private Static String x=“as”;
public String mail = "kongbowoo@yahoo.com.cn";//内部类公有成员
public void display(){
//System.out.println(num);//不能直接访问外部类的非静态成员
// 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
System.out.println(name);//只能直接访问外部类的静态成员
//静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
System.out.println("Inner " + address);//访问本内部类成员。
}
}
public void printInfo(){
Person person = new Person();
// 外部类访问内部类的非静态成员:实例化内部类即可
person.display();
//System.out.println(mail);//不可访问
//System.out.println(address);//不可访问
System.out.println(person.address);//可以访问内部类的私有成员
System.out.println(Person.x);// 外部类访问内部类的静态成员:内部类.静态成员
System.out.println(person.mail);//可以访问内部类的公有成员
}
public static void main(String[] args){StaticTest staticTest = new StaticTest();
staticTest.printInfo();
}
}
在静态嵌套类内部, 不能访问外部类的非静态成员, 这是由 Java 语法中"静态方法不能直接访问非静
态成员"所限定.注意, 外部类访问内部类的的成员有些特别, 不能直接访问, 但可以通过内部类实例
来访问, 这是因为静态嵌套内的所有成员和方法默认为静态的了.同时注意, 内部静态类 Person 只在
类 StaticTest 范围内可见, 若在其它类中引用或初始化, 均是错误的.
一 . 静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
二 . 静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;
三 . 非静态内部类的非静态成员可以访问外部类的非静态变量。
四.在非静态内部类中不可以声明静态成员,一般的非静态内部类,可以随意的访问外部类中的成员
变量与成员方法。即使这些成员方法被修饰为 private(私有的成员变量或者方法),其非静态内部类都
可以随意的访问。则是非静态内部类的特权。不能够从静态内部类的对象中访问外部类的非静态成员
(包括成员变量与成员方法)。
生成一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对
象可以直接生成:Outer.Inner in = new Outer.Inner();而不需要通过生成外部类对象来生成。这样
实际上使静态内部类成为了一个顶级类(正常情况下,你不能在接口内部放置任何代码,但嵌套类可
以作为接口的一部分,因为它是 static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的
规则)
1.41.2 匿名类
匿名内部类就是没有名字的内部类;
匿名内部类不能定义任何静态成员、方法。
匿名内部类中的方法不能是抽象的;
匿名内部类必须实现接口或抽象父类的所有抽象方法。
匿名内部类访问的外部类成员变量或成员方法必须用 static 修饰
1、匿名内部类因为没有类名,可知匿名内部类不能定义构造器。2、因为在创建匿名内部类的时候,会立即创建它的实例,可知匿名内部类不能是抽象类,必须实现
接口或抽象父类的所有抽象方法。
3、匿名内部类会继承一个父类(有且只有一个)或实现一个接口(有且只有一个),实现父类或接
口中所有抽象方法,可以改写父类中的方法,添加自定义方法。
5、当匿名内部类和外部类有同名变量(方法)时,默认访问的是匿名内部类的变量(方法),要访
问外部类的变量(方法)则需要加上外部类的类名。
Java I/O 底层细节,注意是底层细节,而不是怎么用
如何实现分布式缓存
浏览器的缓存机制
JVM tomcat 容器启动,jvm 加载情况描述
当获取第一个获取锁之后,条件不满足需要释放锁应当怎么做?
HasnMap 实现原理,扩容因子过大过小的缺点,扩容过程 采用什么方法能保证每个 bucket
中的数据更均匀 解决冲突的方式,还有没有其他方式(全域哈希)
Collection 集合类中只能在 Iterator 中删除元素的原因
java 地址和值传递的例子
java NIO,java 多线程、线程池,java 网络编程解决并发量,
java 的 I/O
手写一个线程安全的生产者与消费者。
ConcurrentHashMap 和 LinkedHashMap 差异和适用情形
ConcurrentHashMap 分段锁是如何实现的
ConcurrentHashmap jdk1.8 访问的时候是怎么加锁的,插入的时候是怎么加锁的 访问不加
锁插入的时候对头结点加锁
ArrayDeque 的使用场景
JDBC 连接的过程 手写 jdbc 连接过程
可重入锁,实现原理
Java IO 模型(BIO,NIO 等) Tomcat 用的哪一种模型
ArrayBlockingQueue 源码
多进程和多线程的区别
说出三个遇到过的程序报异常的情况
Java 无锁原理
hashmap 和 treemap 的区别
rehash 过程
网络编程的 accept 和 connect,
HashMap 的负载因子.try catch finally 可不可以没有 catch(
try return,finally return)
mapreduce 流程 如何保证 reduce 接受的数据没有丢失,数据如何去重 mapreduce 原理,
partion 发生在什么阶段
直接写一个 java 程序,统计 IP 地址的次数
讲讲多线程,多线程的同步方法
list,map,set 之间的区别
socket 是靠什么协议支持的
java io 用到什么设计模式
serviable 的序列化,其中 uuid 的作用
什么时候会用到 HashMap
什么情景下会用到反射(答注解、Spring 配置文件、动态代理)
浅克隆与深克隆有什么区别
如何实现深克隆
常见的线程安全的集合类
Java 8 函数式编程
回调函数,函数式编程,面向对象之间区别
Java 8 中 stream 迭代的优势和区别?
同步等于可见性吗?保证了可见性不等于正确同步,因为还有原子性没考虑。
还了解除 util 其他包下的 List 吗?CopyOnWriteArrayList
反射能够使用私有的方法属性吗和底层原理?
处理器指令优化有些什么考虑? 禁止重排序
object 对象的常用方法
Stack 和 ArrayList 的区别
statement 和 prestatement 的区别
手写模拟实现一个阻塞队列
怎么使用父类的方法
util 包下有哪几种接口
cookie 禁用怎么办
Netty
new 实例化过程
socket 实现过程,具体用的方法;怎么实现异步 socket.
很常见的 Nullpointerexception ,你是怎么排查的,怎么解决的;
Binder 的原理
C++/JAVA/C 的区别
java 线程安全都体现在哪些方面,如果维护线程安全
如果想实现一个线程安全的队列,可以怎么实现?
JUC 包里的 ArrayBlockingQueue 还有 LinkedBlockingQueue 啥的又结合源码说了一
通。
静态内部类和非静态内部类的区别是什么?怎么创建静态内部类和非静态内部类?
。
断点续传的原理
Xml 解析方式,原理优缺点
数据缓存处理
四大组件生命周期Java 和 JavaScript 的区别
信号量
静态变量和全局变量的区别
二.集合类 Set
说出所有 java 集合类
2.1 HashMap
https://blog.csdn.net/jiary5201314/article/details/51439982
(1)
hashMap 的原理
2.hashcode 的计算
https://www.zhihu.com/question/20733617/answer/111577937
https://blog.csdn.net/justloveyou_/article/details/62893086
Key.hashcode 是 key 的自带的 hascode 函数是一个 int 值 32 位(2) hashMap 参数以及扩容机制
(
3)get
因为整个过程都不需要加锁
(
4)HashMap 的 put 方法源码
这里 HashMap 里面用到链式数据结构的一个概念。上面我们提到过 Entry 类里面
有一个 next 属性,作用是指向下一个 Entry。打个比方, 第一个键值对 A 进来,通过
计算其 key 的 hash 得到的 index=0,记做:Entry[0] = A。一会后又进来一个键值对 B,通过计算其 index 也等于 0,现在怎么办?HashMap 会这样做:B.next = A,Entry[0] = B,
如果又进来 C,index 也等于 0,那么 C.next = B,Entry[0] = C;这样我们发现 index=0 的
地方其实存取了 A,B,C 三个键值对,他们通过 next 这个属性链接在一起。所以疑问不用
担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap 的大致实现。
(
5)HashMap 问题 jdk1.8 优化
(1)HashMap 如果有很多相同 key,后面的链很长的话,你会怎么优化?或者你
会用什么数据结构来存储?针对 HashMap 中某个 Entry 链太长,查找的时间复杂度可能达
到 O(n),怎么优化?HashMap 为什么可以插入空值?
6.Hashmap 为什么线程不安全(
hash 碰撞和扩容导致)
HashMap 扩容的的时候可能会形成环形链表,造成死循环。
要想实现线程安全,那么需要调用 collections 类的静态方法
synchronizeMap()实现
7.Hashmap 中的 key 可以为任意对象或数据类型吗?2.2 CurrentHashMap
http://www.importnew.com/21781.html
https://blog.csdn.net/dingji_ping/article/details/51005799
https://www.cnblogs.com/chengxiao/p/6842045.html
http://ifeve.com/hashmap-concurrenthashmap-%E7%9B%B8%E4%BF%A1%E7%9C%8B%
E5%AE%8C%E8%BF%99%E7%AF%87%E6%B2%A1%E4%BA%BA%E8%83%BD%E9%9A%BE%E4%BD%8
F%E4%BD%A0%EF%BC%81/
一个 ConcurrentHashMap 维护一个 Segment 数组,一个 Segment 维护一
个 HashEntry 数组。
0. JDK1.7 ConCurrentHashMap 原理
其中 Segment 继承于 ReentrantLockSegment 继承了 ReentrantLock,表明每个 segment 都可以当做一个锁。这
样对每个 segment 中的数据需要同步操作的话都是使用每个 segment 容器对象自
身的锁来实现。只有对全局需要改变时锁定的是所有的 segment。
2.ConCurrentHashMap Segment 内部 Get PUT REMOVE
1. JDK1.7 Get
CurrentHashMap 是否使用了锁???
它也没有使用锁来同步,只是判断获取的 entry 的 value 是否为 null,为
null 时才使用加锁的方式再次去获取。 这里可以看出并没有使用锁,但是
value 的值为 null 时候才是使用了加锁!!!
Get 原理:
第一步,先判断一下 count != 0;count 变量表示 segment 中存在 entry
的个数。如果为 0 就不用找了。假设这个时候恰好另一个线程 put 或者 remove
了这个 segment 中的一个 entry,会不会导致两个线程看到的 count 值不一致
呢?看一下 count 变量的定义: transient volatile int count;
它使用了 volatile 来修改。我们前文说过,Java5 之后,JMM 实现了对 volatile的保证:对 volatile 域的写入操作 happens-before 于每一个后续对同一个域
的读写操作。所以,每次判断 count 变量的时候,即使恰好其他线程改变了
segment 也会体现出来
1) 在 get 代码的①和②之间,另一个线程新增了一个 entry
如果另一个线程新增的这个 entry 又恰好是我们要 get 的,这事儿就比较微妙
了。下图大致描述了 put 一个新的 entry 的过程。
因为每个 HashEntry 中的 next 也是 final 的,没法对链表最后一个元素增
加一个后续 entry 所以新增一个 entry 的实现方式只能通过头结点来插入了。
newEntry 对象是通过 new HashEntry(K k , V v, HashEntry next) 来创建的。
如果另一个线程刚好 new 这个对象时,当前线程来 get 它。因为没有同步,
就可能会出现当前线程得到的 newEntry 对象是一个没有完全构造好的对象引
用。
如果在这个 new 的对象的后面,则完全不影响,如果刚好是这个 new
的对象,那么当刚好这个对象没有完全构造好,也就是说这个对象的 value 值
为 null,就出现了如下所示的代码,需要重新加锁再次读取这个值!2) 在 get 代码的①和②之间,另一个线程修改了一个 entry 的 value
value 是用 volitale 修饰的,可以保证读取时获取到的是修改后的值。
3) 在 get 代码的①之后,另一个线程删除了一个 entry
假设我们的链表元素是:e1-> e2 -> e3 -> e4 我们要删除 e3 这个 entry,
因为 HashEntry 中 next 的不可变,所以我们无法直接把 e2 的 next 指向 e4,
而是将要删除的节点之前的节点复制一份,形成新的链表。它的实现大致如下
图所示:
如果我们 get 的也恰巧是 e3,可能我们顺着链表刚找到 e1,这时另一个
线程就执行了删除 e3 的操作,而我们线程还会继续沿着旧的链表找到 e3 返
回。这里没有办法实时保证了,也就是说没办法看到最新的。
我们第①处就判断了 count 变量,它保障了在 ①处能看到其他线程修改
后的。①之后到②之间,如果再次发生了其他线程再删除了 entry 节点,就没
法保证看到最新的了,这时候的 get 的实际上是未更新过的!!!。
不过这也没什么关系,即使我们返回 e3 的时候,它被其他线程删除了,
暴漏出去的 e3 也不会对我们新的链表造成影响。
2. JDK1.7 PUT
1.
将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
2.
遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,
相等则覆盖旧的 value。3.
不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需
要扩容。
4.
最后会解除在 1 中所获取当前 Segment 的锁。
可以说是首先找到 segment,确定是哪一个 segment,然后在这个 segment
中遍历查找 key 值是要查找的 key 值得 entry,如果找到,那么就修改该 key,
如果没找到,那么就在头部新加一个 entry.3. JDK1.7 Remove4.JDK1.7 & JDK1.8 size()
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
volatile 保证内存可见,最大是 65535.
5.JDK 1.81. 其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发
安全性。
2. 大于 8 的时候才去红黑树链表转红黑树的阀值,当 table[i]下面的链表长度大于 8
时就转化为红黑树结构。
6.JDK1.8 put
根据 key 计算出 hashcode 。
判断是否需要进行初始化。
f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS
尝试写入,失败则自旋保证成功。
如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
如果都不满足,则利用 synchronized 锁写入数据(分为链表写入和红黑树写入)。
如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。7. JDK1.8 get 方法
根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
如果是红黑树那就按照树的方式获取值。
就不满足那就按照链表的方式遍历获取值。
8. rehash 过程
Redis rehash :dictRehash 每次增量 rehash n 个元素,由于在自动调整大小时已设置好
了 ht[1]的大小,因此 rehash 的主要过程就是遍历 ht[0],取得 key,然后将该 key 按 ht[1]的 桶的大
小重新 rehash,并在 rehash 完后将 ht[0]指向 ht[1],然后将 ht[0]清空。在这个过程中 rehashidx 非常
重要,它表示上次 rehash 时在 ht[0]的下标位置。
可以看到,redis 对 dict 的 rehash 是分批进行的,这样不会阻塞请求,设计的比较优雅。
但是在调用 dictFind 的时候,可能需要对两张 dict 表做查询。唯一的优化判断是,当 key 在 ht[0]不
存在且不在 rehashing 状态时,可以速度返回空。如果在 rehashing 状态,当在 ht[0]没值的时候,还
需要在 ht[1]里查找。
dictAdd 的时候,如果状态是 rehashing,则把值插入到 ht[1],否则 ht[0]
2.3 . Hashtable
https://blog.csdn.net/ns_code/article/details/36191279
0.参数
(1)table 是一个 Entry[]数组类型,而 Entry 实际上就是一个单向链表。哈希表的
"key-value 键值对"都是存储在 Entry 数组中的。
(
2)count 是 Hashtable 的大小,它是 Hashtable 保存的键值对的数量。
(3)threshold 是 Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量。
threshold 的值="容量*加载因子"。(
4)loadFactor 就是加载因子。
(
5)modCount 是用来实现 fail-fast 机制的
1.put
从下面的代码中我们可以看出,Hashtable 中的 key 和 value 是不允许为空的,当我们
想要想 Hashtable 中添加元素的时候,首先计算 key 的 hash 值,然
后通过 hash 值确定在 table 数组中的索引位置,最后将 value 值替换或者插入新的元
素,如果容器的数量达到阈值,就会进行扩充。
2.get3.Remove
在下面代码中,如果 prev 为 null 了,那么说明第一个元素就是要删除的元素,那么就
直接指向第一个元素的下一个即可。
4.扩容
默认初始容量为 11
线程安全,但是速度慢,不允许 key/value 为 null
加载因子为 0.75:即当 元素个数 超过 容量长度的 0.75 倍 时,进
行扩容
扩容增量:2*原数组长度+1
如 HashTable 的容量为 11,一次扩容后是容量为 232.4 hashtable 和 hashmap 的区别
2.5 HashMap 和 ConCurrentHashMap 区别
2.6 ConcurrentHashMap 和 HashTable 区别
ConcurrentHashMap 仅仅锁定 map 的某个部分,而 Hashtable 则会锁定整个 map。
hashtable(同一把锁):使用 synchronized 来保证线程安全,但效率非常低下。当一个线
程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put
添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越
低。
concurrenthashmap(分段锁):(锁分段技术)每一把锁只锁容器其中一部分数据,多线程访问容
器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。首先将数据分为一段一段的
存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数
据也能被其他线程访问。concurrenthashmap 是由 Segment 数组结构和 HahEntry 数组结构
组成。Segment 是一种可重入锁 ReentrantLock,扮演锁的角色。HashEntry 用于存储键值对
数据。一个 concurrenthashmap 里包含一个 Segment 数组。Segment 的结构和 Hashmap
类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry
是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry
数组的数据进行修改时,必须首先获得对应的 Segment。2.7 linkedHashMap
https://blog.csdn.net/justloveyou_/article/details/71713781
2.8 Linkedhashmap 与 hashmap 的区别
1. LinkedHashMap 是 HashMap 的子类
2. LinkedHashMap 中的 Entry 增加了两个指针 before 和 after,它们分别用于维护
双向链接列表。
3. 在 put 操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 put 操作,但是在细
节上还是做了一定的调整,比如,在 LinkedHashMap 中向哈希表中插入新 Entry
的同时,还会通过 Entry 的 addBefore 方法将其链入到双向链表中。
4. 在扩容操作上,虽然 LinkedHashMap 完全继承了 HashMap 的 resize 操作,但是
鉴于性能和 LinkedHashMap 自身特点的考量,LinkedHashMap 对其中的重哈希过
程(transfer 方法)进行了重写
5. 在读取操作上,LinkedHashMap 中重写了 HashMap 中的 get 方法,通过 HashMap
中的 getEntry 方法获取 Entry 对象。在此基础上,进一步获取指定键对应的值。
2.9 HashSet
对于 HashSet 而言,它是基于 HashMap 实现的
Hashset 源码 http://zhangshixi.iteye.com/blog/673143
Hashset 如何保证集合的没有重复元素?
可以看出 hashset 底层是 hashmap 但是存储的是一个对象,hashset 实际将该元素 e 作为 key 放入 hashmap,当 key 值(该元素 e)相同时,只是进行更新 value,并不会新增
加,所以 set 中的元素不会进行改变。
2.10 hashmap 与 hashset 区别2.11 Collections.sort 内部原理
重写 Collections.sort()
import java.util.*;
class xd{
int a;
int b;
xd(int a,int b){
this.a = a;
this.b = b;
}
}
public class Main {
public static void main(String[] arg) {
xd a = new xd(2,3);
xd b = new xd(4,1);
xd c = new xd(1,2);
ArrayList<xd> array = new ArrayList<>();
array.add(a);
array.add(b);
array.add(c);
Collections.sort(array, new Comparator<xd>() {
@Override
public int compare(xd o1, xd o2) {
if(o1.a > o2.a)
return 1;
else if(o1.a < o2.a)
return -1;
return 0;
}
});for(int i=0;i<array.size();i++)
System.out.println(array.get(i).a);
for(int i=0;i<array.size();i++)
System.out.println(array.get(i).b);
}
}
2.12 hash 算法
java map 底层实现,最好看源码,还有各种集合类的区别
4. TreeMap 和 TreeSet 区别和实现原理
5. 集合和有序集合有什么区别
6. Set 是无序的,那怎么保证它有序?有什么办法吗?提到了 TreeSet,那说一下
TreeSet 为什么能够保证有序?
7. java 中 hashMap 结构,处理冲突方法,还有啥方法,各个方法优缺点
.Collections.sort() 的原理
集合框架的理解 对 Java 的集合框架有什么样的了解, 用过哪些集合类, 各自的效率以及
适用场景
cas 的实现原理,以及 aba 问题
List/Set/Queue 接口及其实现类
HashSet/TreeSet/HashMap/TreeMap/hashTable 这些类的底层实现。
常问: hashSet 和 HashMap 有什么区别 。各自的底层实现是基于什么的。
这里紧接着的问题通常是:我们来聊聊多线程(微笑脸),或者我们来聊聊红
黑树。2.13 迭代器 Iterator Enumeration
1. Iterator 和 ListIterator 的区别是什么?
答:Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍
历 List。Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后
向。
ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替
换元素,获取前一个和后一个元素的索引,等等。
4. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
答:Iterator 的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修
改 的 影 响 。 java.util 包 下 面 的 所 有 的 集 合 类 都 是 快 速 失 败 的 , 而
java.util.concurrent 包下面的所有的类都是安全失败的。快速失败的迭代器
会抛出 ConcurrentModificationException 异常,而安全失败的迭代器永远不会
抛出这样的异常。
5. Enumeration 接口和 Iterator 接口的区别有哪些?
答:Enumeration 速度是 Iterator 的 2 倍,同时占用更少的内存。但是,
Iterator 远远比 Enumeration 安全,因为其他线程不能够修改正在被
iterator 遍历的集合里面的对象。同时,Iterator 允许调用者删除底层集
合里面的元素,这对 Enumeration 来说是不可能的。
2.14 LIST ArrayList,LinkedList 和 Vector 的区别和实现原理
Vector : https://blog.csdn.net/chenssy/article/details/37520981
一.ArrayList 与 LinkedList 区别
ArrayList 和 LinkedList 都实现了 List 接口,他们有以下的不同点:
ArrayList 是基于索引的数据接口,它的底层是数组。它可以以 O(1)时间复杂度对元素进行
随机访问。与此对应,LinkedList 是以元素列表的形式存储它的数据,每一个元素都和它的
前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是 O(n)。
相对于 ArrayList,LinkedList 的插入,添加,删除操作速度更快,因为当元素被添加到集合
任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList 比 ArrayList 更占内存,因为 LinkedList 为每一个节点存储了两个引用,一个指
向前一个元素,一个指向下一个元素。
二.Vetor arraylist Linkedlist 区别
ArrayList 就是动态数组,是 Array 的复杂版本,动态的增加和减少元素.当更多的元素
加入到 ArrayList 中时,其大小将会动态地增长。它的元素可以通过 get/set 方法直接访问,
因为 ArrayList 本质上是一个数组。初始容量为 10。1.插入元素的时候可能扩容,删除元素时
不会缩小容量。2.扩容增长为 Arraylist 增长原来的 0.5 倍 3. 而 Arraylist 没有设置增长空间
的方法。4.线程不同步
Vector 和 ArrayList 类似, 区别在于 Vector 是同步类(synchronized).因此,开销就比
ArrayList 要大。初始容量为 10。实现了随机访问接口,可以随机访问。Vector 是内部是以动态数组的形式来存储数据的。1.Vector 还可以设置增长的空间大小,2. 及 Vector 增长原
来的 1 倍 3.vector 线程同步
LinkedList 是一个双链表,在添加和删除元素时具有比 ArrayList 更好的性能.但在 get
与 set 方面弱于 ArrayList.当然,这些对比都是指数据量很大或者操作很频繁的情况下的对
比。它还实现了 Queue 接口,该接口比 List 提供了更多的方法,包括 offer(),peek(),poll()等.
ArrayList 和 LinkedList 的使用场景,其中 add 方法的实现 ArrayList,LinkedList 的实现
以及插入,查找,删除的过程
Arraylist 如何实现排序
三.使用 ArrayList 的迭代器会出现什么问题?单线程和多线程环境下;
答:常用的迭代器设计模式,iterator 方法返回一个父类实现的迭代器。
1、迭代器的 hasNext 方法的作用是判断当前位置是否是数组最后一个位置,相等为 false,
否则为 true。
2、迭代器 next 方法用于返回当前的元素,并把指针指向下一个元素,值得注意的是,每次
使用 next 方法的时候,都会判断创建迭代器获取的这个容器的计数器 modCount 是否与此
时 的 不 相 等 , 不 相 等 说 明 集 合 的 大 小 被 修 改 过 , 如 果 是 会 抛 出
ConcurrentModificationException 异常,如果相等调用 get 方法返回元素即可。
四.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用 Array 而不是
ArrayList?
答:不同点:定义上:Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类
型。容量上:Array 大小固定,ArrayList 的大小是动态变化的。操作上:ArrayList 提供更多
的方法和特性,如:addAll(),removeAll(),iterator()等等。使用基本数据类型或者知道数
据元素数量的时候可以考虑 Array;ArrayList 处理固定数量的基本类型数据类型时会自动装
箱来减少编码工作量,但是相对较慢。
五.ArrayList 和 Vector 有何异同点?
相同点:
(1)两者都是基于索引的,都是基于数组的。
(
2)两者都维护插入顺序,我们可以根据插入顺序来获取元素。
(
3)ArrayList 和 Vector 的迭代器实现都是 fail-fast 的。
(
4)ArrayList 和 Vector 两者允许 null 值,也可以使用索引值对元素进行随机访问。
不同点:
(1)Vector 是同步,线程安全,而 ArrayList 非同步,线程不安全。对于 ArrayList,如果
迭代时改变列表,应该使用 CopyOnWriteArrayList。(
2)但是,ArrayList 比 Vector 要快,它因为有同步,不会过载。
(
3)在使用上,ArrayList 更加通用,因为 Collections 工具类容易获取同步列表和只读列
表。
2.15 快速失败(fail-fast)和安全失败(fail-safe)
一:快速失败(
fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增
加、删除、修改),则会抛出 Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCo
unt 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使
用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmod
Count 值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如
果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会
抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检
测并发修改的 bug。
场景:java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过
程中被修改)。
二:安全失败(
fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原
有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改
并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,
迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,
在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并
发修改。
快速失败和安全失败是对迭代器而言的。 快速失败:当在迭代一个集合的时候,如果有另
外一个线程在修改这个集合,就会抛出 ConcurrentModification 异常,java.util 下都是快速
失败。 安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影
响下层。在 java.util.concurrent 下都是安全失败三
锁
volatile synchronized Lock
ReentrantLock AQS CAS
3.1 .volatile 和 synchronized
1.volatile 和 synchronized 实现原理3.1.1 Volatile 与 synchronized 区别
3.1.2 Volatile
3.1.3 Synchronized 原理
https://blog.csdn.net/javazejian/article/details/72828483
https://blog.csdn.net/chen77716/article/details/6618779
Synchronized 是可重入的
Jvm 对象都有对象头,对象头是实现 synchronized 的基础,对象头都有如下结构而 Mark Word 的结构(不固定)如下所示
从上图可以看到 Mark Word 包括重量级锁,重量级锁的指针指向 monitor 对象(监
视器锁)所以每一个对象都与一个 monitor 关联,当一个 monitor 被某个线程持有后,
它便处于锁定状态。谈及monitor来看一下monitord的实现,monitor是由ObjectMonitor
实现的,其主要数据结构如下
当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线
程:
Contention List:所有请求锁的线程将被首先放置到该竞争队列
Entry List:Contention List 中那些有资格成为候选人的线程被移到 Entry List
Wait Set:那些调用 wait 方法被阻塞的线程被放置到 Wait Set
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为 OnDeck
Owner:获得锁的线程称为 Owner
!Owner:释放锁的线程
synchronizated 和 lock 差别 Lock 多出的三大优势(尝试非阻塞加锁,尝试超时加锁,
尝试可相应中断加锁)
Lock 内部实现
synchronized 可以替代读写锁吗
ReentrantLock 源码
volatile 理解,这个问题很常见,答出要点: 可见性、防止指令重排即可
java 锁机制
java 中的同步机制,,锁(重入锁)机制,其他解决同步的方 volatile 关键字 ThreadLocal
类的实现原理要懂。
要减小锁粒度呢(用 Java 提供的原子类,比如 AtomicInteger),
AtomicInteger 怎么实现原子修改的(核心方法是 compareAndSwap 方法,俗
称 CAS,源代码没有公开),CAS 方法的主要功能是什么?
Java 编程写一个会导致死锁的程序3.2 CAS
https://www.jianshu.com/p/fb6e91b013cc
CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当
且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并
返回 false。
实 现 原 理 : CAS 中 有 Unsafe 类 中 的 compareAndSwapInt 方 法 , Unsafe 类 中 的
compareAndSwapInt,是一个本地方法,该方法的实现位于 unsafe.cpp 中
Unsafe.cpp:
先想办法拿到变量 value 在内存中的地址。
通过 Atomic::cmpxchg 实现比较替换,其中参数 x 是即将更新的值,参数 e 是原内存的值。
其中 Atomic::cmpxchg 中对此指令加 lock 前缀,而 lock 前缀有两个特性 1,禁止该指令与
前面和后面的读写指令重排序 2,把写缓冲区的所有数据刷新到内存中
这两点保证了内存屏障效果,保证了 CAS 同时具有 volatile 读和 volatile 写的内存语义。
CAS 解读示例:
3.3 可重入锁 ReentrantLock
https://www.cnblogs.com/xrq730/p/4979021.html
1.5.3 乐观锁和悲观锁 阻塞锁,自旋锁,偏向锁,轻量锁,
重量锁。公平锁 非公平锁
自旋锁引入背景:那些处于 ContetionList、EntryList、WaitSet 中的线程均处于阻塞状态,阻塞操作由操作系统完成(在 Linxu 下通过 pthread_mutex_lock 函数)。线程被
阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,
严重影响锁的性能
偏向锁引入背景:避免重入锁的 CAS 操作
https://www.tuicool.com/articles/YVrQFj
1, 公平锁与非公平锁
公平锁和非公平锁,顾名思义,公平锁就是获得锁的顺序按照先到先得的原则,从
实现上说,要求当一个线程竞争某个对象锁时,只要这个锁的等待队列非空,就必
须把这个线程阻塞并塞入队尾(插入队尾一般通过一个 CAS 保持插入过程中没有
锁释放)。相对的,非公平锁场景下,每个线程都先要竞争锁,在竞争失败或当前
已被加锁的前提下才会被塞入等待队列,在这种实现下,后到的线程有可能无需进
入等待队列直接竞争到锁。
3.3 ReentrantLock 和 synchronized 区别
synchronized 原语和 ReentrantLock 在一般情况下没有什么区别,但是在非
常复杂的同步应用中,请考虑使用 ReentrantLock,特别是遇到下面 2 种需求的
时候。
1.某个线程在等待一个锁的控制权的这段时间需要中断
2.需要分开处理一些 wait-notify,ReentrantLock 里面的 Condition 应用,能够控
制 notify 哪个线程
3.具有公平锁功能,每个到来的线程都将排队等候
3. ReentrantLock 可中断 可超时尝试非阻塞加锁,尝试超时加锁,尝试可相应
中断加锁
4. ReentrantLocklock():获取锁,如果锁被暂用则一直等待
unlock():释放锁
tryLock(): 注意返回类型是 boolean,如果获取锁的时候锁被占用就返回 false,
否则返回 true
tryLock(long time, TimeUnit unit):比起 tryLock()就是给了一个时间期限,保
证等待参数时间
lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,
那么可以中断此线程,先去做别的事
1.5.3 重入锁、对象锁、类锁的关系
四 java 多线程;
https://blog.csdn.net/evankaka/article/details/44153709#t3
https://www.cnblogs.com/gaopeng527/p/4234211.html
https://www.cnblogs.com/lixuan1998/p/6937986.html
https://www.cnblogs.com/felixzh/p/6036074.html
4.1 .如何创建线程?哪种好?
有 4 种方式可以用来创建线程:1.继承 Thread 类 2.实现 Runnable 接口 3.应用程
序可以使用 Executor 框架来创建线程池 4. 实现 Callable 接口
实现 Runnable 接口比继承 Thread 类所具有的优势:1):适合多个相同的程序代
码的线程去处理同一个资源 2):可以避免 java 中的单继承的限制 3):增加程序的健
壮性,代码可以被多个线程共享,代码和数据独立 4):线程池只能放入实现 Runabl
e 或 callable 类线程,不能直接放入继承 Thread 的类 5)runnable 实现线程可以对线
程进行复用,因为 runnable 是轻量级的对象,重复 new 不会耗费太大资源,而 Threa
d 则不然,它是重量级对象,而且线程执行完就完了,无法再次利用4.2.线程状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
3、运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止
运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行 wait()方法,JVM 会把该线程放入等待池中。(wait
会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,
则 JVM 会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM
会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处
理完毕时,线程重新转入就绪状态。(注意,sleep 是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命
周期。
4.3.一般线程和守护线程的区别
所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回
收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,
当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来
说,只要任何非守护线程还在运行,程序就不会终止。区别:唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,
如果全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可以理解
为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的线程;比如 JVM
的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没
事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。
在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在 thread.start()之前设置,否则会跑出一个
IllegalThreadStateException 异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在 Daemon 线程中产生的新线程也是 Daemon 的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一
个操作的中间发生中断。
4.4.sleep wait yield notify notifyAll join
一.Sleep 与 wait 区别
1. sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其
他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 sleep()
使当前线程进入阻塞状态,在指定时间内不会执行。
2. wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待
此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象
锁定池准备获得对象锁进入运行状态。
区别比较:
1、这两个方法来自不同的类分别是 Thread 和 Object
2、最主要是 sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同
步控制块或者方法。
3、wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可
以在任何地方使用(使用范围)
4、sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常
(1) sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时
间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程
的运行也需要时间,一个线程对象调用了 sleep 方法之后,并不会释放他所持有的所有对象
锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象
调用它的 interrupt(),产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程
就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继
续执行 catch 语句块(可能还有 finally 语句块)以及以后的代码。
注意 sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep()让 t
对象进入 sleep,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程
(2) wait 属于 Object 的成员方法,一旦一个对象调用了 wait 方法,必须要采用 notify()
和 notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了 wait()
后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了 wait()方法的对象。
wait()方法也同样会在 wait 的过程中有可能被其他对象调用 interrupt()方法而产生
二 yield join notify notifyAll
yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。
如果没有的话,那么 yield()方法将不会起作用,并且由可执行状态后马上又被执行。join 方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执
行结束后,再继续执行当前线程。如:
t.join();//主要用于等待 t 线程运行结束,若无此句,
main 则会执行完毕,导致结果不可预测。
notify 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程
等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管
理的实现。
notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系
统的实现
4.5 中断线程
中断线程有很多方法:(1)使用退出标志,使线程正常退出,也就是当 run 方法完成
后线程终止。(
2)通过 return 退出 run 方法(
3)通过对有些状态中断抛异常退出
thread.interrupt() 中断。(
4)使用 stop 方法强行终止线程(过期)
中断线程可能出现的问题:
使用:Thread.interrupt()并不能使得线程被中断,线程还是会执行。最靠谱的方法就是
设置一个全局的标记位,然后再 Thread 中去检查这个标记位,发现标记位改变则中断线程。
4.6 多线程如何避免死锁
什么是死锁?所谓死锁是指多个进 程因竞争资源而造成的一种僵局(互相等待),若无外力作用,
这些进程都将无法向前推进。死锁产生的 4 个必要条件:
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一
段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程
只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺
走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,
而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不
放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的
资源同时被 链中下一个进程所请求。
如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,
并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加
锁和释放锁,就不会出现死锁了。
https://blog.csdn.net/ls5718/article/details/51896159
1.加锁顺序(线程按照一定的顺序加锁)
2.加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,
并释放自己占有的锁)
3.死锁检测
4,7 多线程的好处以及问题
(1)发挥多核 CPU 的优势
(
2)防止阻塞
(
3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要
考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,
任务 B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那
就简单很多了。
问题:线程安全问题
4.8 多线程共用一个数据变量注意什么?4.9 线程通信方式
1. 同步
同步是指多个线程通过 synchronized 关键字这种方式来实现线程间的通信。
2.wait/notify 机制
4.10 线程池4.11.线程中抛出异常怎么办
当单线程的程序发生一个未捕获的异常时我们可以采用 try....catch 进行异常的捕获,但
是在多线程环境中,线程抛出的异常是不能用 try....catch 捕获的,这样就有可能导致一
些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。
简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHan
dler 是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常
将造成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler()来查询线程
的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的 uncaughtEx
ception()方法进行处理。java 线程池达到提交上限的具体情况
线程池用法。
Java 多线程,线程池有哪几类,每一类的差别 .要你设计的话,如何实现一个线程池 线
程池的类型,固定大小的线程池内部是如何实现的,等待队列是用了哪一个队列实现
线程池种类和工作流程(重点讲 newcached 线程池)
线程池工作原理
比如 corePoolSize 和 maxPoolSize 这两个参数该怎么调
线程池使用了什么设计模式
线程池使用时一般要考虑哪些问题
线程池的配置 Excutor 以及 Connector 的配置、
AysncTask 每来一个任务都会创建一个线程来执行吗?(否,线程池的方式实现的)
介绍下 AsyncTask 的实现原理;
五.
Java 进阶 ssh/ssm 框架
2.1Spring
2.1.0 什么是 Spring 以及优点
1. 什么是 spring?
Spring 是个 java 企业级应用的开源开发框架。Spring 主要用来开发 Java 应用,但是有些扩展是针对构建 J2EE 平台的
web 应用。Spring 框架目标是简化 Java 企业级应用开发,并通过 POJO 为基础的编程模型促进良好的编程习惯。
2. 使用 Spring 框架的好处是什么?
轻量:Spring 是轻量的,基本的版本大约 2MB。
控制反转:Spring 通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
面向切面的编程(AOP):Spring 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
容器:Spring 包含并管理应用中对象的生命周期和配置。
MVC 框架:Spring 的 WEB 框架是个精心设计的框架,是 Web 框架的一个很好的替代品。
事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(
JTA)。
异常处理:Spring 提供方便的 API 把具体技术相关的异常(比如由 JDBC,Hibernate or JDO 抛出的)转化
为一致的 unchecked 异常。
2.1.1 ApplicationContext 和 beanfactory 的区别
1. 利用 MessageSource 进行国际化
BeanFactory 是不支持国际化功能的,因为 BeanFactory 没有扩展 Spring 中MessageResource 接口。相反,由于 ApplicationContext 扩展了 MessageResource
接口,因而具有消息处理的能力(i18N),具体 spring 如何使用国际化,以后章节会详细
描述。
2.强大的事件机制(Event)
基本上牵涉到事件(Event)方面的设计,就离不开观察者模式。不明白观察者模式的朋
友,最好上网了解下。因为,这种模式在 java 开发中是比较常用的,又是比较重要的。
ApplicationContext 的事件机制主要通过 ApplicationEvent 和 ApplicationListener 这两
个接口来提供的,和 java swing 中的事件机制一样。即当 ApplicationContext 中发布一
个事件的时,所有扩展了 ApplicationListener 的 Bean 都将会接受到这个事件,并进行
相应的处理。
3.底层资源的访问
ApplicationContext 扩展了 ResourceLoader(资源加载器)接口,从而可以用来加载多
个 Resource,而 BeanFactory 是没有扩展 ResourceLoader
4. .BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个
Bean 时(调用 getBean()),才对该 Bean 进行加载实例化,这样,我们就不能发现
一些存在的 Spring 的配置问题。而 ApplicationContext 则相反,它是在容器启动
时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring
中存在的配置错误。
2.1.2 Spring Bean 生命周期
Spring 上下文中的 Bean 也类似,如下
1、实例化一个 Bean--也就是我们常说的 new;
2、按照 Spring 上下文对实例化的 Bean 进行配置--也就是 IOC 注入;
3、如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanNa
me(String)方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
4、如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanF
actory(setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来
获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以);
5、如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用 setApplica
tionContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也可以实现
步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接口,有更
多的实现方法);
6、如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBefore
Initialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是 Bean
内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应用于内存
或缓存技术;
7、如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初
始化方法。8、如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterIn
itialization(Object obj, String s)方法、;
注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,
所以一般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring
配置文件中也可以配置非 Singleton,这里我们不做赘述。
9、当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个
接口,会调用那个其实现的 destroy()方法;
10、最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调
用其配置的销毁方法。
2.1.3 spring 中 bean 的作用域
默认是单例
作用域
字符
描述
单例
singleton 整个应用中只创建一个实例
原型
prototype 每次注入时都新建一个实例
会话
session
为每个会话创建一个实例
请求
request
为每个请求创建一个实例
2.1.4 Spring IOC
https://blog.csdn.net/it_man/article/details/4402245
IOC:,IOC 利用 java 反射机制,AOP 利用代理模式。所谓控制反转是指,本来被调用者的
实例是有调用者来创建的,这样的缺点是耦合性太强,IOC 则是统一交给 spring 来管理创建,将对
象交给容器管理,你只需要在 spring 配置文件总配置相应的 bean,以及设置相关的属性,让 spring
容器来生成类的实例对象以及管理对象。在 spring 容器启动的时候,spring 会把你在配置文件中配置
的 bean 都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些 bean 分配给你需要调
用这些 bean 的类。
IoC 的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通
过 DI(Dependency Injection,依赖注入)来实现的。比如对象 A 需要操作数据库,以前我们总是
要在 A 中自己编写代码来获得一个 Connection 对象,有了 spring 我们就只需要告诉 spring,A 中需
要一个 Connection,至于这个 Connection 怎么构造,何时构造,A 不需要知道。在系统运行时,sp
ring 会在适当的时候制造一个 Connection,然后像打针一样,注射到 A 当中,这样就完成了对各个
对象之间关系的控制。A 需要依赖 Connection 才能正常运行,而这个 Connection 是由 spring 注入
到 A 中的,依赖注入的名字就这么来的。那么 DI 是如何实现的呢? Java 1.3 之后一个重要特征是反
射(
reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,s
pring 就是通过反射来实现注入的。
2.1.5 Spring AOP
2.5.1.1.什么是 AOPAOP 技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类
的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,
就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的
重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。使用“横切”技术,
AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关
注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心
关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分
离系统中的各种关注点,将核心关注点和横切关注点分离开来。
2.AOP 原理
基于动态代理
https://www.cnblogs.com/CHENJIAO120/p/7080790.html
它们之间的调用先后次序反映在上图的序号中:
调用者 Bean 尝试调用目标方法,但是被生成的代理截了胡
代理根据 Advice 的种类(本例中是@Before Advice),对 Advice 首先进行调用
代理调用目标方法
返回调用结果给调用者 Bean(由代理返回,没有体现在图中)
2.1.5.2 基于接口的动态代理http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
在 java 的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(I
nterface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到
的。首先我们先来看看 java 的 API 帮助文档是怎么样对这两个类进行描述的:
1.InvocationHandler:
每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实
例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就
会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。我们来看看 Inv
ocationHandler 这个接口的唯一一个方法 invoke 方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的 Method 对象
args: 指代的是调用真实对象某个方法时接受的参数
2.Proxy
Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但
是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, Inv
ocationHandler h) throws IllegalArgumentException
2.5.2.4 实现子类的动态代理
CGlib 动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;
2.1.6 事务
2.1.6.1 事务的实现方式
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用 beginTransaction()、comm
it()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(
2)基于 TransactionProxyFactoryBean 的声明式事务管理
(
3)基于 @Transactional 的声明式事务管理
(
4)基于 Aspectj AOP 配置事务
根据代理机制的不同,Spring 事务的配置又有几种不同的方式:
第一种方式:每个 Bean 都有一个代理第二种方式:所有 Bean 共享一个代理基类
第三种方式:使用拦截器
第四种方式:使用 tx 标签配置的拦截器
第五种方式:全注解
2.1.6.2 事务的传播级别
1) PROPAGATION_REQUIRED ,默认的 spring 事务传播级别,使用该级别的特点是,如果上
下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。
所以这个级别通常能满足处理大多数的业务场景。
2)PROPAGATION_SUPPORTS ,从字面意思就知道,supports,支持,该传播级别的特点是,
如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行。所以说,并
非所有的包在 transactionTemplate.execute 中的代码都会有事务支持。这个通常是用来处理那些
并非原子性的非核心业务逻辑操作。应用场景较少。
3)PROPAGATION_MANDATORY , 该级别的事务要求上下文中必须要存在事务,否则就会抛出
异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段
代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。
4)PROPAGATION_REQUIRES_NEW ,从字面即可知道,new,每次都要一个新事务,该传播
级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以
后,上下文事务恢复再执行。
这是一个很有用的传播级别,举一个应用场景:现在有一个发送 100 个红包的操作,在发送之前,
要做一些系统的初始化、验证、数据记录操作,然后发送 100 封红包,然后再记录发送日志,发送
日志要求 100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个 PROPAGATION_REQUIRES_NEW 级别的事务传播控
制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。
5)PROPAGATION_NOT_SUPPORTED ,这个也可以从字面得知,not supported ,不支持,
当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就
越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须
调用的,比如循环 1000 次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务
太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了。用
当前级别的事务模板抱起来就可以了。
6)PROPAGATION_NEVER ,该事务更严格,上面一个事务传播级别只是不支持而已,有事务就
挂起,而 PROPAGATION_NEVER 传播级别要求上下文中不能存在事务,一旦有事务,就抛出 run
time 异常,强制停止执行!这个级别上辈子跟事务有仇。
7)PROPAGATION_NESTED ,字面也可知道,nested,嵌套级别事务。该传播级别特征是,如
果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
2.1.6.3 事务的嵌套失效那么什么是嵌套事务呢?很多人都不理解,我看过一些博客,都是有些理解偏差。
嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回
滚点,叫 save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执
行结束,父事务继续执行。重点就在于那个 save point。看几个问题就明了了:
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的 save point,然后尝试其他的事务或者其他的业务逻辑,父事
务之前的操作不会受到影响,更不会自动回滚。
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说
子事务是父事务的一部分,正是这个道理。那么:
事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?答案是第二种情况,还是那
句话,子事务是父事务的一部分,由父事务统一提交。
2.1.7 Spring MVC
2.1.7.0 什么是 Spring MVC
Spring MVC 是一个基于 MVC 架构的用来简化 web 应用程序开发的应用开发框架,它是 Spring 的
一个模块,无需中间整合层来整合 ,它和 Struts2 一样都属于表现层的框架。在 web 模型中,MVC 是
一种很流行的框架,通过把 Model,View,Controller 分离,把较为复杂的 web 应用分成逻辑清晰的
几部分,简化开发,减少出错,方便组内开发人员之间的配合。
2.1.7.1 Spring MVC 执行流程(工作原理)
答:1.用户发送请求至前端控制器 DispatcherServlet
2.DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3.处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则
生成)一并返回给 DispatcherServlet。
4.DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器
5.执行处理器(Controller,也叫后端控制器)。
6.Controller 执行完成返回 ModelAndView
7.HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet
8.DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
9.ViewReslover 解析后返回具体 View
10.DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)。11.DispatcherServlet 响应用
2.1.7.2 SpringMVC 加载流程
springmvc 加载流程
1.Servlet 加载(监听器之后即执行)Servlet 的 init()
2.加载配置文件
3.从 ServletContext 拿到 spring 初始化 springmvc 相关对象
4.放入 ServletContext
2.1.7.2 springMVC 和 struts2 的区别
(
1)springmvc 的入口是一个 servlet 即前端控制器(
DispatchServlet),而 struts2 入口是一个
filter 过虑器(
StrutsPrepareAndExecuteFilter)。
(
2)springmvc 是基于方法开发(一个 url 对应一个方法),请求参数传递到方法的形参,可以设计
为单例或多例(建议单例),struts2 是基于类开发,传递参数是通过类的属性,只能设计为多例。
(
3)Struts 采用值栈存储请求和响应的数据,通过 OGNL 存取数据,springmvc 通过参数解析器是将
request 请求内容解析,并给方法形参赋值,将数据和视图封装成 ModelAndView 对象,最后又将 Mo
delAndView 中的模型数据通过 reques 域传输到页面。Jsp 视图解析器默认使用 jstl。
2.1.8 Spring 中设计模式
https://www.cnblogs.com/jifeng/p/7398852.html
第一种:简单工厂
又叫做静态工厂方法(StaticFactory Method)模式,但不属于 23 种 GOF 设计模式之一。
简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 bean 对象,但是否是在传入参数后创建还是传
入参数前创建这个要根据具体情况来定。如下配置,就是在 HelloItxxz 类中创建一个 itxxzBean。
第三种:单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
spring 中的单例模式完成了后半句话,即提供了全局的访问点 BeanFactory。但没有从构造器级别去控制单例,这是因为 spring 管理
的是是任意的 java 对象。
核心提示点:Spring 下默认的 bean 均为 singleton,可以通过 singleton=“true|false” 或者 scope=“?”来指定
第四种:适配器(Adapter)
在 Spring 的 Aop 中,使用的 Advice(通知)来增强被代理类的功能。Spring 实现这一 AOP 功能的原理就使用代理模式(1、JDK 动
态代理。2、CGLib 字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置
拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
第六种:代理(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。 从结构上来看和 Decorator 模式类似,但 Proxy 是控制,更像是一种对功能的限
制,而 Decorator 是增加职责。spring 的 Proxy 模式在 aop 中有体现,比如 JdkDynamicAopProxy 和 Cglib2AopProxy。
第七种:观察者(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
spring 中 Observer 模式常用的地方是 listener 的实现。如 ApplicationListener。
第八种:策略(Strategy)
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
spring 中在实例化对象的时候用到 Strategy 模式
在 SimpleInstantiationStrategy 中有如下代码说明了策略模式的使用情况:
第九种:模板方法(Template Method)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以不改变一个算法的结构即可重定义该算
法的某些特定步骤。
Template Method 模式一般是需要继承的。这里想要探讨另一种对 Template Method 的理解。spring 中的 JdbcTemplate,在用这
个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到 JdbcTemplate 已有的稳定的、公用的数据库连接,那么我
们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入 JdbcTemplate 的方法中。但是变化的东西是一段代码,而且这段代码会
用到 JdbcTemplate 中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵 JdbcTemplate 中变量的方法,我们
去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到 JdbcTemplate,从而完成了调用。这可能是 Template
Method 不需要继承的另一种实现方式吧。
以下是一个具体的例子:
JdbcTemplate 中的 execute 方法
Spring 的加载流程,Spring 的源码中 Bean 的构造的流程
Spring 事务源码,IOC 源码,AOP 源码
spring 的作用及理解
事务怎么配置
Spring 的 annotation 如何实现
SpringMVC 工作原
了解 SpringMVC 与 Struct2 区别
了解 SpringMVC 请求流程
springMVC 和 spring 是什么关系
项目中 Spring 的 IOC 和 AOP 具体怎么使用的
spring mvc 底层实现原理
动态代理的原理
如果使用 spring mvc,那 post 请求跟 put 请求有什么区别啊;
然后开始问 springmvc:
描述从 tomcat 开始到 springmvc 返回到前端显示的整个流程...
接着问 springmvc 中的 handlerMapping 的内部实现;
.然后又问 spring 中从载入 xml 文件到 getbean 整个流程,描述一遍2.2 Servlet
2.2.1 Servlet 生命周期
1.创建 Servlet 对象,通过服务器反射机制创建 Servlet 对象,第一次请
求时才会创建。(默认)
2,调用 Servlet 对象的 init()方法,初始化 Servlet 的信息,init()方法只会在创建后被调用一次;
3,响应请求,调用 service()或者是 doGet(),doPost()方法来处理请求,这些方法是运行的在
多线程状态下的。
4, 在长时间没有被调用或者是服务器关闭时,会调用 destroy()方法来销毁 Servlet 对象。
2.2.2 servlet 是什么
Servlet 定义:Servlet 是基于 Java 技术的 Web 组件,由容器管理并产生动态的内容。
Servlet 引擎作为 WEB 服务器
的扩展提供支持 Servlet 的功能。Servlet 与客户端通过 Servlet 容器实现的请求/响应模型进
行交互
Servlet 知道是做什么的吗?和 JSP 有什么联系?JSP 的运行原理?JSP 属于 Java 中
的吗?
Servlet 是线程安全
scala 写的大型框架吗 spark
如何确保分布式环境下异步消息处理的顺序性?
servlet 是单例
servlet 和 filter 的区别。
servlet 流程
2.2 Struts
2.2.1 Struts 工作流程
2.2.2 Struts 工作原理2.2.3 do Fliter2.2.4 拦截器与过滤器的区别2.2.5 Struts 中为什么不用考虑线程安全
2.2.6 Struts2 和 Struts1 区别2.3 Hibernate
Hibernate 的生成策略,主要说了 native 、uuid
Hibernate 与 Mybatis 区别
2.4 Redis
Redis 数据结构 Redis 持久化机制
Redis 的一致性哈希算法
redis了解多少 redis五种数据类型,当散列类型的 value 值非常大的时候怎么进行压缩,
用 redis 怎么实现摇一摇与附近的人功能,redis 主从复制过程,
Redis 如何解决 key 冲突
redis 的五种数据结构
redis 是怎么存储数据的
redis 使用场景
2.5 Tomcat
Tomcat 的结构
tomcat 均衡方式 ,netty
2.6 netty
netty 源码
2.7 Hadoop
2.8 Volley
Volley 的原理及使用
springMVC 和 spring 是什么关系
一些 java 的常用框架的架构
.Servlet 的 Filter 用的什么设计模式
Spring 读过哪些源码吗
利用 Bean 的初始化可以做什么事情
排行榜可以使用 redis 哪种数据结构
RESTful 架构Hibernate、Mybatis 与 JDBC 区别
springmvc 的流程 一个请求来了之后如何处理(
handler 链)
框架封装 jdbc 受检异常的考虑和原因?
zookeeper 的常用功能,自己用它来做什么
hadoop/spark/impala/lucene/RocksDB/redis 这些框架的技术点
ibatis 跟 hibernate 的区别
ibatis 是怎么实现映射的,它的映射原理是什么
redis 的操作是不是原子操作
秒杀业务场景设计
WebSocket 长连接问题
如何设计淘宝秒杀系统(重点关注架构,比如数据一致性,数据库集群一致性哈希,缓存,
分库分表等等)?
List 接口去实例化一个它的实现类(ArrayList)以及直接用 ArrayList 去 new 一个该类的对
象,这两种方式有什么区别,为什么大多数情况下会用到
Tomcat 关注哪些参数
Mapreduce
Spring 配置过滤器 和 Struts 的拦截器配置与使用。
对后台的优化有了解吗?比如负载均衡。我给面试官说了 Ngix+Tomcat 负载均
衡,异步处理(消息缓冲服务器),缓存(Redis,
Memcache),
NoSQL,数据库优化,存储索引优化
开源项目
Netty 框架源码看过吗
MapReduce
Volley 机制讲完之后问我 Volley 的缺点是什么,怎么改进
对 Restful 了解
Restful 的认识,优点,以及和 soap 的区别
lrucache 的基本原理
service 中启动方式有哪些区别是?
六.
Java 内存模型 和 垃圾回收
3.0 什么是 JMM 内存模型?(
JMM 和内存区域划分不是一回事)
https://blog.csdn.net/javazejian/article/details/72772461
大体:JMM 就是一组规则,这组规则意在解决在并发编程可能出现的线
程安全问题,JMM (Java Memory Model)是 Java 内存模型,JMM 定义了程
序中各个共享变量的访问规则,即在虚拟机中将变量存储到内存和从内存读
取变量这样的底层细节.并提供了内置解决方案(happen-before 原则)及其
外部可使用的同步手段(synchronized/volatile 等),确保了程序执行在多线程
环境中的应有的原子性,可视性及其有序性。
2.JMM 规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的
主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进
行,而不能直接读写主内存中的变量(
volatile 变量仍然有工作内存的拷贝,但是由于
它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的
线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内
存来完成。
3.0.1 JMM 中的 happens-before 原则
happens-before 原则内容如下
程序顺序原则,即在一个线程内必须保证语义串行性,也就是说按照代
码顺序执行。
锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之
前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动
作之后(同一个锁)。
volatile 规则 volatile 变量的写,先发生于读,这保证了 volatile 变量的
可见性,简单的理解就是,volatile 变量在每次被线程访问时,都强迫从主内存
中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,
任何时刻,不同的线程总是能够看到该变量的最新值。
线程启动规则 线程的 start()方法先于它的每一个动作,即如果线程 A
在执行线程 B 的 start 方法之前修改了共享变量的值,那么当线程 B 执行 start
方法时,线程 A 对共享变量的修改对线程 B 可见
传递性 A 先于 B ,B 先于 C 那么 A 必然先于 C
线程终止规则 线程的所有操作先于线程的终结,Thread.join()方法的作
用是等待当前执行的线程终止。假设在线程 B 终止之前,修改了共享变量,线
程 A 从线程 B 的 join 方法成功返回后,线程 B 对共享变量的修改将对线程 A 可
见。
线程中断规则 对线程 interrupt()方法的调用先行发生于被中断线程的
代码检测到中断事件的发生,可以通过 Thread.interrupted()方法检测线程是否
中断。
对象终结规则 对象的构造函数执行,结束先于 finalize()方法3.1 内存分区什么是堆中的永久代?
答:永久代是用于存放静态文件,如 Java 类、方法等。持久代对垃圾回收
没有显著影响,但是有些应用可能动态生成或者调用一些 class,例如 Hibernate
等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增
的类,永久代中一般包含:
类的方法(字节码...)
类名(Sring 对象)
.class 文件读到的常量信息
class 对象相关的对象列表和类型列表 (e.g., 方法对象的 array).
JVM 创建的内部对象
JIT 编译器优化用的信息3.2 GC 算法(
YGC and FGC)
其中永久代如何内存不足也会触发 fullGC3.2.1 YGC
答:说白了就是复制算法,对象只会存在于 Eden 区和名为“From”的
Survivor 区,Survivor 区“To”是空的。紧接着进行 GC,Eden 区中所有存活
的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的
年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过
-XX:MaxTenuringThreshold 来设置)的对象会被移动到年老代中,没有达到阈值
的对象会被复制到“To”区域。经过这次 GC 后,Eden 区和 From 区已经被清
空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是
上次 GC 前的“From”,新的“From”就是上次 GC 前的“To”。不管怎样,都会保
证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,直到“To”
区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
其中如果发生晋升失败的情况,那么说明老年代的内存空间不够用了,需要
进行一次 FullGC
3.2.2 FGC
答:FGC 就是标记整理或者是标记清除算法来清除老年代。
3.3 垃圾收集器 CMS3.4 java 类加载机制 双亲委派
https://blog.csdn.net/ns_code/article/details/17881581
3.4.1 java 类加载的过程类加载过程
加载
1.
通过一个类的全限名来获取定义此类的二进制节流。(实现这个代码模块就是类加
载器)
2.
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数
据的访问入口。
验证
文件格式验证
– [x] 是否以魔数 0xCAFEBABE 开头
– [x] 主次版本号是否在当前虚拟机处理范围之内
– [x] 常量池中的常量是否有不被支持的常量类型
– [x] 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
– [x] CONSTANTUtf8info 型的常量中是否有不符合 UTF8 编码的数据
– [x] Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息
– [x] 等等
元数据验证
– [x] 这个类是否有父类
– [x] 这个类的父类是否继承了不准许被继承的类– [x] 如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法
– [x] 类中的字段方法是否与父类产生矛盾
–
字节码验证
– [x] 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作
– [x] 保证跳转指令不会跳转到方法体以外的字节码指令上
– [x] 保证方法体重的类型转换是有效的
符号引用验证
– [x] 符号引用中通过字符串描述的全限定名是否找到相应的类
– [x] 在指定的类中是否存在符合方法的字段描述符以及简单名称说描述的方法和字段
– [x] 符号引用中的类、字段、方法的访问性是否被当前类访问
准备
准备阶段是正式为类变量分配内存并设置类变量初始值(被static修饰的变量)的阶段,这些变
量所使用的内存都将在方法区中进行分配
解析
解析阶段就是虚拟机将常量池内的符号引用替换为直接引用的过程
初始化
初始化就是执行类构造器方法的过程
3.4.2 双亲委派机制3.4.3 破坏双亲委派模型
1.在 JDK1.2 之前,用户去继承 java.lang.ClassLoader 的唯一目的就是为了重写 loadClass
方法,由于用户自己重写了 loadClass,那么也就是用户自己去自定义加载类,故事破坏
2.JDBC,JDNI 等的 SPI 的加载都是父类的加载器去请求子类的加载器去加载累;
3.OSGI 的热部署就是自定义类加载器机制的实现;3.5 内存泄露
6.
检查内存泄露的工具
3.6 .内存泄露的案例分析 jvm 调优
https://www.cnblogs.com/csniper/p/5592593.html
https://mp.weixin.qq.com/s/ydkEkh_Uc1paftJLKIsm0w
3.6.1 jvm 调优目的
1.将转移到老年代的对象数量降低到最小;
2.减少 fullGC 的执行时间
3.6.2 案例分析
1.案例 1(修改 NewRatio 可以说有时候访问访问着就会发生卡顿)
(
http://www.360doc.com/content/13/0305/10/15643_269388816.shtml)一个服务系统,经常出现卡顿,分析原因,发现 Full GC 时间太长:
jstat -gcutil:
S0
S1
E
O
P
YGC YGCT FGC FGCT GCT
12.16 0.00 5.18 63.78 20.32 54 2.047 5
6.946 8.993
分析上面的数据,发现 Young GC 执行了 54 次,耗时 2.047 秒,每次 Young GC 耗时 37ms,
在正常范围,而 Full GC 执行了 5 次,耗时 6.946 秒,每次平均 1.389s,数据显示出来的
问题是:Full GC 耗时较长,分析该系统的是指发现,NewRatio=9,也就是说,新生代和
老生代大小之比为 1:9,这就是问题的原因:
1,新生代太小,导致对象提前进入老年代,触发老年代发生 Full GC;
2,老年代较大,进行 Full GC 时耗时较大;
优化的方法是调整 NewRatio 的值,调整到 4,发现 Full GC 没有再发生,只有 Young GC
在执行。这就是把对象控制在新生代就清理掉,没有进入老年代(这种做法对一些应用是很
有用的,但并不是对所有应用都要这么做)
2.案例 2(与案例 1 重复)3.案例 3 (MinorGC 时间过长 可以这样说 人物关系提取模块 需要业务上每 30 分钟加载一
个 80MB 的数据文件到内存进行数据分析(人名字和政党),这些数据会在内存中形成超过
100w 个 HashMap<String,String> Entry,这段时间会造成 1000ms 左右的 minorGC 的停顿)3.7 jstat jmap jps jinfo jconsole
3.7.1 jstat3.7.2 jmap
3.7 JVM 参数设置
参数说明
-Xmx3550m:设置 JVM 最大堆内存为 3550M。
-Xms3550m:设置 JVM 初始堆内存为 3550M。此值可以设置与-Xmx 相同,以避免每次垃
圾回收完成后 JVM 重新分配内存。
-Xss128k:设置每个线程的栈大小。JDK5.0 以后每个线程栈大小为 1M,之前每个线程栈
大小为 256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个
值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,
经验值在 3000~5000 左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会
在很大程度上降低系统的性能。
-Xmn2g:设置年轻代大小为 2G。在整个堆内存大小确定的情况下,增大年轻代将会减小
年老代,反之亦然。此值关系到 JVM 垃圾回收,对系统性能影响较大,官方推荐配置为整
个堆大小的 3/8。
-XX:NewSize=1024m:设置年轻代初始值为 1024M。
-XX:MaxNewSize=1024m:设置年轻代最大值为 1024M。
-XX:PermSize=256m:设置持久代初始值为 256M。-XX:MaxPermSize=256m:设置持久代最大值为 256M。
-XX:NewRatio=4:设置年轻代(包括 1 个 Eden 和 2 个 Survivor 区)与年老代的比值。表
示年轻代比年老代为 1:4。
-XX:SurvivorRatio=4:设置年轻代中 Eden 区与 Survivor 区的比值。表示 2 个 Survivor 区
(
JVM 堆内存年轻代中默认有 2 个大小相等的 Survivor 区)与 1 个 Eden 区的比值为 2:4,
即 1 个 Survivor 区占整个年轻代大小的 1/6。
-XX:MaxTenuringThreshold=7:表示一个对象如果在 Survivor 区(救助空间)移动了 7 次
还没有被垃圾回收就进入年老代。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,
直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为
一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象在年轻代存
活时间,增加对象在年轻代被垃圾回收的概率,减少 Full GC 的频率,这样做可以在某种程
度上提高服务稳定性。
-XX:PretenureSizeThreshold 直接晋升到老年代的对象大小,设置这个参数后,大于这个参
数的对象将直接在老年代分配。
-XX:MaxTenuringThreshold 每次 minorGC 就增加一次,超过这个值,在 from 中的对象直
接进入到老年代3.8 内存分配与回收策略
1.对象优先在 Eden 分配
2.大对象直接进入老年代
3.长期存活的对象将进入老年代
4.动态对象年龄判定
如果在 Survivor 空间中相同年龄所有对象大小总和大于 Survivor 空间的一半,(比如说
Survivor 空间大小为 1M,而有两个年龄为 1 的对象大小和是大于 512K 的),那么年龄大于
等于该年龄的对象都可以直接进入到老年代。
5.空间分配担保
在进行 MinorGC 前,虚拟机会查看 HandlePromotionFailure 设置值是否为 True,那么说
明允许担保失败(会检查虚拟机老年代剩余空间的大小与平均晋升到老年代空间的大小,如
果大于说明“可能”是安全的),为 True 那么进行一次 MinorGC,如果此时刻发现进入到老
年代的新对象的大小是大于老年代的剩余空间,说明担保失败了,只能进行一次 FullGC 清
除老年代的剩余空间。
3.9 面试问题
3.9.1 一般 Java 堆是如何实现的?
我:在 HotSpot 虚拟机实现中,Java 堆分成了新生代和老年代,我当时看的是 1.7 的实现,
所有还有永久代,新生代中又分为了 eden 区和 survivor 区,
survivor 区又分成了 S0 和 S1,
或则是 from 和 to,(这个时候,我要求纸和笔,因为我觉得这个话题可以聊蛮长时间,又
是我比较熟悉的...一边画图,一边描述),其中 eden,from 和 to 的内存大小默认是 8:1:1
(各种细节都要说出来...),此时,我已经在纸上画出了新生代和老年代代表的区域3.9.2 对象在内存中的初始化过程
参考:1.https://blog.csdn.net/WantFlyDaCheng/article/details/81808064
2.《深入理解 java 虚拟机》
原文:https://blog.csdn.net/WantFlyDaCheng/article/details/81808244
Student s = new Student() 为例
1.首先查看类的符号引用,看是否已经在常量池中,在说明已经加载过了,不在
的话需要进行类的加载,验证,准备,解析,初始化的过程。
2.上诉过程执行完毕以后,又将 Student 加载进内存,也就是存储 Student.class
的字段信息和方法信息,存储到方法区中
字段信息:存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符。
方法信息:类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、
修饰符、异常、方法的字节码。
3。然后在自己的线程私有的虚拟机栈中,存储该引用,然后在每个线程的私有
空间里面去分配空间存储 new Student(),如果空间不足在 eden 区域进行分配空
间
4,对类中的成员变量进行默认初始化
5,对类中的成员变量进行显示初始化
6,有构造代码块就先执行构造代码块,如果没有,则省略(此步上文未体现)
7,执行构造方法,通过构造方法对对对象数据进行初始化
8,堆内存中的数据初始化完毕,把内存值复制给 s 变量
3.9.3 对象的强、软、弱和虚引用
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就
会回收这些对象的内存
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它
所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回
收它的内存
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如
果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
3.9.4 如何减少 GC 的次数1.对象不用时最好显示置为 NULL
一般而言,为 NULL 的对象都会被作为垃圾处理,所以将不用的对象置为
NULL,有利于 GC 收集器判定垃圾,从而提高了 GC 的效率。
2.尽量少使用 System,gc()
此函数建议 JVM 进行主 GC,会增加主 GC 的频率,增加了间接性停顿的次
数。
3.尽量少使用静态变量
静态变量属于全局变量,不会被 GC 回收,他们会一直占用内存
4.尽量使用 StringBuffer,而不使用 String 来累加字符串
5.分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,
JVM 在这种
情况下只能进行主 GC 以回收内存,从而增加主 GC 的频率。
6.尽量少用 finaliza 函数
它会加大 GC 的工作量。
7.如果有需要使用经常用到的图片,可以使用软引用类型,将图片保存在内存中,
而不引起 outofmemory
8.能用基本类型入 INT 就不用对象 Integer
9.增大-Xmx 的值
3.9.5 新生代 老年代 永久代
年轻代:
事实上,在上一节,已经介绍了新生代的主要垃圾回收方法,在新生代中,使用“停止-复制”算法进行
清理,将新生代内存分为 2 部分,1 部分 Eden 区较大,1 部分 Survivor 比较小,并被划分为两个等量的
部分。每次进行清理时,将 Eden 区和一个 Survivor 中仍然存活的对象拷贝到 另一个 Survivor 中,然后清
理掉 Eden 和刚才的 Survivor。这里也可以发现,停止复制算法中,用来复制的两部分并不总是相等的(传统的停止复制算法两部分
内存相等,但新生代中使用 1 个大的 Eden 区和 2 个小的 Survivor 区来避免这个问题)
由于绝大部分的对象都是短命的,甚至存活不到 Survivor 中,所以,Eden 区与 Survivor 的比例较大,
HotSpot 默认是 8:1,即分别占新生代的 80%,10%,10%。如果一次回收中,Survivor+Eden 中存活下
来的内存超过了 10%,则需要将一部分对象分配到 老年代。用-XX:SurvivorRatio 参数来配置 Eden 区域
Survivor 区的容量比值,默认是 8,代表 Eden:Survivor1:Survivor2=8:1:1.
老年代:
老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制
算法,则相当低效。一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),
将所有存活的对象向一端移动,以保证内存的连续。
在发生 Minor GC 时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果
大于,则直接触发一次 Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),
如果允许,则只会进行 MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行 Full GC(这代表
着如果设置-XX:+Handle PromotionFailure,则触发 MinorGC 就会同时触发 Full GC,哪怕老年代还有很多
内存,所以,最好不要这样做)。
方法区(永久代):
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回
收。对于无用的类进行回收,必须保证 3 点:
1.
类的所有实例都已经被回收
2.
加载类的 ClassLoader 已经被回收
3.
类对象的 Class 对象没有被引用(即没有通过反射引用该类的地方)
永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。HotSpot 提供-Xnoclassgc 进行
控制
使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 可以查看类加载和卸载信息
-verbose、-XX:+TraceClassLoading 可以在 Product 版 HotSpot 中使用;
-XX:+TraceClassUnLoading 需要 fastdebug 版 HotSpot 支持
如何加快 gc 的速度 快速判断对象生死
垃圾收机制简述,堆,栈,如何判断对象已死,有环 root 链如何找到了?
线程安全 java 里面的实现方式
如果我们一个项目,理论上需要 1.5G 的内存就足够,但是项目上线后发现隔了几个星期,
占用内存到了 2.5G,这时候你会考虑是什么问题?怎么解决?
可能造成内存泄漏的原因有哪些?检查内存泄漏的工具有哪些?你平时是怎么检查内存泄
漏的?
jvm 多态原理。invokestatic invokeinterface 等指令。常量池中的符号引用 找到直接引
用。在堆中找到实例对象,获取到偏移量,由偏移量在方法表中指出调用的具体方法。接口是
在方法表中进行扫描)等等扯了半天七.
juc 包
https://blog.csdn.net/china_wanglong/article/details/3
8828407
7.0 juc 概况
7.1 Tools
7.1.1 CountDownLatch
这个类是一个同步计数器,主要用于线程间的控制,当 CountDownLatch 的 count 计数>0 时,
await()会造成阻塞,直到 count 变为 0,await()结束阻塞,使用 countDown()会让 count 减 1。
CountDownLatch 的构造函数可以设置 count 值,当 count=1 时,它的作用类似于 wait()和 notify()
的作用。如果我想让其他线程执行完指定程序,其他所有程序都执行结束后我再执行,这时可以用
CountDownLatch,但计数无法被重置,如果需要重置计数,请考虑使用 CyclicBarrier 。
7.1.2 CyclicBarrier
该类从字面理解为循环屏障,它可以协同多个线程,让多个线程在这个屏障前等到,直到所有
线程都到达了这个屏障时,再一起执行后面的操作。假如每个线程各有一个 await,任何一个线程运
行到 await 方法时就阻塞,直到最后一个线程运行到 await 时才同时返回。和之前的 CountDownLatch
相比,它只有 await 方法,而 CountDownLatch 是使用 countDown()方法将计数器减到 0,它创建的参数就是 countDown 的数量;CyclicBarrier 创建时的 int 参数是 await 的数量。
7.1.3 Semaphore
该类用于控制信号量的个数,构造时传入个数。总数就是控制并发的数量。假如是 5,程序执
行前用 acquire()方法获得信号,则可用信号变为 4,程序执行完通过 release()方法归还信号量,可用
信号又变为 5.如果可用信号为 0,acquire 就会造成阻塞,等待 release 释放信号。acquire 和 release
方法可以不在同一个线程使用。Semaphore 实现的功能就类似厕所有 5 个坑,假如有 10 个人要上厕
所,那么同时只能有多少个人去上厕所呢?同时只能有 5 个人能够占用,当 5 个人中 的任何一个人
让开后,其中等待的另外 5 个人中又有一个人可以占用了。另外等待的 5 个人中可以是随机获得优先
机会,也可以是按照先来后到的顺序获得机会,这取决于构造 Semaphore 对象时传入的参数选项。
单个信号量的 Semaphore 对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另
一个线程释放“锁”,这可应用于死锁恢复的一些场合。
7.1.4 Exchanger
这个类用于交换数据,只能用于两个线程。当一个线程运行到 exchange()方法时会阻塞,另一
个线程运行到 exchange()时,二者交换数据,然后执行后面的程序。
7.2 List Set
CopyOnWriteArrayList, CopyOnWriteArraySet 和 ConcurrentSkipListSet
7.3 Map
ConcurrentHashMap 和 ConcurrentSkipListMap
7.4 Queue
ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDeque, ConcurrentLinkedQueue 和
ConcurrentLinkedDeque
7.4.1 ArrayBlockingQueue
1.基于数组实现,保证并发的安全性是基于 ReetrantLock 和 Condition 实现的。其中有两个重要的成
员变量 putindex 和 takeindex,这两个需要搞懂,putindex 就是指向数组中上一个添加完元素的位置的
下一个地方,比如刚在 index=1 的位置添加完,那么 putindex 就是 2,其中有一点特别注意的就是当
index=数组的长度减一的时候,意味着数组已经到了满了,那么需要将 putindex 置位 0,原因是数
组在被消费的也就是取出操作的时候,是从数组的开始位置取得,所以最开始的位置容易是空的,所
以把要添加的位置置位 0;takeindex 也是一样的,当 takeindex 到了数组的长度减一的时候,也需要
将 takeindex 置为 0。
2.add offer put
add 调用了 offer 方法 add 方法数组满了则抛出异常
offer 方法:用 ReetracLock 加锁,首先判断数组是否满了,数组满了则返回 false,数组不满的话直
接入队,也就是将 putindex 索引处的值置为新要加入的数,如果加入以后发现 putindex++ = 数组的
长度,那么说明后面的全部已经填满了,因此 putindex 置为 0,因为前面的可能出队的过程空出来了,
所以变为 0,最后一步就是执行 notEmpty.signal 去唤醒消费的执行了 take 的线程,只有可能是执行
了 take 的方法的线程,因为执行了其它方法 remove,poll 不会产生线程的挂起操作。
put:首先是 ReetrantLock 加锁,然后判断是否满了,队列满了,则执行 notFull.await()操作挂起,等待 notFull.signal()唤醒。没满,则直接进行入队,入队和 offer 操作一样,也就是将 putindex 索引
处的值置为新要加入的数,如果加入以后发现 putindex++ = 数组的长度,那么说明后面的全部已经
填满了,因此 putindex 置为 0,因为前面的可能出队的过程空出来了,所以变为 0,最后一步就是执
行 notEmpty()去唤醒消费的执行了 take 的线程
3.remove,poll,take
poll 首先加锁 ReetranLock ,然后判断队列是否为空,不为空,则将 putindex 出的值用副本 copy,然
后置位 null,然后去执行唤醒 notFull()操作,也就是唤醒调用了 put 操作的线程,唤醒操作并不一定总
是发生。
take 操作,先加锁,然后如果队列空则 notEmpty.await()方法,不为空,则执行和 poll 一样的出队操
作:则将 putindex 出的值用副本 copy,然后置位 null,然后去执行唤醒 notFull()操作
7.4.2 LinkedBlockingQueue
1.基于链表实现,有 takelock 和 putlock,也就是说可以同时在首尾两端进行操作,因此吞吐量比
ArrayBlockingQueue 大,同时由于首尾两端都可以进行操作,所以当在进行添加的操作的过程可以
一直去添加,直到没有被阻塞的添加线程为止,然后才去执行消费的线程。
1.add,offer,put
add 调用 offer,满了抛出异常
offer 方法 putlock 锁,然后不满则加入,同时获取一个 c 值,c 值代表本次队列增加前的队列的数目
(一开始长度 2,增加 1,现在长度是 3,那么 c 就是 2),然后判断如果不满则继续去唤醒 notFull.signl,
去唤醒添加线程去添加(添加过程是直接 last 节点指向下一个,简单的节点后增加一个节点,然后 last
指向最后一个节点),上述过程结束,然后去判断 c==0(c 代表了之前的队列长度,如果添加之前队
列长度是 0 那么说明可能有挂起的消费线程,需要从队列取元素,但队列长度为 0 没有元素;判断
c>0 没有意义,因为添加之前队列不为空,说明不存在挂起的消费线程,挂起的原因是因为队列为空,
所以不存在因此源码是判断 c==0) ,c 如果等于 0 那么去唤醒阻塞的 notEmpty 上的条件等待线程。
put 操作就是满了则挂起,不满则执行,同时添加完一个后,发现没满继续去唤醒挂起的添加线程
2.poll take
反之,一样的逻辑
poll 则获取 takelcok 然后不为空则出队一个元素,也就是链表的删除头结点操作,通过是如果队列
不为空,那么继续去唤醒被挂起的消费线程(消费线程就是执行了队列的 take 操作的线程),直到
没有消费线程或者队列为空,结束,然后如果 c(也是队列消费一个头节点的元素后,没消费之前的
长度,没发生删除的时候队列的长度),如果 c 的长度已经是队列的长度,则去唤醒被挂起的执行了
put 方法的线程,然后释放 takelock 锁
take 方法一样的道理,为空则挂起,不为空一直消费,唤起消费线程一直消费,直到条件不满足,那
么去尝试判断 c 的值,c 是队列长度减一,那么去唤醒执行了 put 方法的被挂起的线程。
以下内容来自 深入剖析 java 并发之阻塞队列 LinkedBlockingQueue 与 ArrayBlockingQueu
7.4.3 LinkedBlockingQueue 和 ArrayBlockingQueue 迥异
通过上述的分析,对于 LinkedBlockingQueue 和 ArrayBlockingQueue 的基本使用以及内部实现
原理我们已较为熟悉了,这里我们就对它们两间的区别来个小结
1.队列大小有所不同,ArrayBlockingQueue 是有界的初始化必须指定大小,而 LinkedBlockingQueu
e 可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,
在无界的情况下,可能会造成内存溢出等问题。2.数据存储容器不同,ArrayBlockingQueue 采用的是数组作为数据存储容器,而 LinkedBlockingQu
eue 采用的则是以 Node 节点作为连接对象的链表。
3.由于 ArrayBlockingQueue 采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任
何额外的对象实例,而 LinkedBlockingQueue 则会生成一个额外的 Node 对象。这可能在长时间内需
要高效并发地处理大批量数据的时,对于 GC 可能存在较大影响。
4.两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue 实现的队列中的锁是没有分离的,
即添加操作和移除操作采用的同一个 ReenterLock 锁,而 LinkedBlockingQueue 实现的队列中的锁
是分离的,其添加采用的是 putLock,移除采用的则是 takeLock,这样能大大提高队列的吞吐量,也
意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发
性能。
7.5 线程池
1.线程池工作原理
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于
corePoolSize;如果当前线程数为 corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
如果阻塞队列满了,那就创建新的线程执行当前任务;直到线程池中的线程数达到 maxPoolSize,这
时再有任务来,只能执行 reject()处理该任务;
2.线程池分类
4 种类型的线程池:
newFixedThreadPool()
说明:初始化一个指定线程数的线程池,其中 corePoolSize == maxiPoolSize,使用 LinkedBlockin
gQuene 作为阻塞队列
特点:即使当线程池没有可执行任务时,也不会释放线程。
newCachedThreadPool()
说明:初始化一个可以缓存线程的线程池,默认缓存 60s,线程池的线程数可达到 Integer.MAX_VA
LUE,即 2147483647,内部使用 SynchronousQueue 作为阻塞队列;
特点:在没有任务执行时,当线程的空闲时间超过 keepAliveTime,会自动释放线程资源;当提交新
任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
因此,使用时要注意控制并发的任务数,防止因创建大量的线程导致而降低性能。
newSingleThreadExecutor()
说明:初始化只有一个线程的线程池,内部使用 LinkedBlockingQueue 作为阻塞队列。
特点:如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任
务的顺序执行
newScheduledThreadPool()
特定:初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使
用该线程池定期的同步数据。总结:除了 newScheduledThreadPool 的内部实现特殊一点之外,其它线程池内部都是基于 Thread
PoolExecutor 类(Executor 的子类)实现的。
3.线程池底层实现类 ThreadPoolExecutor 类
ThreadPoolExecutor(
corePoolSize,maxPoolSize,keepAliveTime,timeUnit,workQueue,threa
dFactory,handle);
corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于
corePoolSize;如果当前线程数为 corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
如果执行了线程池的 prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,
前提是当前线程数小于 maximumPoolSize;
keepAliveTime
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程
数大于 corePoolSize 时才有用;
unit
keepAliveTime 的单位;
workQueue
用来保存等待被执行的任务的阻塞队列,且任务必须实现 Runable 接口,在 JDK 中提供了如下阻塞
队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按 FIFO 排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按 FIFO 排序任务,吞吐量通常要高于 Array
BlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除
操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列;
threadFactory
创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策
略处理该任务,线程池提供了 4 种策略:
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持
久化存储不能处理的任务。
4.线程池状态RUNNING 自然是运行状态,指可以接受任务执行队列里的任务
SHUTDOWN 指调用了 shutdown() 方法,不再接受新任务了,但是队列里的任务得执行完毕。
STOP 指调用了 shutdownNow() 方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所
有正在执行任务。
TIDYING 所有任务都执行完毕,在调用 shutdown()/shutdownNow() 中都会尝试更新为这个状态。
TERMINATED 终止状态,当执行 terminated() 后会更新为这个状态
四.设计模式
4.0 什么是设计模式
在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复
出现)的各种问题,所提出的解决方案。
4.1.常见的设计模式及其 JDK 中案例:
4.1.1 适配器模式
java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)
4.1.2 迭代器模式
提供一个一致的方法来顺序访问集合中的对象,这个方法与底层的集合的具体
实现无关。
JDK 中:
java.util.Iterator
java.util.Enumeration
4.1.3 代理模式
代理(proxy)模式:指目标对象给定代理对象,并由代理对象代替真实对象
控制客户端对真实对象的访问。
代理模式模式有以下角色:
抽象主题(subject)角色:声明真实主题和代理主题的共同接口。
真实主题(real subject)角色:定义代理对象需要代理的真实对象。
代理主题(proxy subject)角色:代替真实对象来控制对真实对象的访问,代理对象持有真实对象的应用,从而可以随时控制客户端对真实对象的访问。
实例:大话设计模式:
JDK:java.lang.reflect.Proxy
RMI
4.1.4 观察者模式Jdk : java.util.EventListener
javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener
javax.faces.event.PhaseListener
4.1.5 装饰器模式
动态的给一个对象附加额外的功能,这也是子类的一种替代方式。可以看到,
在创建一个类型的时候,同时也传入同一类型的对象。这在 JDK 里随处可见,你
会发现它无处不在,所以下面这个列表只是一小部分。
装饰原有对象、在不改变原有对象的情况下扩展增强新功能/新特征.。当不
能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可以使
用装饰模式。
Jdk : 装 饰 者 模 式 通 过 包 含 一 个 原 有 的 Inputstream 对 象 , 并 且 将
InputStream 原有的方法或直接暴露,或进行装饰后暴露,又或者添加了新的特
性,如 DataInputStream 中的 readInt(),BufferedInputStream 中的缓存功
能。(这些新的功能就是装饰后新添加的)
java.io.BufferedInputStream(InputStream)
java.io.DataInputStream(InputStream)
java.io.BufferedOutputStream(OutputStream)
java.util.zip.ZipOutputStream(OutputStream)java.util.Collections#checkedList|Map|Set|SortedSet|SortedMap
4.1.6 工厂模式
简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
工厂方法:每种产品由一种工厂来创建。(不这样会有什么问题?)
抽象工厂:感觉只是工厂方法的复杂化,产品系列复杂化的工厂方法。
工厂方法模式:就是一个返回具体对象的方法。
java.lang.Proxy#newProxyInstance()
java.lang.Object#toString()
java.lang.Class#newInstance()
java.lang.reflect.Array#newInstance()
java.lang.reflect.Constructor#newInstance()
java.lang.Boolean#valueOf(String)
java.lang.Class#forName()
抽象工厂模式
抽象工厂模式提供了一个协议来生成一系列的相关或者独立的对象,而不用指定具体对象的
类型。它使得应用程序能够和使用的框架的具体实现进行解耦。这在 JDK 或者许多框架比
如 Spring 中都随处可见。它们也很容易识别,一个创建新对象的方法,返回的却是接口或
者抽象类的,就是抽象工厂模式了。
java.util.Calendar#getInstance()
java.util.Arrays#asList()
java.util.ResourceBundle#getBundle()
java.sql.DriverManager#getConnection()
java.sql.Connection#createStatement()
java.sql.Statement#executeQuery()
java.text.NumberFormat#getInstance()
javax.xml.transform.TransformerFactory#newInstance()
4.1.7 建造者模式
定义了一个新的类来构建另一个类的实例,以简化复杂对象的创建。建造模式通常
也使用方法链接来实现。java.lang.StringBuilder#append()
java.lang.StringBuffer#append()
java.sql.PreparedStatement
javax.swing.GroupLayout.Group#addComponent()
例如 StringBuilder 就是定义了一个新类 StringBuilder 来完成“aa”+ “bb”的创建
System.Text.StringBuilder sb = new StringBuilder();
sb.Append("aa");//添加的子对象部分(这就是创建 子对象的部分)
sb.Append("bb");(这个就对应 GetResult())
string str= sb.ToString();//最终 都演变成 最后一种形式
4.1.8 命令模式
4.1.9 责任链模式
通过把请求从一个对象传递到链条中下一个对象的方式,直到请求被处理完毕,以实现对象
间的解耦。
java.util.logging.Logger#log()
javax.servlet.Filter#doFilter()
4.1.10 享元模式Flyweight 享元模式:
使用缓存来加速大量小对象的访问时间。
java.lang.Integer#valueOf(int)
java.lang.Boolean#valueOf(boolean)
java.lang.Byte#valueOf(byte)
java.lang.Character#valueOf(char)
4.1.11 中介者模式
中介者模式
通过使用一个中间对象来进行消息分发以及减少类之间的直接依赖。
java.util.Timer
java.util.concurrent.Executor#execute()
java.util.concurrent.ExecutorService#submit()
java.lang.reflect.Method#invoke()
4.1.12 备忘录模式
生成对象状态的一个快照,以便对象可以恢复原始状态而不用暴露自身的内容。Date
对象通过自身内部的一个 long 值来实现备忘录模式。
java.util.Date
java.io.Serializable
4.1.13 组合模式
Composite 组合模式:
又叫做部分-整体模式,使得客户端看来单个对象和对象的组合是同等的。换句话说,某个
类型的方法同时也接受自身类型作为参数。
avax.swing.JComponent#add(Component)
java.util.Map#putAll(Map)
java.util.List#addAll(Collection)
java.util.Set#addAll(Collection)
4.1.14 模板方法模式
模板方法模式
让子类可以重写方法的一部分,而不是整个重写,你可以控制子类需要重写那些操作。
java.util.Collections#sort()
java.io.InputStream#skip()
java.io.InputStream#read()
java.util.AbstractList#indexOf()4.1.15 单例模式
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单
个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不
需要实例化该类的对象。
https://www.cnblogs.com/cielosun/p/6582333.html
https://blog.csdn.net/u014590757/article/details/79818702
1.非线程安全懒汉模式
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
2、 线程安全懒汉模式
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static synchronized SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
3.饿汉模式
直接在运行这个类的时候进行一次 loading,之后直接访问。显然,这种方法没有起到
lazy loading 的效果,考虑到前面提到的和静态类的对比,这种方法只比静态类多了一个内
存常驻而已。
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return instance;
}
}
4. 静态类内部加载
使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用 getInstance
()方法时才进行加载,达到了类似懒汉模式的效果,而这种方法又是线程安全的。
public class SingletonDemo {
private static class SingletonHolder{
private static SingletonDemo instance=new SingletonDemo();
}
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
return SingletonHolder.instance;
}
}
5.双重锁校验模式
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
接下来我解释一下在并发时,双重校验锁法会有怎样的情景:
STEP 1. 线程 A 访问 getInstance()方法,因为单例还没有实例化,所以进入了锁定块。
STEP 2. 线程 B 访问 getInstance()方法,因为单例还没有实例化,得以访问接下来代码块,
而接下来代码块已经被线程 1 锁定。STEP 3. 线程 A 进入下一判断,因为单例还没有实例化,所以进行单例实例化,成功实例
化后退出代码块,解除锁定。
STEP 4. 线程 B 进入接下来代码块,锁定线程,进入下一判断,因为已经实例化,退出代
码块,解除锁定。
STEP 5. 线程 A 初始化并获取到了单例实例并返回,线程 B 获取了在线程 A 中初始化的单
例。
理论上双重校验锁法是线程安全的,并且,这种方法实现了 lazyloading。
7. 懒汉模式与饿汉模式区别
饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例
进行创建,实例在整个程序周期都存在。它的好处是只在类加载的时候创建
一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问
题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载
之后就被创建,内存就被浪费了。
8. 双重校验锁方法与线程安全的懒汉模式区别
可以看到上面在同步代码块外多了一层 instance 为空的判断。由于单
例对象只需要创建一次,如果后面再次调用 getInstance()只需要直接返回单
例对象。因此,大部分情况下,调用 getInstance()都不会执行到同步代码
块,从而提高了程序性能。不过还需要考虑一种情况,假如两个线程 A、B,
A 执行了 if (instance == null)语句,它会认为单例对象没有创建,此时线程
切到 B 也执行了同样的语句,B 也认为单例对象没有创建,然后两个线程依
次执行同步代码块,并分别创建了一个单例对象。为了解决这个问题,还需
要在同步代码块中增加 if(instance == null)语句,也就是上面看到的代码 2。
9. 单例模式与静态变量区别
首先解决引用的唯一实例可能被重新赋值的问题,单例模式中的 getInst
ance()静态方法实现时,1.采用懒汉式创建一个对象(当然这只是创建方式
的一种),规避了这一风险,无则创建,有则跳过创建。2.其次,getInstan
ce()静态方法定义在该类的内部,获取该类对象的引用位置非常明确,无需
额外的沟通商定,团队成员拿起即用。最后一个区别并不是很明显,声明一
个静态变量,4.实际上,我们会直接对其进行初始化赋值,这样,在内存占
用上,所占用的内存为该初始化赋值对象实际的内存。而单例模式可以通过
懒汉创建法延迟该内存的占用,要知道,当一个静态变量只进行声明,而不
进行初始化时,实际的内存占用只有 4 个字节
4.2 设计模式六大原则
https://www.cnblogs.com/dolphin0520/p/3919839.html1.单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能
领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变
化的原因。
2.开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修
改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
3.里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的
地方必须能透明地使用其子类的对象。
4.依赖倒转原则(Dependency Inversion Principle, DIP):抽象不应该依赖于细
节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程
5.接口隔离原则(Interface Segregation Principle, ISP):使用多个专门的接口,
而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
6.迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实
体发生相互作用
4.3 java 动态代理
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
在 java 的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(I
nterface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到
的。首先我们先来看看 java 的 API 帮助文档是怎么样对这两个类进行描述的:
1.InvocationHandler:
每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实
例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就
会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。我们来看看 Inv
ocationHandler 这个接口的唯一一个方法 invoke 方法
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的 Method 对象
args: 指代的是调用真实对象某个方法时接受的参数
2.Proxy
Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但
是我们用的最多的就是 newProxyInstance 这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, Inv
ocationHandler h) throws IllegalArgumentException
loader:
一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进
行加载
interfaces:
一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什
么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样
我就能调用这组接口中的方法了h:
一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,
会关联到哪一个 InvocationHandler 对象上
个人理解:就是说通过 Proxy 的 ProxyInstance 类创建出一个代理类,这个代理类执行
的关于它代理的对象(真正的对象)的方法(代理类可以自己定义自己的方法,要区别),
通过一个 InvocationHandler,InvocationHandler 是一个接口,接口中有 invoke 方法,invo
ke 方法关联到一个真正的对象,然后去执行真正对象的方法,来实现代理。单例模式(双检锁模式)、简单工厂、观察者模式、适配器模式、职责链模式等等
享元模式模式 选两个画下 UML 图
手写单例
写的是静态内部类的单例,然后他问我这个地方为什么用 private,这儿为啥用 static,
这就考察你的基本功啦
静态类与单例模式的区别
单例模式 double check
单例模式都有什么,都是否线程安全,怎么改进(从 synchronized 到 双重检验锁
到 枚举 Enum)
1、
基本的设计模式及其核心思想
2、
来,我们写一个单例模式的实现。这里有一个深坑,详情请见《 JVM 》
第 370 页
3、
剩下的什么的就随缘吧。适配器类图什么的我也画过啊。4、
基本的设计原则。如果有人问你接口里的属性为什么都是 final static
的,记得和他聊一聊设计原则。
设计模式了解哪些
jdk 中哪些类用了哪些设计模式
算法
1.最小堆;
2.大数据归并排序、遗传算法 sqrt()是实现,归并排序实现,mapreduce 排序
3.快速排序和堆排序的优缺点,为什么?
4.一个是链表相加,思路就是反转 然后求和,另一个是多个有序数组 归并,用优先队列就
好
5.最后一个算法题,是一个装水的问题,问在装多少,我用的双指针
6.LintCode -最小子串覆盖
7.查找数组中的最小元素 二分
8.第二题是算两个没有公共字母的字符串的最大长度积
9.LintCode - 反转二叉树
10.LintCode - 翻转字符串
11.单链表的快速排序
12.LintCode - 接雨水 III,写具体的方法和算法
13.整数去重问题
14.找出增序排列中一个数字第一次和最后一次出现的数组下标
15.海量数据去重
16.找出海量数据中前 10 个最大的数(数据有重复)
17.数组先升序在降序,找出最大数
18.正整数数组,拼出一个最大的正数
19.一个正整数数组,给其中一个数字加 1,使得所有数乘积最大,找出加 1 的那个数字
20.手写快排、堆排 二分查找
21.单词接龙的程序
22.括号匹配;
23.一个数组存着负数与正数,将正数放在前年,负数放在后面
24.母鸡、公鸡和小鸡问题:公鸡五块一只,母鸡三块一只,小鸡一块三只,用 100 元买 100
只鸡的
25.各种排序算法的时间复杂度和空间复杂度
26.Dijkstra(求最短路径)
27.旋转数组找某个数
28.哲学家问题
29.最大连续子序列和
30.最左前缀匹配
31.单链表反转并输出32.找到非排序数组中未出现的第一个正整数
33.在 0 到 n 这 n+1 个数中取 n 个数,如何找到缺少的那个。
34.链表中如何判断有环路
35.一个二维矩阵 n*n 中,n 对应表示各个节点,每个节点之间有连线就在相应
位置上标识 1,如何在其中判断出是不是一个图
(任一节点开始遍历,深度遍历,每遍历一个点进行一个标记,当深度遍历到自
己访问过的点时,代表存在环,即是个图)
36.二叉树中找出从根到叶子节点中和最大的那条路径
37.实现二叉树的广度优先遍历
38.手写直接插入排序
39.在一个字符串中找出第一个字符出现的位置,保证高效
40.N 级楼梯,一次一步或两步
41.深度优先遍历,广度优先遍历算法 在什么地方可以应用
42.杨辉三角形的算法,第 N 行的数的计算
43.给定两个全都是大写的字符串 a,b a 的长度大于 b 的长度,问如何判断 b 中的所有字符
都在 a 中(首先 a,b 排序,然后再两列比较)
44.一致性哈希算法
45.手写双向链表删除倒数第二个结点并分析
46.找到数组第三大数,没有则返回最大数
47.如何找到一条单链表的中间结点
48.从 10 亿个数中找不重复的数
将 10 亿个数排序后存在不同子文件中,每个子文件在内存中用 HashMap 来进行判断,
比如放入 map 中是(
int 和 boolean 它们封装类的键值对),第一次放进去时候 boolean 为
false,当 map 中有这个数之后再放进去时,将 false 改为 true。最后遍历 map 找出为 false
的数就是不重复的。
49.判断二叉树是否为平衡二叉树。
50.0G 文件的淘宝商品编号,只有 512M 内存,怎么判断究竟是不是合法编号(即编号是否
存在)
51.假如淘宝存着一个包含 10w 个敏感词的词库,紧接着需要从多个商品标题中随机抽查 3
个有没有包含敏感词的商品
52. 查找中间链表元素
53. 图算法
54 平衡树的旋转·
55.一道算法题,在一个整形数组中,有正数有负数,找出和最大的子串
56. 动态规划的思想
57. 给出一个字符数组,找出第一个出现次数最多的字符,注意是第一个
58.一个无序数组找第 K 大的元素
59.找出数组两个数的和等于给定的数
60. 无序数组找中位数(时间复杂度为 logN),
61.两个有序数组找中位数(时间复杂度为 logN)
62.写大数加法代码
63.输出二叉树从左边看过去能看到的所有节点
64.算法题:给定一个翻转过的有序数组,找出翻转点的下标,如:原数组 1,2,3,5,6,
7,8,翻转后的数组 5,6,7,8,1,2,3,翻转点下标是 565.给定一个整数数组,数组中元素无重复。和一个整数 limit,求数组元素全排列,要求相
邻两个数字和小于 limit
66.算法题:行列都有序二维数组,找出指定元素的位置,扩展到三维数组呢
67.输入指定个数的字符串,按照字符串长度进行排序,然后重新从短到长输出,排序算法要自
己写不能用自带的
68.求二叉树深度,比较坑的是,牛客网没有提供二叉树构造的输入样例,所以还要自己写个构
造二叉树的算法
69.二叉树的几种遍历方式
70.对整数分解质因数,90=2*3*3*5
71.二叉树非递归后续遍历
72.实现三个线程轮流打印 ABC 十次
73.列举集合的所有子集
74.给单链表排序,时间复杂度 O(nlogn),空间复杂度 O(1)
75.判断一个字符串能否被字典完全分词(dp)
76.找出只出现一次的数字…链表的中间节点,链表的第 n/m 个节点 找出链表的中间节点,
找出链表的三分之一节点,五分之一节点...
77.打印杨辉三角
78、手写栈实现队列
79.给定一个 2 叉树,打印每一层最右边的结点
80.给定一个数组,里面只有一个数出现了一次,其他都出现了两次。怎么得到这个出现了
一次的数?
81.在 6 基础上,如果有两个不同数的出现了一次,其他出现了两次,怎么得到这两个数?
82.查找有序数组和为 S 的数
83.如有个公司有 10000 名员工,要求按照年龄来排序要求时间复杂度 O(N)
83.两个 innt32 整数 m 和 n 的二进制表达有多少位不同
84. 全排列的算法思路84.字符串反转
85. 拓扑排序
86. .树的中序遍历,除了递归和栈还有什么实现方式 中序遍历的非递归做法?引出 BFS
和 DFS 的区别
87. 拓扑排序思想
88. 给定 n 个数,寻找第 k 小的数
89. 写了一个小程序,给定一段字符串,主要为赋值型的字符串,让把它们对应到 map 里
面
90. 1000 以内的素数
91. 手写希尔排序
92. 利用数组,实现一个循环队列类
93. 写一个汉诺塔问题,打印出转移路径,接着写一个二叉树前序遍历的代码,最后让写一
个多叉树实现,并层次遍历的代码,连写四个代码
94.。第一道题是一个字母组成的字符串,对该字符串字母序进行排序,大写在小写前面,时间
复杂度 O(n),如 AaB 是有序的,ABa 是无序的。第二道题计算 f(x,n)=x+x^2+.....+x^n,要求
乘法次数最少。
95. 拓扑排序思想
96.一个字符串数组,现给定一个 string 去进行找出对应的数组中字符串的下标
(可以有容
错,但两字符串长度必须一致,容错为 2)
例如: ["hello","hj","abc"]
key=“hellg" 返回下角标 0
97. 图的 prime 算法 kruskal 算法 dijkstra 算法 解决什么问题? 分别写一下 伪代码
98. 从一堆字符串中,去除重复的字符,并输出
99. 手写 Kmp 算法
100. 对一个基本有序的数组应该采用什么方式进行排序,对一个乱序的数组应该采用什么
方式排序能快速找到前 n 个数?为什么?
101. 给定一个数组, 里面放置任意数量的随机数, 如何快速统计出数组中重复的数字以及
出现次数
102. 给定字母集合(a-z), 求出由集合中这些字母组成的所有非空子集
103. 第一道题是用 5 行代码实现字符个数统计;第二题是反转单链表;第三题快速排序
104. 接着推导快速排序的时间复杂度为什么是 O(nlogn)?
105. 并发场景下的多线程代码水题
106. 算法题 一个数组里的数据只有一个是 3 个相同的,其他都是两个相同 怎么找出这个
数 围绕上一题优化
107. 字符转 int 型,考虑负数,异常等问题
108. 跳表
109. 给定 n 个左括号以及 n 个右括号,打印出所有合法的括号组合
110. 给定四个点如何判断是否为矩形
海量数据
海量 URL 数据,低内存情况下找重复次数最高的那一个
10 亿个数求 100 个最大的
大文件排序
给定三个大于 10G 的文件(每行一个数字)和 100M 内存的主机,找到在三个文件都
出现且次数最多的 10 个字符串
求两个 int 数组的并集、交集
1t query 统计前 k 个热门的
对 10G 个数进行排序,限制内存为 1G 大数问题,但是这 10G 个数可能是整数,字符
串以及中文改如何排序,
假如有 100 万个玩家,需要对这 100W 个玩家的积分中前 100 名的积分,按照顺序显
示在网站中,要求是实时更新的。积分可能由做的任务和获得的金钱决定。问如何对着 100万个玩家前 100 名的积分进行实时更新?
我跟他讨论了什么分治啊、Hash 啊,但后来他都说我的方法都是从全局的数据进行考虑的,
这样空间和时间要求太多,并且不现实。后来我跟他一顿讨论,最后他给出了解决方法,就
是利用缓存机制,缓存---tomcat---DB,层级计算,能不用到 DB 层就别用,因为每进一层,
实现起来都会更复杂和更慢。解决的思路就是,考虑出了前 100 名的后 100W-100 名玩家
的积分,让变化的积分跟第 100 名比较,如果比第 100 名高,那就替换的原则。
10 亿条短信,找出前一万条重复率高的
对一万条数据排序,你认为最好的方式是什么
一个大文件,里面是很多字符串,用最优的方式计算出一个字符串是否存在
QQ 每天都会产生大量的在线日志记录, 假设每天的在线日志记录有十亿条, 请设计一个算
法快速找出今天的在线人数
有 4 个文件,每个文件大小为 10G,每一行是一个单词,最后统计出 Top10 的单词
七.数据结构与算法
7.1 排序
https://blog.csdn.net/whuslei/article/details/6442755
7.1.1 直接插入排序public class Solution {
public void sortIntegers2(int[] A) {
InsertSort(A);
}
public void InsertSort(int[] A)
{
int i,j,k;
for(i=1;i<A.length;i++)
{
for(j=i-1;j>=0;j--)
{
if(A[j] <= A[i])//寻找第一个小于 A[i]的位置,也就是[i]
该插入的地方
break;
}
if(j != i-1) //这个判断的意思是如果是刚好 A[i-1]这个地方小
于 A[i],那么不需要操作
{
int temp = A[i];
for(k=i-1;k>j;k--)
A[k+1] = A[k];
A[k+1] = temp;//循环结束 k=j,故需要 k+1 放在该放的位置
上}
}
}
}
7.1.2 希尔排序
7.1.3 冒泡排序
7.1.4 快速排序public class Solution {
public void sortIntegers2(int[] A) {
// write your code here
quicksort(A,0,A.length-1);
}
public void quicksort(int[] A,int begin,int end)
{
int i = begin;
int j = end;
if(i >= j)
{
return;
}
int keng = A[i];
while(i < j)
{
while(i<j && A[j] > keng)
{
j--;
}
if(i<j && A[j] <= keng)
{
A[i] = A[j];
i++;
}
while(i<j && A[i] < keng)
{
i++;
}
if(i<j && A[i] >= keng){
A[j] = A[i];
j--;
}
}
A[i] = keng;
quicksort(A,begin,i-1);
quicksort(A,i+1,end);
}
}
import java.util.*;
public class Solution {
/**
* @param A: an integer array
* @return: nothing
*/
public void sortIntegers2(int[] A) {
quicksort(A,0,A.length-1);
}
public int partion(int[] A,int low,int high)
{
int hole = A[low];
int i = low;
int j = high;
if(low < high)
{
while(i < j)
{
while(i < j && A[j] >= hole)
j--;
if(i < j && A[j] < hole)
{
A[i] = A[j];
i++;
}
while(i < j && A[i] <= hole)
i++;
if(i < j && A[i] > hole)
{
A[j] = A[i];
j--;
}}
A[i] = hole;
return i;
}
return -1;
}
public void quicksort(int[] A,int low,int high)
{
LinkedList<Integer> stack = new LinkedList<>();
int k = -1;
if(low < high)
{
stack.offerFirst(low);
stack.offerFirst(high);
while(stack.size() != 0)
{
int right = (int)stack.poll();
int left = (int)stack.poll();
k = partion(A,left,right);
if(k-1 > left)
{
stack.offerFirst(left);
stack.offerFirst(k-1);
}
if(k+1 < right)
{
stack.offerFirst(k+1);
stack.offerFirst(right);
}
}
}
}
}
7.1.5 直接选择排序public class Solution {
public void sortIntegers2(int[] A) {
SelectSort(A);
}
public void SelectSort(int[] A)
{
for(int i=0;i<A.length;i++)
{
int minIndex = i;
for(int j=i;j<A.length;j++)
{
if(A[j] < A[minIndex])
minIndex = j;
}
int temp = A[i];
A[i] = A[minIndex];
A[minIndex] = temp;
}
}
}
7.1.6 堆排序7.1.7 归并排序public class Solution {
public void sortIntegers2(int[] A) {
int [] temp = new int[A.length];
mergeSort(A,0,A.length-1,temp);
}
public void mergeArray(int[] A,int leftBegin,int leftEnd,int
rightBegin,int rightEnd,int[] temp)
{
int i = leftBegin,j = rightBegin;
int k = 0;
while (i <= leftEnd && j <= rightEnd) //谁小就把谁弄到 temp 数
组里,直到有一方先全部弄完
{
if(A[i] < A[j])
temp[k++] = A[i++];
else
temp[k++] = A[j++];
}
while(i <= leftEnd) //无法确定哪一方先全部遍历结束,所以写了两
个循环,只要没结束就会继续复制到 temp 中
temp[k++] = A[i++];
while(j <= rightEnd)
temp[k++] = A[j++];
for(i=0;i<k;i++)
A[leftBegin+i] = temp[i]; //将排序好的 temp 复制到
A[leftBegin]到 A[rightEnd]中,完成排序
}
public void mergeSort(int[] A,int left,int right,int[] temp)
{
if(left < right)
{
int mid = (left + right) / 2;//递归的过程中一层层往下,直
到 left=right,也就是当前要归并的两数组都是只有一个元素
mergeSort(A,left,mid,temp);
mergeSort(A,mid+1,right,temp);
mergeArray(A,left,mid,mid+1,right,temp);
}
}
}
import java.util.*;
public class Solution {
public void sortIntegers2(int[] A) {
if(A.length == 0)return;
System.out.println(A.length+"");
int [] temp = new int[A.length];
mergeSort(A,0,A.length-1,temp);
}
public void mergeArray(int[] A,int leftBegin,int leftEnd,int
rightBegin,int rightEnd,int[] temp)
{
int i = leftBegin,j = rightBegin;
int k = 0;
while (i <= leftEnd && j <= rightEnd) //谁小就把谁弄到 temp 数
组里,直到有一方先全部弄完
{
if(A[i] < A[j])
temp[k++] = A[i++];
else
temp[k++] = A[j++];
}
while(i <= leftEnd) //无法确定哪一方先全部遍历结束,所以写了两
个循环,只要没结束就会继续复制到 temp 中
temp[k++] = A[i++];
while(j <= rightEnd)
temp[k++] = A[j++];
for(i=0;i<k;i++)
A[leftBegin+i] = temp[i]; //将排序好的 temp 复制到
A[leftBegin]到 A[rightEnd]中,完成排序
}
public void mergeSort(int[] A,int left,int right,int[] temp)
{
int s=2,i=0;
while(s <= A.length-1)
{
i = 0;
while(i+s <= A.length) //按照跨度 s 进行两两合并,s=2 两两
合并 s=4 四四合并
{
mergeArray(A,i,(i+i+s-1)/2,(i+i+s-1)/2+1,i+s-1,temp);
i += s;
}
mergeArray(A,i-s,i-1,i,A.length-1,temp);//合并尾部剩下的
数据,具体见我举的例子
s *= 2;
}
mergeArray(A,0,s/2-1,s/2,A.length-1,temp);//合并尾部与前面一大片已经排序序的部分。
}
}
7.1.8 基数排序
7.2 树
7.2.1 二分查找树7.3 LRU 实现
https://blog.csdn.net/hxqneuq2012/article/details/52709652
.红黑树与二叉树有什么区别、红黑树用途
直接上手红黑树和平衡二叉树区别
AVL 树的概念, 四种旋转方式, AVL 树左右旋转的例子
红黑树的旋转 2node 节点插入和 3node 节点插入时候旋转的情况 简述伪代码
字典树
链表使用的循环链表还是双向链表
树和图的区别
了解哈夫曼树、b+/b-树、红黑树
栈和队列的区别,如何实现栈
有序集合底层数据结构是
找到二叉树第 m 层的第 n 个节点
.链表的结构,操作的时间复杂度分析前缀树
堆与普通二叉树有什么区别
什么是递归,递归的几个条件?写递归要注意些什么?
八.数据库
8.1 索引 B 树 B+树
8.1.1 索引特点优缺点适用场合
http://blog.jobbole.com/24006/
http://www.yuanrengu.com/index.php/2017-01-13.html
8.1.1.1 索引的优缺点特点
8.1.1.2 索引使用的注意事项8.1.1.3 索引的适用场景
性别不适应用建立索引?为什么?
因为你访问索引需要付出额外的 IO 开销,你从索引中拿到的只是地址,要想真正访问
到数据还是要对表进行一次 IO。假如你要从表的 100 万行数据中取几个数据,那么利用索
引迅速定位,访问索引的这 IO 开销就非常值了。但如果你是从 100 万行数据中取 50 万行
数据,就比如性别字段,那你相对需要访问 50 万次索引,再访问 50 万次表,加起来的开
销并不会比直接对表进行一次完整扫描小。
8.1.2 Mysql 索引原理 B+树:
https://blog.csdn.net/v_july_v/article/details/6530142
https://blog.csdn.net/guoziqing506/article/details/64122287
索引是怎么优化查询效率的?这中间的过程能描述下吗?可以从 B+树原理回答
1. 索引为什么采用 B+树
B+树更有利于对数据库的扫描
B 树在提高了磁盘 IO 性能的同时并没有解决元素遍历的效率低下的问题,而 B+
树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以对于数据库中频繁使
用的 range query,B+树有着更高的性能。
B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更
小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字
数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 I/O 读写次数
也就降低了。
B+树的查询效率更加稳定
由于内部结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。
所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长
度相同,导致每一个数据的查询效率相当。
2. B+树
B+树是 B 树的一种变形,它更适合实际应用中操作系统的文件索引和数据库索引。
定义:
3. B-树(也就是 B 树)B-树搜索:
4. B+树与 B-树的区别
答:1.内部节点中,关键字的个数与其子树的个数相同,不像 B 树种,子树的个数总比关键字个数多 1 个
2.所有指向文件的关键字及其指针都在叶子节点中,不像 B 树,有的指向文件的关
键字是在内部节点中。换句话说,B+树中,内部节点仅仅起到索引的作用,
3.B+在搜索过程中,如果查询和内部节点的关键字一致,那么搜索过程不停止,而
是继续向下搜索这个分支,B+为了找到这个关键字的指针。
8.1.3 索引分类
https://www.cnblogs.com/heyonggang/p/6610526.html
8.1.3.0 索引分类
组合索引:实质上是将多个字段建到一个索引里,列值的组合必须唯一
聚集索引:定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个
表中只能拥有一个聚集索引。
非聚集索引:唯一索引 普通索引 主键索引 全文索引
UNIQUE(唯一索引):不可以出现相同的值,可以有 NULL 值
INDEX(普通索引):允许出现相同的索引内容
PROMARY KEY(主键索引):不允许出现相同的值
fulltext index(全文索引):可以针对值中的某个单词,但效率确实不敢恭维
8.1.3.1 创建索引语句
创建唯一,主键,普通,全文索引
创建组合索引8.1.3.2 索引类型
1.直接创建索引和间接创建索引
直接创建索引:CREATE INDEX mycolumn_index ON mytable (myclumn)
间接创建索引:定义主键约束或者唯一性键约束,可以间接创建索引
2.普通索引和唯一性索引
普通索引:CREATE INDEX mycolumn_index ON mytable (myclumn)
唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使
用
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
普通索引允许被索引的数据列包含重复的值。比如说,因为人有可能同名,所以同一个姓名
在同一个“员工个人资料”数据表里可能出现两次或更多次。
如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就
应该用关键字 UNIQUE 把它定义为一个唯一索引。这么做的好处:一是简化了 MySQL 对这
个索引的管理工作,这个索引也因此而变得更有效率;二是 MySQL 会在有新记录插入数据
表时,自动检查新记录的这个字段的值是否已经在某个记录的这个字段里出现过了;如果是,
MySQL 将拒绝插入那条新记录。也就是说,唯一索引可以保证数据记录的唯一性。事实上,
在许多场合,人们创建唯一索引的目的往往不是为了提高访问速度,而只是为了避免数据出
现重复。
3.单个索引和复合索引
单个索引:即非复合索引
复合索引:又叫组合索引,在索引建立语句中同时包含多个字段名,最多 16 个字段
CREATE INDEX name_index ON username(firstname,lastname)
4.聚簇索引和非聚簇索引(聚集索引,群集索引)
https://www.cnblogs.com/s-b-b/p/8334593.html
MySQL 里主键就是聚集索引
定义:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。5.主索引 和外键索引
在前面已经反复多次强调过:必须为主键字段创建一个索引,这个索引就是所谓的“主索引”。
主索引与唯一索引的唯一区别是:前者在定义时使用的关键字是 PRIMARY 而不是 UNIQUE。
主索引:CREATE PRIMARY COUSTERED INDEX myclumn_cindex ON mytable(mycolumn) 或
者 mysql>ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
外键索引:外键索引 mysql>ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
6.全文索引
http://www.360doc.com/content/17/1211/13/33260087_712076317.shtml7.空间索引
空间索引是指依据空间对象的位置和形状或空间对象之间的某种空间关系按一定的顺序排
列的一种数据结构 ,其中包含空间对象的概要信息,如对象的标识、外接矩形及指向空间
对象实体的指针。
区别:它将空间对象按范围划分,每个结点都对应一个区域和一个磁盘页,非叶结点的磁盘
页中
数据库从左到右原则
8.2 innoDB 与 MyISAM 引擎区别
两种存储引擎在索引上的区别:
https://blog.csdn.net/zsq520520/article/details/68954646(图 inndb 主键索引)是 InnoDB 主索引(同时也是数据文件)的示意图,可以看到叶节点包含
了完整的数据记录。这种索引叫做聚集索引。因为 InnoDB 的数据文件本身要按主键聚集,所以 InnoDB
要求表必须有主键(MyISAM 可以没有),如果没有显式指定,则 MySQL 系统会自动选择一个可以
唯一标识数据记录的列作为主键,如果不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字
段作为主键,这个字段长度为 6 个字节,类型为长整形8.3 事务隔离级别(恶果:脏读 幻读 不可重复读)
Mysql 默认级别可重复读8.4 数据库特性 ACID
1.原子性(
Atomicity):原子性很容易理解,也就是说事务里的
所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所
有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。
2.一致性(
Consistency):从一个一致性状态到另一个一致性状
态。
例如现有完整性约束 a+b=10,如果一个事务改变了 a,那么必须
得改变 b,使得事务结束后依然满足 a+b=10,否则事务失败。
3.隔离性(Isolation):一个事务所做的修改在最终提交以前,
对其它事务不可见。
比如现有有个交易是从 A 账户转 100 元至 B 账户,在这个交易还
未完成的情况下,如果此时 B 查询自己的账户,是看不到新增加的
100 元的。
4.(Durability) 持久性
持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库
上,即使出现宕机也不会丢失。
8.5 sql
8.5.1.Sql 优化8.6 5 种连接 left join、right join、inner join,full join cross join
https://www.cnblogs.com/pcjim/articles/799302.html
sql 之 left join、right join、inner join 的区别
left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录
inner join(等值连接) 只返回两个表中联结字段相等的行
举例如下:
--------------------------------------------
表 A 记录如下:
aID
aNum
1
a20050111
2
a20050112
3
a20050113
4
a20050114
5
a20050115
表 B 记录如下:
bID
bName1
2006032401
2
2006032402
3
2006032403
4
2006032404
8
2006032408
--------------------------------------------
1.left join
sql 语句如下:
select * from A
left join B
on A.aID = B.bID
结果如下:
aID
aNum
bID
bName
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
5
a20050115
NULL
NULL
(所影响的行数为 5 行)
结果说明:
left join 是以 A 表的记录为基础的,A 可以看成左表,B 可以看成右表,left join 是以左表为准的.
换句话说,左表(A)的记录将会全部表示出来,而右表(B)只会显示符合搜索条件的记录(例子中为: A.aID = B.bID).
B 表记录不足的地方均为 NULL.
--------------------------------------------
2.right join
sql 语句如下:
select * from A
right join B
on A.aID = B.bID
结果如下:
aID
aNum
bID
bName
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
NULL
NULL
8
2006032408
(所影响的行数为 5 行)
结果说明:
仔细观察一下,就会发现,和 left join 的结果刚好相反,这次是以右表(B)为基础的,A 表不足的地方用 NULL 填充.--------------------------------------------
3.inner join
sql 语句如下:
select * from A
innerjoin B
on A.aID = B.bID
结果如下:
aID
aNum
bID
bName
1
a20050111
1
2006032401
2
a20050112
2
2006032402
3
a20050113
3
2006032403
4
a20050114
4
2006032404
结果说明:
很明显,这里只显示出了 A.aID = B.bID 的记录.这说明 inner join 并不以谁为基础,它只显示符合条件的记录.
--------------------------------------------
注:
LEFT JOIN 操作用于在任何的 FROM 子句中,组合来源表的记录。使用 LEFT JOIN 运算来创建一个左边外部联接。左边外
部联接将包含了从第一个(左边)开始的两个表中的全部记录,即使在第二个(右边)表中并没有相符值的记录。
语法:FROM table1 LEFT JOIN table2 ON table1.field1 compopr table2.field2
说明:table1, table2 参数用于指定要将记录组合的表的名称。
field1, field2 参数指定被联接的字段的名称。且这些字段必须有相同的数据类型及包含相同类型的数据,但它们不需要有相同
的名称。
compopr 参数指定关系比较运算符:"=", "<", ">", "<=", ">=" 或 "<>"。
如果在 INNER JOIN 操作中要联接包含 Memo 数据类型或 OLE Object 数据类型数据的字段,将会发生错误.
4.全外连接(
full join ...on...)
select * from table1 a full join table2 b on a.id=b.id 全外连接其实是左连接和右
连接的一个合集,也就是说他会查询出左表和右表的全部数据,匹配不上的会显示为 null;
如下图:
5.交叉连接(cross join...)
select * from table1 a crossjoin table2 b ;也可以写为 select * from table1,table2;
交叉连接,也称为笛卡尔积,查询返回结果的行数等于两个表行数的乘积
8.7 数据库范式
8.8 数据库连接池
8.8.1 数据库连接池原理
https://blog.csdn.net/xiebaochun/article/details/28901363
https://blog.csdn.net/shuaihj/article/details/14223015
连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、
连接池的关闭。
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创
建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,
这样避免了连接随意建立和关闭造成的系统开销。Java 中提供了很多容器类可以方便的构
建连接池,例如 Vector、Stack 等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对
系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连
接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,
如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等
待,如果超出最大等待时间,则抛出异常给客户。 当客户释放数据库连接时,先判断该连
接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户
服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资
源,该过程正好与创建相反
8.8.2 数据库连接池的示例代码
像打开关闭数据库连接这种和数据库的交互可能是很费时的,尤其是当客户端数量增加的时
候,会消耗大量的资源,成本是非常高的。可以在应用服务器启动的时候建立很多个数据库
连接并维护在一个池中。连接请求由池中的连接提供。在连接使用完毕以后,把连接归还到
池中,以用于满足将来更多的请求。
数据库分页查询8.9 DDL DML DCL
DML(data manipulation language):
它们是 SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这 4 条命令是用来对数据库里的数据进行操作的语
言
DDL is Data Definition Language statements. Some examples:数据定义语言,用于定义和管理 SQL 数
据库中的所有对象的语言
1.CREATE - to create objects in the database 创建
2.ALTER - alters the structure of the database 修改
3.DROP - delete objects from the database 删除
4.TRUNCATE - remove all records from a table, including all spaces allocated for the records are removed
DCL is Data Control Language statements. Some examples:数据控制语言,用来授予或回收访问数据库的
某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等
1.COMMIT - save work done 提交
2.SAVEPOINT - identify a point in a transaction to which you can later roll back 保存点
3.ROLLBACK - restore database to original since the last COMMIT 回滚
4.SET TRANSACTION - Change transaction options like what rollback segment to use 设置当前事务的特性,它对后面
的事务没有影响.
8.10 explain
https://www.cnblogs.com/gomysql/p/3720123.ht
ml
8.10.1.什么是 explain?
需要知道该 SQL 的执行计划,比如是全表扫描,还是索引扫描,这些都需要通过
EXPLAIN 去完成。EXPLAIN 命令是查看优化器如何决定执行查询的主要方法。
8.10.2 explain 命令详解
1.id包含一组数字,表示查询中执行 select 子句或操作表的顺序(第一个 select 是 1,
子查询是 2.子子查询是 3.依次类推)
2.select_type
示查询中每个 select 子句的类型(简单 OR 复杂)
a. SIMPLE:查询中不包含子查询或者 UNION
b. 查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
c. 在 SELECT 或 WHERE 列表中包含了子查询,该子查询被标记为:SUBQUERY
d. 在 FROM 列表中包含的子查询被标记为:DERIVED(衍生)用来表示包含在 from
子句中的子查询的 select,mysql 会递归执行并将结果放到一个临时表中。服务器内部
称为"派生表",因为该临时表是从子查询中派生出来的
e. 若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;若 UNION 包含在
FROM 子句的子查询中,外层 SELECT 将被标记为:DERIVED
f. 从 UNION 表获取结果的 SELECT 被标记为:UNION RESULT
SUBQUERY 和 UNION 还可以被标记为 DEPENDENT 和 UNCACHEABLE。
DEPENDENT 意味着 select 依赖于外层查询中发现的数据。
UNCACHEABLE 意味着 select 中的某些 特性阻止结果被缓存于一个 item_cache 中。
3.type
表示 MySQL 在表中找到所需行的方式,又称“访问类型”,常见类型如下:
ALL, index, range, ref, eq_ref, const, system, NULL(全局遍历啦,还是索引查询等)
4.possible_keys
指出 MySQL 能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该
索引将被列出,但不一定被查询使用(表中的某一列上有多个索引都和这个有关系,索
引建立在这个字段上了,那么都列出来)
5.key
显示 MySQL 在查询中实际使用的索引,若没有使用索引,显示为 NULL
6.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
7. ref
表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
8. rows
表示 MySQL 根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
9. Extra
包含不适合在其他列中显示但十分重要的额外信息
10.table
Select 查询的表的名字8.11 分库分表
https://blog.csdn.net/winy_lm/article/details/50708493
8.11.1 概念
1. 分表
用户 id 直接 mod 分成表的数目大小,将大表拆成小表
2. 分库
同分表
3. 分库分表
1. 中间变量 = user_id % (分库数量 * 每个库的表数量)
2. 库 = 取整数 (中间变量 / 每个库的表数量)
3. 表 = 中间变量 % 每个库的表数量
还有一种就是分库分表的垂直切分和水平划分 个人感觉这个垂直划分就是上文的分库
1. 垂直切分
将关联的表切分到数据库中(不涉及到切分数据表的操作)。
2. 水平切分
将一个表中的数据切分到不同的数据库中。
8.11.2 垂直切分水平切分的坏处8.11.3 如何解决分库分表带来的坏处
8.11.3.1 ACID 解决方法
8.11.3.2 水平切分 ID 被破坏(递增 ID)8.11.3.3 跨库 join
数据冗余就是分开表以后,在每个表中多添加一些被分走的数据的信息,可能
会重复,但是可以在一个库中进行 join(就是把需要的数据就算其它数据库有了,
我还是要添加到我的数据库中)8.11.3.4 外键约束解决
8.12 数据库锁
8.12.1 封锁8.12.2 封锁协议(解决脏读不可重复读)8.12.3 死锁活锁8.12.3 解决死锁的方法
8.12.3.1 死锁的预防8.12.3.2 死锁的诊断与解除8.12.4 两段锁协议
8.12.5 GAP 锁(解决幻读)
在索引记录的间隙上加锁,禁止插入,这样就避免了幻读
具体例子:
https://blog.csdn.net/cug_jiang126com/article/details/505967298.12.6 next-key 锁
https://blog.csdn.net/xifeijian/article/details/20313977
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符
合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间
隙(GAP)”,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。
举例来说,假如 emp 表中只有 101 条记录,其 empid 的值分别是 1,2,...,100,101,下
面的 SQL:
Select * from emp where empid > 100 for update;
是一个范围条件的检索,InnoDB 不仅会对符合条件的 empid 值为 101 的记录加锁,也会对
empid 大于 101(这些记录并不存在)的“间隙”加锁。
InnoDB 使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对
于上面的例子,要是不使用间隙锁,如果其他事务插入了 empid 大于 100 的任何记录,那
么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的
需要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下 InnoDB 使用间隙锁的情况,
在后续的章节中会做进一步介绍。
8.13 其它问题8.13.1 limit20000 如何优化
http://uule.iteye.com/blog/2422189
1.子查询优化法
原始 slq 语句:select * from Member limit 10000,100
先找出第一条数据,然后大于等于这条数据的 id 就是要获取的数据
缺点:数据必须是连续的,可以说不能有 where 条件,where 条件会筛选数据,导致数据失去连续性
这种:
Sql 代码
1.
select * from Member where MemberID >= (select MemberID fro
m Member limit 100000,1) limit 100
2、使用 id 限定优化
原始 sql 语句:select* from orders_history where type=2 limit 100000,100.
这种方式假设数据表的 id 是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的 id 的范围,可以使用 id
between and 来查询:
Java 代码
1.
select * from orders_history where type=2 and id between 100
0000 and 1000100 limit 100;
3.反向查找优化法
当偏移超过一半记录数的时候,先用排序,这样偏移就反转了
缺点:order by 优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数
,偏移大于数据的一半
8.13.2 数据库的隔离级别 隔离级别如何实现
(封锁协议 X 锁 S 锁 GAP 锁)
8.13.3 char varchar text 区别
https://blog.csdn.net/wxq1987525/article/details/6564380
具体对这三种类型的说明不做阐述可以查看 mysql 帮助文档。
char 的总结:
char 最大长度是 255 字符,注意是字符数和字符集没关系。可以有默认值,尾部有空
格会被截断。
varchar 的总结:
varchar 的最大长度 65535 是指能存储的字节数,其实最多只能存储 65532 个字节,还有 3 个字节用于存储长度。注意是字节数这个和字符集有关系。一个汉字字符用 utf8 占用 3
字节,用 gbk 占用 2 字节。可以有默认值,尾部有空格不会截断。
text 的总结:
text 和 varchar 基本相同。text 会忽略指定的大小这和 varchar 有所不同,text 不能有
默认值。尾部有空格不会被截断。text 使用额外的 2 个字节来存储数据的大小,varchar 根
据存储数据的大小选择用几个字节来存储。text 的 65535 字节全部用来存储数据,varchar
则会占用 1-3 个字节去存储数据大小。
上面所说的一切只针对 mysql,其他数据库可能不同。有不妥的地方请指出
8.13.4 drop delete truncate 区别
delete 和 truncate 只删除表的数据不删除表的结构
速度,一般来说: drop> truncate >delete
delete 语句是 dml,这个操作会放到 rollback segement 中,事务提交之后才生效;
如果有相应的 trigger,执行的时候将被触发. truncate,drop 是 ddl, 操作立即生效,原数据不放到 rollback segme
nt 中,不能回滚. 操作不触发 trigger.
drop 直接删掉表
truncate 删除表中数据,再插入时自增长 id 又从 1 开始
delete 删除表中数据,可以加 where 字句。
8.13.5 事务
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都
执行,要么都不执行,它是一个不可分割的工作单位。事务是数据库维护数据一致性的单位,在每
个事务结束时,都能保持数据一致性。
8.13.6 超键、候选键、主键、外键 视图
超键:在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个
属性组合在一起也可以作为一个超键。超键包含候选键和主键。
候选键:是最小超键,即没有冗余元素的超键。
主键:数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一
个主键,且主键的取值不能缺失,即不能为空值(Null)。
外键:在一个表中存在的另一个表的主键称此表的外键。
视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有
一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比
多表查询。
8.13.7 存储过程与触发器
存储过程(
Stored Procedure)是一组为了完成特定功能的 SQL 语句集,经编译后存储在数据库中,用户通过指定
存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。B+树和 B 树的区别 插入节点怎么分裂
有人建议给每张表都建一个自增主键,这样做有什么优点跟缺点
MyISAM、InnoDB 的区别(一个 B+树叶子节点存的地址,一个是直接存的数据)
sql,group by 的理解
对 MySQL 的了解,和 oracle 的区别
mybatis 中 %与$的区别
一个成绩表求出有不及格科目的同学
500 万数字排序,内存只能容纳 5 万个,如何排序,如何优化?
平时怎么写数据库的模糊查询(由字典树扯到模糊查询,前缀查询,例如“abc%”,还是索
引策略的问题)
数据库里有 10000000 条用户信息,需要给每位用户发送信息(必须发送成功),要求节省
内存(主键索引、分区技术、异步处理)
数据库连接池(
druid)、线程池作用等等
数据库设计与优化能力:
数据库基本知识(存取控制、触发器、存储过程(了解作用)、游标(了解作用)
基本数据库安全
数据库设计,不多讲看看 i 西科和圈子表结构设计(满足三范式等等),多思考。
并发控制(并发数据不一致性、事务隔离级别、乐观锁与悲观锁等)
mysql 锁机制
项目中如何实现事务
数据库设计一般设计成第几范式
mysql 用的什么版本 5.7 跟 5.6 有啥区别 提升 MySQL 安全性问了一个这样的表(三个字段:姓名,id,分数)要求查出平均分大于 80 的 id 然后分数降序排
序。 然后经过提示用聚合函数 avg。
select id from table group by id having avg(score) > 80 order by avg(score) desc。
,为什么 mysql 事务能保证失败回滚 ACID
学生表,成绩表,说出查找学生总成绩大于 500 的学生的姓名跟总分
一道算法题,在一个整形数组中,找出第三大的数,注意时间效率
主键索引底层的实现原理?B+树
经典的 01 索引问题?
如何在长文本中快捷的筛选出你的名字? 全文索引
多列索引及最左前缀原则和其他使用场景
数据库的完整性约束,事务隔离级别,写一个 SQL 语句,索引的最左前缀原则
数据库悲观锁怎么实现的
建表的原则
索引的内涵和用法
写怎么创建表
给了两条 SQL 语句,让根据这两条语句建索引(个人想法:主要考虑复合索引只能匹配前
缀列的特点)
select 语句实现顺序
那么我们来聊一下数据库。A 和 B 两个表做等值连接(Inner join) 怎么优化 哈希
数据库连接池的理解和优化
.数据库事物,什么是事物,什么情况下会用到事物,举例说明
Sql 语句。增删改查基本语句,建表建索引。需要注意细节,比如 count 是否计算 null 值
的行等。
Sql 语句 分组排序
SQL 语句的 5 个连接概念
数据库优化和架构(主要是主从分离和分库分表相关) 分库分表 跨库 join 实现 探
讨 主从分离和分库分表相关
数据库中间件
跨库 join
读写分离在中间件的实现
限流 and 熔断
行锁适用场景九.网络
9.1.HTTP
9.1.1 http 请求报文 & http 响应报文
1.http 请求报文
http 响应报文由状态行,响应头部,空行,响应数据组成
2.http 响应报文
HTTP 响应由四个部分组成:
1.状态码(Status Code):描述了响应的状态。可以用来检查是否成功的完成了请求。请求失
败的情况下,状态码可用来找出失败的原因。如果 Servlet 没有返回状态码,默认会返回成
功的状态码 HttpServletResponse.SC_OK。
2.HTTP 头部(HTTP Header):它们包含了更多关于响应的信息。比如:头部可以指定认为
响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式。如何在
Serlet 中检索 HTTP 的头部看这里。
3.空行
4.主体(Body):它包含了响应的内容。它可以包含 HTML 代码,图片,等等。主体是由传输
在 HTTP 消息中紧跟在头部后面的数据字节组成的。
9.1.2 http 报文头部请求头和响应头9.1.3 http 请求方法
9.1.4 http 请求过程
以下是 HTTP 请求/响应的步骤:1、客户端连接到 Web 服务器
一个 HTTP 客户端,通常是浏览器,与 Web 服务器的 HTTP 端口(默认为 80)建立
一个 TCP 套接字连接。例如,http://www.oakcms.cn。
2、发送 HTTP 请求
通过 TCP 套接字,客户端向 Web 服务器发送一个文本的请求报文,一个请求报文由
请求行、请求头部、空行和请求数据 4 部分组成。
3、服务器接受请求并返回 HTTP 响应
Web 服务器解析请求,定位请求资源。服务器将资源复本写到 TCP 套接字,由客户端
读取。一个响应由状态行、响应头部、空行和响应数据 4 部分组成。
4、释放连接 TCP 连接
若 connection 模式为 close,则服务器主动关闭 TCP 连接,客户端被动关闭连接,
释放 TCP 连接;若 connection 模式为 keepalive,则该连接会保持一段时间,在该时间内
可以继续接收请求;
5、客户端浏览器解析 HTML 内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响
应头,响应头告知以下为若干字节的 HTML 文档和文档的字符集。客户端浏览器读取响应
数据 HTML,根据 HTML 的语法对其进行格式化,并在浏览器窗口中显示。
1.http 协议,ajax; 协议的划分
6. http 请求流程
http 长连接设置 短链接
http 怎么记住状态
http 是无状态的怎么来
9.1.4 Get 和 Post 区别幂等
从定义上看,HTTP 方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。
post 并不是幂等的。
9.1.5 http 状态码600 源站没有返回响应头部,只返回实体内容
9.1.6 http 长连接 短连接 HTTP 协议是无状态
无状态:HTTP 协议是无状态的,指的是协议对于事务处理没有记忆能力,服务器不知
道客户端是什么状态。也就是说,打开一个服务器上的网页和上一次打开这个服务器上的网
页之间没有任何联系。HTTP 是一个无状态的面向连接的协议,无状态不代表 HTTP 不能保
持 TCP 连接,更不能代表 HTTP 使用的是 UDP 协议(无连接)。
https://www.cnblogs.com/gotodsp/p/6366163.html
短连接:在 HTTP/1.0 中默认使用短连接。也就是说,客户端和服务器每进行一次 HTTP
操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类
型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每
遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。
长连接:而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。使用长连接的 HTTP
协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据
的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)
中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。
什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个 TCP 连接都需
要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,次处理时直接发送数据包就 OK 了,不用建立 TCP 连接。例如:
数据库的连接用长连接, 如果用短连接频繁的通信会造成 socket 错误,而且频繁的 socket创建也是对资源的浪费。
而像 WEB 网站的 http 服务一般都用短链接,因为长连接对于服务端来说会耗费一定的
资源,而像 WEB 网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而
知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好
9.1.7 http1.1 与 http1.0 的区别
1.http1.0 需要 keep-alive 参数来告知服务器要建立一个长连接,而 http1.1 默认支持长连接
2.HTTP 1.1 支持只发送 header 信息(不带任何 body 信息),如果服务器认为客户端有权限
请求服务器,则返回 100,否则返回 401。客户端如果接受到 100,才开始把请求 body 发
送到服务器。这样当服务器返回 401 的时候,客户端就可以不用发送请求 body 了,节约了
带宽。
3.host 域 http1.0 没有 host 域,http1.1 才支持这个参数。
4.带宽优化及网络连接的使用,HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的
一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range
头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的
选择以便于充分利用带宽和连接。
9.1.8 http2.0 与 http1.0 的区别
https://www.cnblogs.com/heluan/p/8620312.html
新的二进制格式(Binary Format),HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在
天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0
和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing),即连接共享,建立起一个连接请求后,可以在这个链接上一直发送,不
要等待上一次发送完并且受到回复后才能发送下一个(http1.0 是这样),是可以同时发送多个请求,
互相并不干扰。
header 压缩,如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而且每次都要重
复发送,HTTP2.0 利用 HPACK 对消息头进行压缩传输,客服端和服务器维护一个动态链表(当一
个头部没有出现的时候,就插入,已经出现了就用表中的索引值进行替代),将既避免了重复 head
er 的传输,又减小了需要传输的大小。(Hpack https://www.jianshu.com/p/f44b930cfcac)
服务端推送(
server push),就是客户端请求 html 的时候,服务器顺带把此 html 需要的 css,js
也一起发送给客服端,而不像 http1.0 中需要请求一次 html,然后再请求一次 css,然后再请求一
次 js。
9.1.9 转发与重定向的区别
一句话,转发是服务器行为,重定向是客户端行为。为什么这样说呢,这就要看两个动作
的工作流程:转发过程:客户浏览器发送 http 请求----》web 服务器接受此请求--》调用内部的一个方法在容
器内部完成请求处理和转发动作----》将目标资源发送给客户;在这里,转发的路径必须是同一
个 web 容器下的 url,其不能转向到其他的 web 路径上去,中间传递的是自己的容器内的 requ
est。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器
做了转发的。转发行为是浏览器只做了一次访问请求。
重定向过程:客户浏览器发送 http 请求----》web 服务器接受后发送 302 状态码响应及对应新的
location 给客户浏览器--》客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求
url 是新的 location 地址----》服务器根据此请求寻找资源并发送给客户。在这里 location 可以重
定向到任意 URL,既然是浏览器重新发出了请求,则就没有什么 request 传递的概念了。
在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为
是浏览器做了至少两次的访问请求的。
9.2.TCP UDP
https://blog.csdn.net/oney139/article/details/8103223
9.2.0 TCP 头部的 IP,TCP 协议号。
URG=1
当 URG 字 段 被 置 1, 表 示 本 数 据 报 的 数 据 部 分 包 含 紧 急 信 息 , 此 时 紧 急 指 针
有 效 。紧 急 数 据 一 定 位 于 当 前 数 据 包 数 据 部 分 的 最 前 面 ,紧 急 指 针 标 明 了 紧 急
数 据 的 尾 部 。如 control+c:这 个 命 令 要 求 操 作 系 统 立 即 停 止 当 前 进 程 。此 时 ,
这 条 命 令 就 会 存 放 在 数 据 包 数 据 部 分 的 开 头 , 并 由 紧 急 指 针 标 识 命 令 的 位 置 ,
并 URG 字 段 被 置 1。
PSH=1
当 接 收 方 收 到 PSH=1 的 报 文 后 ,会 立 即 将 数 据 交 付 给 应 用 程 序 ,而 不 会 等 到
缓 冲 区 满 后 再 提 交 。一 些 交 互 式 应 用 需 要 这 样 的 功 能 ,降 低 命 令 的 响 应 时 间 。
RST=1
当 该 值 为 1 时 , 表 示 当 前 TCP 连 接 出 现 严 重 问 题 , 必 须 要 释 放 重 连 。
三次握手需要的信息:暂时需要的信息有:ACK : TCP 协议规定,只有 ACK=1 时有效,也规定连接建立后所有发送的报文的
ACK 必须为 1。
SYN(SYNchronization) : 在连接建立时用来同步序号。当 SYN=1 而 ACK=0 时,表
明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使 SYN=1 和 ACK=1.
因此, SYN 置 1 就表示这是一个连接请求或连接接受报文。
FIN (
finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段
的发送方的数据已经发送完毕,并要求释放连接
9.2.1 TCP 与 UDP 区别
UDP 首部 8 个字节,TCP 首部最低 20 个字节。
对应的协议不同
TCP 对应的协议:
(1) FTP:定义了文件传输协议,使用 21 端口。常说某某计算机开了 FTP 服务便是启动了文件传
输服务。下载文件,上传主页,都要用到 FTP 服务。(
2) Telnet:它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这
种端口可以提供一种基于 DOS 模式下的通信服务。如以前的 BBS 是-纯字符界面的,支持 BBS 的服
务器将 23 端口打开,对外提供服务。
(
3) SMTP:定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件。
如常见的免费邮件服务中用的就是这个邮件服务端口,所以在电子邮件设置-中常看到有这么 SMTP
端口设置这个栏,服务器开放的是 25 号端口。
(
4) POP3:它是和 SMTP 对应,POP3 用于接收邮件。通常情况下,POP3 协议所用的是 110 端
口。也是说,只要你有相应的使用 POP3 协议的程序(例如 Fo-xmail 或 Outlook),就可以不以 We
b 方式登陆进邮箱界面,直接用邮件程序就可以收到邮件(如是 163 邮箱就没有必要先进入网易网站,
再进入自己的邮-箱来收信)。
(
5)HTTP 协议:是从 Web 服务器传输超文本到本地浏览器的传送协议。
UDP 对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为 IP 地址。DNS 用的是 53 号端口。
(
2) SNMP:简单网络管理协议,使用 161 号端口,是用来管理网络设备的。由于网络设备很多,
无连接的服务就体现出其优势。
(
3) TFTP(Trival File Transfer Protocal),简单文件传输协议,该协议在熟知端口 69 上使用 UDP
服务。9.2.2 TCP 三次握手
1.首先由 Client 发出请求连接即 SYN=1 ACK=0 (请看头字段的介绍), TCP 规定 SYN=1
时不能携带数据,但要消耗一个序号,因此声明自己的序号是 seq=x
2.然后 Server 进行回复确认,即 SYN=1 ACK=1 seq=y, ack=x+1,
3.再然后 Client 再进行一次确认,但不用 SYN 了,这时即为 ACK=1, seq=x+1,
ack=y+1.然后连接建立,为什么要进行三次握手呢(两次确认)。
为什么采用三次握手而不是采用两次握手?9.2.3 TCP 四次挥手
TIME_WAIT 阶 段 要 等 待 2 个 MSL 时 间 才 关 闭 , 因 为 网 络 原 因 可 能 要 重 发 。
CLOSE_WAIT 是要等待自己(通常是服务器)把自己传输东西发送完了。9.2.4 tcp 粘包问题 nagle 算法9.2.5 tcp 如何保证可靠性传输
1.数据包校验:目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢
弃报文段并且不给出响应,这时 TCP 发送数据端超时后会重发数据;
2.对失序数据包重排序:既然 TCP 报文段作为 IP 数据报来传输,而 IP 数据报的到
达可能会失序,因此 TCP 报文段的到达也可能会失序。TCP 将对失序数据进行重新
排序,然后才交给应用层;
3.丢弃重复数据:对于重复数据,能够丢弃重复数据;
4.应答机制:当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。这个确
认不是立即发送,通常将推迟几分之一秒;
5.超时重发:当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个
报文段。如果不能及时收到一个确认,将重发这个报文段;
6.流量控制:TCP 连接的每一方都有固定大小的缓冲空间。TCP 的接收端只允许另一
端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区
溢出,这就是流量控制。TCP 使用的流量控制协议是可变大小的滑动窗口协议。https://coolshell.cn/articles/11564.html
https://coolshell.cn/articles/11609.html
http://blog.chinaunix.net/uid-26275986-id-4109679.html
https://blog.csdn.net/jhh_move_on/article/details/45770087
9.2.6 TCP 流量控制 拥塞控制
https://blog.csdn.net/sicofield/article/details/9708383
拥塞控制
9.2.6.1 慢开始与拥塞避免
1.慢开始
发送方维持一个叫做拥塞窗口 cwnd(
congestion window)的状态变量。拥塞窗口的大小取决于
网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受
方的接收能力,发送窗口可能小于拥塞窗口。
慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就
是说由小到大逐渐增加拥塞窗口的大小。
这里用报文段的个数的拥塞窗口大小举例说明慢开始算法,实时拥塞窗口大小是以字节为
单位的。如下图:解释一下这张图:就是发送方每收到一个确认就 cwnd+1,也就是说发送方发送 2 就收
到 2 个,所以就是 cwnd 就是 4,也就是翻倍成长的道理,每次都是翻倍,也就是指数增长。
为了防止 cwnd 增长过大引起网络拥塞,还需设置一个慢开始门限 ssthresh 状态变量。ssthresh 的用法如下:
当 cwnd<ssthresh 时,使用慢开始算法。
当 cwnd>ssthresh 时,改用拥塞避免算法。
当 cwnd=ssthresh 时,慢开始与拥塞避免算法任意。
2.拥塞避免
拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间 RTT 就把发送方的拥塞
窗口 cwnd 加 1,而不是加倍。这样拥塞窗口按线性规律缓慢增长。
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是
没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都
当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞
窗口设置为 1,执行慢开始算法。如下图:
9.2.6.2 快速重传快速恢复快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方
及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,
发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待
设置的重传计时器时间到期。如下图:
快重传配合使用的还有快恢复算法,有以下两个要点:
①当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把 ssthresh 门限减半。但是
接下去并不执行慢开始算法。
②考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可
能没有出现拥塞。所以此时不执行慢开始算法,而是将 cwnd 设置为 ssthresh 的大小,然后
执行拥塞避免算法。如下图:
9.2.7 滑动窗口机制
https://blog.csdn.net/GitChat/article/details/785468989.2.8 TCP 状态转移
1.CLOSED:起始
点,在超时或者连接关闭时候进入此状态。
2.LISTEN:svr 端在等待连接过来时候的状态,svr 端为此要调用 socket,
bind,listen 函数,就能
进入此状态。此称为应用程序被动打开(等待客户端来连接)。
3.SYN_SENT:客户端发起连接,发送 SYN 给服务器端。如果服务器端不能连接,则直
接进入
CLOSED 状态。
4.SYN_RCVD:跟 3 对应,服务器端接受客户端的 SYN 请求,服务器端由 LISTEN 状态
进入SYN_RCVD 状态。同时服务器端要回应一个 ACK,同时发送一个 SYN 给客户端;另外
一种情
况,客户端在发起 SYN 的同时接收到服务器端得 SYN 请求,客户端就会由 SYN_SENT
到
SYN_RCVD 状态。
5. ESTABLISHED:服务器端和客户端在完成 3 次握手进入状态,说明已经可以开始
传输数据了
以上是建立连接时服务器端和客户端产生的状态转移说明。相对来说比较简单明了,
如果你
对三次握手比较熟悉,建立连接时的状态转移还是很容易理解。
接下来服务器端和客户端就进行数据传输。。。。,当然,里面也大有学问,就此
打住,稍
后再表。
下面,我们来看看连接关闭时候的状态转移说明,关闭需要进行 4 次双方的交互,
还包括要处
理一些善后工作(
TIME_WAIT 状态),注意,这里主动关闭的一方或被动关闭的一
方不是指
特指服务器端或者客户端,是相对于谁先发起关闭请求来说的:
6.FIN_WAIT_1:主动关闭的一方,由状态 5 进入此状态。具体的动作时发送 FIN 给对
方。
7.FIN_WAIT_2:主动关闭的一方,接收到对方的 FIN ACK,进入此状态。由此不能再
接收对方
的数据。但是能够向对方发送数据。
8.CLOSE_WAIT:接收到 FIN 以后,被动关闭的一方进入此状态。具体动作时接收到
FIN,同
时发送 ACK。
9.LAST_ACK:被动关闭的一方,发起关闭请求,由状态 8 进入此状态。具体动作时
发送 FIN
给对方,同时在接收到 ACK 时进入 CLOSED 状态。
10.CLOSING:两边同时发起关闭请求时,会由 FIN_WAIT_1 进入此状态。具体动作是,
接收
到 FIN 请求,同时响应一个 ACK。
11.TIME_WAIT:最纠结的状态来了。从状态图上可以看出,有 3 个状态可以转化成
它,我们
一一来分析:
a.由 FIN_WAIT_2 进入此状态:在双方不同时发起 FIN 的情况下,主动关闭的一方在
完成自身发
起的关闭请求后,接收到被动关闭一方的 FIN 后进入的状态。
b.由 CLOSING 状态进入:双方同时发起关闭,都做了发起 FIN 的请求,同时接收到了
FIN 并做了
ACK 的情况下,由 CLOSING 状态进入。
c.由 FIN_WAIT_1 状态进入:同时接受到 FIN(对方发起),ACK(本身发起的 FIN
回应),与
b 的区别在于本身发起的 FIN 回应的 ACK 先于对方的 FIN 请求到达,而 b 是 FIN 先到达。这种情
况概率最小。
9.2.9 TIME_WAIT 和 CLOSE_WAIT
关闭的 4 次连接最难理解的状态是 TIME_WAIT,存在 TIME_WAIT 的 2 个理由:
1.可靠地实现 TCP 全双工连接的终止。
2.允许老的重复分节在网络中消逝。
https://blog.csdn.net/wu936754331/article/details/49104497
https://blog.csdn.net/u013616945/article/details/77510925
1.为什么 time_wait 需要 2*MSL 等待时间?
MSL 就是 maximum segment lifetime(最大分节生命期),这是一个 IP 数据包能
在互联网上生存的最长时间,超过这个时间将在网络中消失。
现在我们考虑终止连接时的被动方发送了一个 FIN,然后主动方回复了一个
ACK,然而这个 ACK 可能会丢失,这会造成被动方重发 FIN,这个 FIN 可能会在
互联网上存活 MSL。
如果没有 TIME_WAIT 的话,假设连接 1 已经断开,然而其被动方最后重发的那个
FIN(或者 FIN 之前发送的任何 TCP 分段)还在网络上,然而连接 2 重用了连接 1
的所有的 5 元素(源 IP,目的 IP,TCP,源端口,目的端口),刚刚将建立好连接,
连接 1 迟到的 FIN 到达了,这个 FIN 将以比较低但是确实可能的概率终止掉连接
2. 大量的 time_wait 如何解决2. 当一个 tcp 监听了 80 端口后,Udp 还能否监听 80 端口
答:由于 TCP/IP 传输层的两个协议 TCP 和 UDP 是完全独立的两个软件模块,因此各
自的端口号也相互独立,如 TCP 有一个 255 号端口,UDP 也可以有一个 255 号端口,二
者并不冲突。
。
9.tcp 的滑动端口机制 如何保证不重复发送的
10.描述 TCP 滑动窗口机制,如何实现流控
11.防止 xxs 攻击和 sql 攻击等等
12.TCP/IP 有几层,每层有何含义
https://blog.csdn.net/xieyutian1990/article/details/23789871
3.为什么 TIME_WAIT 状态还需要等 2*MSL(Max SegmentLifetime,最大分段生存期)秒之后才
能返回到 CLOSED 状态呢?
因为虽然双方都同意关闭连接了,而且握手的 4 个报文也都发送完毕,按理可以直接回到 CLOSED
状态(就好比从 SYN_SENT 状态到 ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,
你无法保证你最后发送的 ACK 报文一定会被对方收到,就是说对方处于 LAST_ACK 状态下的 SOC
KET 可能会因为超时未收到 ACK 报文,而重发 FIN 报文,所以这个 TIME_WAIT 状态的作用就是用
来重发可能丢失的 ACK 报文。13. tcp 包可以被篡改吗?
9.7.计算机网络分层模型
9.7.1 osi 七层9.7.2 APR
(1)首先,每个主机都会在自己的 ARP 缓冲区中建立一个 ARP 列表,以表示 IP 地址和
MAC 地址之间的对应关系。
(
2)当源主机要发送数据时,首先检查 ARP 列表中是否有对应 IP 地址的目的主机的 MA
C 地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送 ARP 数据包,
该数据包包括的内容有:源主机 IP 地址,源主机 MAC 地址,目的主机的 IP 地址。
(
3)当本网络的所有主机收到该 ARP 数据包时,首先检查数据包中的 IP 地址是否是自己
的 IP 地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的 IP 和 M
AC 地址写入到 ARP 列表中,如果已经存在,则覆盖,然后将自己的 MAC 地址写入 ARP
响应包中,告诉源主机自己是它想要找的 MAC 地址。
(
4)源主机收到 ARP 响应包后。将目的主机的 IP 和 MAC 地址写入 ARP 列表,并利用此
信息发送数据。如果源主机一直没有收到 ARP 响应数据包,表示 ARP 查询失败。
广播发送 ARP 请求,单播发送 ARP 响应。
9.7.3 ICMP 协议ICMP 是 InternetControl Message Protocol,因特网控制报文协议。它是 TCP/IP 协议族的一
个子协议,用于在 IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、
路由器是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递
起着重要的作用。ICMP 报文有两种:差错报告报文和询问报文。
9.7.4 DNCP 协议
动态主机配置协议,是一种让系统得以连接到网络上,并获取所需要的配置参数手段。通常被应
用在大型的局域网络环境中,主要作用是集中的管理、分配 IP 地址,使网络环境中的主机动态的获
得 IP 地址、Gateway 地址、DNS 服务器地址等信息,并能够提升地址的使用率。
9.7.5 RARP 协议
逆地址解析协议,作用是完成硬件地址到 IP 地址的映射,主要用于无盘工作站,因为给无盘工
作站配置的 IP 地址不能保存。
9.7.6 路由选择协议 OSPF RIP
RIP:(距离向量路由)
https://blog.csdn.net/xuzhiwangray/article/details/5050
2233
RIP:1.A 收到附近节点 C 的路由表信息,然后将附近节点 C
作为下一跳,那么 A 的距离就得在 C 上加 12. 然后合并新的路由节点信息,合并过程:
(1 )无新信息,不改变
(
2 )新的项目,直接添加
(
3 )相同的下一跳,更新
(
4 )不同的下一跳,距离更短则更新距离和下一跳地址,否则不变
OSPF:(链路状态路由)
9.7.7 SNMP
简单网络管理协议(SNMP),由一组网络管理的标准组成,包含一个应用层协议(
application layer protoco
l)、数据库模型(database schema)和一组资源对象。该协议能够支持网络管理系统,用以监测连接到网络上的设
备是否有任何引起管理上关注的情况。
9.7.8 SMTP
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规
则,由它来控制信件的中转方式。9.9 IP
1.IP 报文
2. IP 地址类别
3. 特殊的地址4. 私有地址
9.10 网络攻击
1.SYN Flood 攻击
关于 SYN Flood 攻击。一些恶意的人就为此制造了 SYN Flood 攻击——给服务器发了
一个 SYN 后,就下线了,于是服务器需要默认等 63s 才会断开连接,这样,攻击者就可以
把服务器的 syn 连接的队列耗尽,让正常的连接请求不能处理。于是,Linux 下给了一个叫
tcp_syncookies 的参数来应对这个事——当 SYN 队列满了后,TCP 会通过源地址端口、目
标地址端口和时间戳打造出一个特别的 Sequence Number 发回去(又叫 cookie),如果是
攻击者则不会有响应,如果是正常连接,则会把这个 SYN Cookie 发回来,然后服务端可
以通过 cookie 建连接(即使你不在 SYN 队列中)。请注意,请先千万别用 tcp_syncookies
来处理正常的大负载的连接的情况。因为,synccookies 是妥协版的 TCP 协议,并不严谨。
对于正常的请求,你应该调整三个 TCP 参数可供你选择,第一个是:
tcp_synack_retries 可
以用他来减少重试次数;第二个是:
tcp_max_syn_backlog,可以增大 SYN 连接数;第三
个是:
tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了。
2. DDOS 攻击
DDoS 攻击是 Distributed Denial of Service 的缩写,即不法黑客组织通过控制服务器等
资源,发动对包括国家骨干网络、重要网络设施、政企或个人网站在内的互联网上任一目标
的攻击,致使目标服务器断网,最终停止提供服务。
预防:1.高防服务器 主要是指能独立硬防御 50Gbps 以上的服务器,能够帮助网站拒
绝服务攻击,定期扫描网络主节点等 2.DDoS 清洗会对用户请求数据进行实时监控,及时
发现 DOS 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。3.CDN
加速 在现实中,CDN 服务将网站访问流量分配到了各个节点中,这样一方面隐藏网站的
真实 IP,另一方面即使遭遇 DDoS 攻击,也可以将流量分散到各个节点中,防止源站崩
溃。
3.DNS 欺骗
DNS 欺骗就是攻击者冒充 域名服务器 的一种欺骗行为
预防:1.使用入侵检测系统 2.使用 DNSSEC
4. 重放攻击
重放攻击又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达
到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。
预防:1.加随机数 2.加时间戳
5.SQL 注入
所谓 SQL 注入,就是通过把 SQL 命令插入到 Web 表单提交或输入域名或页面请求的
查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。预防:1.加密处理 将用户登录名称、密码等数据加密保存。加密用户输入的数据,然
后再将它与数据库中保存的数据比较,这相当于对用户输入的数据进行了“消毒”处理,用户
输入的数据不再对数据库有任何特殊的意义,从而也就防止了攻击者注入 SQL 命令。
2. 确保数据库安全 只给访问数据库的 web 应用功能所需的最低的权限,撤销不必要
的公共许可 3.输入验证 检查用户输入的合法性,确信输入的内容只包含合法的数据。数据
检查应当在客户端和服务器端都执行之所以要执行服务器端验证,是为了弥补客户端验证机
制脆弱的安全性。
9.11 DNS 浏览器中输入 URL 到页面加载的发生了什么
https://blog.csdn.net/dojiangv/article/details/51794535
1. DNS 的解析流程CDN
其中在 DNS 通过域名解析成 IP 地址的过程中,会涉及到 DNS 重定向的问题,
定义:CDN,英文 Content Delivery Network,中文翻译是内容分发网络,
目的就是通过现有的 Internet 中增加一新的网络架构,将网站内容发布到离用户
最近的网络“边缘”,提高用户访问网站的速度,所以更像是增加了一层 CACHE
(缓存)层
功能:当用户访问加入 CDN 服务的网站时,域名解析请求将最终交给全局
负载均衡 DNS 进行处理。全局负载均衡 DNS 通过一组预先定义好的策略,将
当时最接近用户的节点地址提供给用户,使用户能够得到快速的服务。
组成:每个 CDN 节点由两部分组成:负载均衡设备和高速缓存服务器
负载均衡设备负责每个节点中各个 Cache 的负载均衡,保证节点的工作效
率;同时,负载均衡设备还负责收集节点与周围环境的信息,保持与全局负载
DNS 的通信,实现整个系统的负载均衡。
高速缓存服务器(Cache)负责存储客户网站的大量信息,就像一个靠近用
户的网站服务器一样响应本地用户的访问请求。
2. 在浏览器中输入 www.baidu.com 后执行的全部过程
1.客户端浏览器通过 DNS 解析到 www.baidu.com 的 IP 地址为 220.181.0.1,
通过这个 ip 地址找到客户端到服务器的路径,客户端浏览器发起一个 http 会话
到 220.181.0.1,然后通过 TCP 进行封装数据包,输入到网络层。
2.在客户端的传输层,把 HTTP 会话请求分成报文段,添加源和目的端口,
如服务器端用 80 端口监听客户端的请求,客户端由系统随机选择一个端口,如
5000,与客户端进行交换,服务器把相应的请求返回给客户端的 5000 端口。然
后使用 ip 层的 ip 地址查找目的端。
3.客户端的网络层不用关心应用层和传输层的东西,主要做的是通过查找路
由表确定如何到达服务器,期间可能经过多个路由器。
4,。客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定
的 ip 地址和 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的 ip 数据包现在就可以传输了,然后发送 Ip 数据包
到达服务器的地址。
DNS 均衡
9.12 https ssl
9.12.1 什么是 https
HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的
网络协议,要比 http 协议安全。
9.12.2 https 与 http 区别
HTTPS 和 HTTP 的区别主要如下:
1)https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
2)http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl
加密传输协议。
3)http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者
是 80,后者是 443。
4)http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构
建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
1. HTTP 的 URL 以 http:// 开头 ,而 HTTPS 的 URL 以 https://
开头
2. HTTP 是不安全的,而 HTTPS 是安全的
4. 在 OSI 网络模型中,HTTP 工作于应用层,而 HTTPS 工作在传输层
5. HTTP 无需加密,而 HTTPS 对传输的数据进行加密
6. HTTP 无需证书,而 HTTPS 需要认证证书9.12.3 https 的通信过程
文字简述:客户端 A 和服务器 B 之间的交互
1. A 与 B 通过 TCP 建立链接,初始化 SSL 层。
2. 进行 SSL 握手,A 发送 https 请求,传送客户端 SSL 协议版本号、支持的加密算法、
随机数等。
3.
服务器 B 把 CA 证书(包含 B 的公钥),把自己支持的加密算法、随机数等回传给
A。
4. A 接收到 CA 证书,验证证书有效性。5.
校验通过,客户端随机产生一个字符串作为与 B 通信的对称密钥,通过 CA 证书解
出服务器 B 的公钥,对其加密,发送给服务器。
6.
B 用私钥解开信息,得到随机的字符串(对称密钥),利用这个密钥作为之后的通
信密钥。
7.
客户端向服务器发出信息,指明后面的数据使用该对称密钥进行加密,同时通知服
务器 SSL 握手结束。
8.
服务器接收到信息,使用对称密钥通信,通知握手接收。
9.
SSL 握手结束,使用对称密钥加密数据。
9.12.4 SSL 工作原理
https://blog.csdn.net/ENERGIE1314/article/details/54581411/
三种协议:1.握手协议 2.记录协议 3,警报协议
1.RSA 握手协议
第一步,Client 给出协议版本号、一个客户端生成的随机数(Client random),
以及客户端支持的加密方法。
第二步,Server 确认双方使用的加密方法,并给出数字证书、以及一个服务器
生成的随机数(Server random)。
第三步,Client 确认数字证书有效,然后生成一个新的随机数(Premaster secr
et),并使用数字证书中的公钥,加密这个随机数,发给 Server。
第四步,Server 使用自己的私钥,获取 Client 发来的随机数(即 Premaster se
cret)。
第五步,Client 和 Server 根据约定的加密方法,使用前面的三个随机数,生成”
对话密钥”(
session key),用来加密接下来的整个对话过程。
2.记录协议
记录协议 对数据传输提供保密性和完整性3.警报协议
建立连接的过程客户端跟服务端会交换什么信息(参考 TCP 报文结构)
丢包如何解决重传的消耗
traceroute 实现原理
select 和 poll 区别?
在不使用 WebSocket 情况下怎么实现服务器推送的一种方法
可以使用客户端定时刷新请求或者和 TCP 保持心跳连接实现。
查看磁盘读写吞吐量?
PING 位于哪一层
网络重定向,说下流程
controller 怎么处理的请求:路由
.IP 地址分为几类,每类都代表什么,私网是哪些十 操作系统
http://c.biancheng.net/cpp/html/2611.html
操作系统概述:
操作系统用来协调软件与底层硬件,相当于彼此的接口
操作系统有进程管理,内存管理,文件管理,输入输出管理。
内存管理的功能有:
内存空间的分配与回收:由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高
编程效率。
地址转换:在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供
地址变换功能,把逻辑地址转换成相应的物理地址。
内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
存储保护:保证各道作业在各自的存储空间内运行,
.互不干扰。
10.1 进程线程
10.1.1.进程线程区别
进程是系统进行资源分配和调度的一个独立单位,最小的资源管理单位。线程是进程的一个实体,
是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,最小的 CPU 执行单元。
线程拥有的资源:程序计数器 寄存器 栈 状态字10.1.2 进程通信方式
共享内存:共享内存可以说是最有用的进程间通信方式,也是最快的 IPC 形式。两个不同进程 A、B 共享内存
的意思是,同一块物理内存被映射到进程 A、B 各自的进程地址空间。进程 A 可以即时看到进程 B 对共享内存中数据的
更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
10.1.3 僵尸进程
1 什么是僵尸进程:
当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为
一个僵尸进程。
2 怎样来清除僵尸进程:
1.改写父进程,在子进程死后要为它收尸。具体做法是接管 SIGCHLD 信号。子进程死后,会发送 SI
GCHLD 信号给父进程,父进程收到此信号后,执行 waitpid()函数为子进程收尸。这是基于这样的原
理:就算父进程没有调用 wait,内核也会向它发送 SIGCHLD 消息,尽管对的默认处理是忽略,如果
想响应这个消息,可以设置一个处理函数。
2.把父进程杀掉。父进程死后,僵尸进程成为”孤儿进程”,过继给 1 号进程 init,init 始终会负责清理
僵尸进程.它产生的所有僵尸进程也跟着消失。
10.1.4 进程同步 PV 信号量
https://blog.csdn.net/leves1989/article/details/3305609
首先应弄清 PV 操作的含义:PV 操作由 P 操作原语和 V 操作
原语组成(原语是不可中断的过程),对信号量进行操作,具体定义如下:
P(S):①将信号量 S 的值减 1,即 S=S1;
②如果 S0,则该进程继续执行;否则该进程置为等待状
态,排入等待队列。
V(S):①将信号量 S 的值加 1,即 S=S+1;
②如果 S>0,则该进程继续执行;否则释放队列中第一个
等待信号量的进程。
PV 操作的意义:我们用信号量及 PV 操作来实现进程的同步和
互斥。PV 操作属于进程的低级通信。
什么是信号量?信号量(
semaphore)的数据结构为一个值
和一个指针,指针指向等待该信号量的下一个进程。信号量的值
与相应资源的使用情况有关。当它的值大于 0 时,表示当前可用
资源的数量;当它的值小于 0 时,其绝对值表示等待使用该资源
的进程个数。注意,信号量的值仅能由 PV 操作来改变。
一般来说,信号量 S0 时,S 表示可用资源的数量。执行一
次 P 操作意味着请求分配一个单位资源,因此 S 的值减 1;当 S
<0 时,表示已经没有可用资源,请求者必须等待别的进程释放
该类资源,它才能运行下去。而执行一个 V 操作意味着释放一个
单位资源,因此 S 的值加 1;若 S0,表示有某些进程正在等待
该资源,因此要唤醒一个等待状态的进程,使之运行下去。10.2 死锁
10.2.1 死锁避免-银行家算法
我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资
源相当于用户向银行家贷款。为保证资金的安全,银行家规定:
(1) 当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客;
(2) 顾客可以分期贷款,但贷款的总数不能超过最大需求量;
(3) 当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间
里得到贷款;
(4) 当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.
操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,
如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请
资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前
的申请量分配资源,否则也要推迟分配。
10.2.2 死锁避免-安全序列
安全序列
安全序列是指对当前申请资源的进程排出一个序列,保证按照这个序列分配资源完成进程,不会发生“酱油和醋”的尴尬
问题。
我们假设有进程 P1,P2,.....Pn
则安全序列要求满足:Pi(1<=i<=n)需要资源<=剩余资源 + 分配给 Pj(1 <= j < i)资源
为什么等号右边还有已经被分配出去的资源?想想银行家那个问题,分配出去的资源就好比第二个开发商,人家能还回
来钱,咱得把这个考虑在内。
10.3 同步 异步 阻塞 非阻塞
https://www.cnblogs.com/George1994/p/6702084.html
1 例子
故事:老王烧开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
老王想了想,有好几种等待方式
1.老王用水壶煮水,并且站在那里,不管水开没开,每隔一定时间看看水开了没。-同步阻
塞
老王想了想,这种方法不够聪明。
2.老王还是用水壶煮水,不再傻傻的站在那里看水开,跑去寝室上网,但是还是会每隔一段
时间过来看看水开了没有,水没有开就走人。-同步非阻塞
老王想了想,现在的方法聪明了些,但是还是不够好。
3.老王这次使用高大上的响水壶来煮水,站在那里,但是不会再每隔一段时间去看水开,而
是等水开了,水壶会自动的通知他。-异步阻塞
老王想了想,不会呀,既然水壶可以通知我,那我为什么还要傻傻的站在那里等呢,嗯,得
换个方法。
4.老王还是使用响水壶煮水,跑到客厅上网去,等着响水壶自己把水煮熟了以后通知他。-
异步非阻塞
老王豁然,这下感觉轻松了很多。
同步和异步
同步就是烧开水,需要自己去轮询(每隔一段时间去看看水开了没),异步就是水开
了,然后水壶会通知你水已经开了,你可以回来处理这些开水了。
同步和异步是相对于操作结果来说,会不会等待结果返回。
阻塞和非阻塞
阻塞就是说在煮水的过程中,你不可以去干其他的事情,非阻塞就是在同样的情况下,
可以同时去干其他的事情。阻塞和非阻塞是相对于线程是否被阻塞。
其实,这两者存在本质的区别,它们的修饰对象是不同的。阻塞和非阻塞是指进程访问的数
据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就
绪时是直接返回还是等待就绪。
而同步和异步是指消息通信机制,同步一般指主动请求并等待 I/O 操作完毕的方式,当数据就
绪后在读写的时候必须阻塞,异步则指主动请求数据后便可以继续处理其它任务,随后等待
I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。
10.4 操作系统 CPU 调度算法
由于要执行的进程的数目是多于处理器的数目,所以需要处
理器去决定下一次运行哪个进程
进程就是作业1.先来先服务调度算法(FCFS)
:就是按照各个作业进入系统的自然次序来调度作业。这种调度算法
的优点是实现简单,公平。其缺点是没有考虑到系统中各种资源的综合使用情况,往往使短作业的用户不
满意,因为短作业等待处理的时间可能比实际运行时间长得多。
2.短作业优先调度算法 (SPF): 就是优先调度并处理短作业,所谓短是指作业的运行时间短。而
在作业未投入运行时,并不能知道它实际的运行时间的长短,因此需要用户在提交作业时同时提交作
业运行时间的估计值。
3.最高响应比优先算法(HRN):
FCFS 可能造成短作业用户不满,
SPF 可能使得长作业用户不满,
于是提出 HRN,选择响应比最高的作业运行。响应比=1+作业等待时间/作业处理时间。
4. 基于优先数调度算法(HPF):每一个作业规定一个表示该作业优先级别的整数,当需要将新的
作业由输入井调入内存处理时,优先选择优先数最高的作业。
5.时间片轮转调度算法
时间片轮转调度算法主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个
队列,进程调度程序总是选择就绪队列中第一个进程执行,即先来先服务的原则,但仅能运行一个时间片,如 100ms。
在使用完一个时间片后,即使进程并未完成其运行,它也必须释放出(被剥夺)处理机给下一个就绪的进程,而被剥夺
的进程返回到就绪队列的末尾重新排队,等候再次运行。
10.5 内存管理方式(页存储 段存储 段页存储)
页存储
为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立一张页表,记录页
面在内存中对应的物理块号,页表一般存放在内存中。在配置了页表后,进程执行时,通过查找该表,
即可找到每页在内存中的物理块号。可见页表作用是实现从页号到物理块号的地址映射,这种是页存储管理方式。如下图所示:
段存储
将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分
配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配,这种是段存储管理方式。如
下图所示:
段页存储
作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大
小相等的页。对于主存空间也分成大小相等的页,主存的分配以页为单位,这种是段页存储管理方式。如下图所示:
10.6 页面置换算法
10.6.1 概念
缺页中断:缺页中断就是要访问的页不在主存,需要操作系统将其调入主存
后再进行访问。
页面置换算法:在地址映射过程中,若在页面中发现所要访问的页面不在内
存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,
则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出
空间。而用来选择淘汰哪一页的规则叫做页面置换算法。
10.6.2 OPT 最优页面置换算法
寻找在将来的时间段内,最晚被访问到的页面,然后将其置换到。(无法预
知未来,不现实)
10.6.3 先进先出置换算法(FIFO)
最简单的页面置换算法是先入先出(FIFO)法。这种算法的实质是,总是选择在主存
中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存。
10.6.4 最近最久未使用(LRU)算法
它的实质是,当需要置换一页时,选择在最近一段时间里最久没有使用过
的页面予以置换。这种算法就称为最久未使用算法(Least Recently Used,LRU)
LRU 算法是经常采用的页面置换算法,并被认为是相当好的,但是存在如
何实现它的问题。LRU 算法需要实际硬件的支持。其问题是怎么确定最后使用
时间的顺序,对此有两种可行的办法:
1.计数器。最简单的情况是使每个页表项对应一个使用时间字段,并给 CPU
增加一个逻辑时钟或计数器。每次存储访问,该时钟都加 1。每当访问一个页面
时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可
以始终保留着每个页面最后访问的“时间”。在置换页面时,选择该时间值最小的
页面。这样做,不仅要查页表,而且当页表改变时(因 CPU 调度)要维护这个页表中的时间,还要考虑到时钟值溢出的问题。
2.栈。用一个栈保留页号。每当访问一个页面时,就把它从栈中取出放在栈
顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的
页。由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。在最
坏的情况下,移走一页并把它放在栈顶上需要改动 6 个指针。每次修改都要有开
销,但需要置换哪个页面却可直接得到,用不着查找,因为尾指针指向栈底,其
中有被置换页。
10.6.5 时钟(CLOCK)置换算法
简单的 CLOCK 算法是给每一帧关联一个附加位,称为使用位。当某一页首次装
入主存时,该帧的使用位设置为 1;当该页随后再被访问到时,它的使用位也被置
为 1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一
个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。
当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为 0 的一帧。每当
遇到一个使用位为 1 的帧时,操作系统就将该位重新置为 0;如果在这个过程开
始时,缓冲区中所有帧的使用位均为 0,则选择遇到的第一个帧替换;如果所有
帧的使用位均为 1,则指针在缓冲区中完整地循环一周,把所有使用位都置为 0,
并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情
况,故称为 CLOCK 算法,又称为最近未用(Not Recently Used, NRU)算法。
CLOCK 算法的性能比较接近 LRU,而通过增加使用的位数目,可以使得 CLOCK
算法更加高效。在使用位的基础上再增加一个修改位,则得到改进型的 CLOCK
置换算法。这样,每一帧都处于以下四种情况之一:
1.
最近未被访问,也未被修改(u=0, m=0)。
2.
最近被访问,但未被修改(u=1, m=0)。
3.
最近未被访问,但被修改(u=0, m=1)。
4.
最近被访问,被修改(u=1, m=1)。
算法执行如下操作步骤:
1.
从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不
做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。
2.
如果第 1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个
这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成
0。
3.
如果第 2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用
位均为 0。重复第 1 步,并且如果有必要,重复第 2 步。这样将可以找到供替换
的帧。
10.7 IO 种类 IO 的原理1. IO 种类
计算机系统中的 I/O 设备按使用特性可分为以下类型:
1) 人机交互类外部设备:用于同计算机用户之间交互的设备,如打印机、显示器、鼠标、键盘等。这类设备数据交换
速度相对较慢,通常是以字节为单位进行数据交换。
2) 存储设备:用于存储程序和数据的设备,如磁盘、磁带、光盘等。这类设备用于数据交换,速度较快,通常以多字
节组成的块为单位进行数据交换。
3) 网络通信设备:用于与远程设备通信的设备,如各种网络接口、调制解调器等。其速度介于前两类设备之间。网络
通信设备在使用和管理上与前两类设备也有很大不同。
除了上面最常见的分类方法,
I/O 设备还可以按以下方法分类:
1) 按传输速率分类:
低速设备:传输速率仅为每秒几个到数百个字节的一类设备,如键盘、鼠标等。
中速设备:传输速率在每秒数千个字节至数万个字节的一类设备,如行式打印机、 激光打印机等。
高速设备:传输速率在数百个千字节至千兆字节的一类设备,如磁带机、磁盘机、 光盘机等。
2) 按信息交换的单位分类:
块设备:由于信息的存取总是以数据块为单位,所以存储信息的设备称为块设备。它属于有结构设备,如磁
盘等。磁盘设备的基本特征是传输速率较高,以及可寻址,即对它可随机地读/写任一块。
字符设备:用于数据输入/输出的设备为字符设备,因为其传输的基本单位是字符。它属于无结构类型,如交
互式终端机、打印机等。它们的基本特征是传输速率低、不可寻址,并且在输入/输出时常釆用中断驱动方式。
2.设备 I/0 输入输出控制方式
程序直接控制方式
计算机从外部设备读取数据到存储器,每次读一个字的数据。对读入的每个字,CPU 需要对外设状态进行循环检
查,直到确定该字已经在 I/O 控制器的数据寄存器中。
中断驱动方式、
中断驱动方式的思想是,允许 I/O 设备主动打断 CPU 的运行并请求服务,从而“解放”CPU,使得其向 I/O 控制
器发送读命令后可以继续做其他有用的工作。
DMA 方式在中断驱动方式中,
I/O 设备与内存之间的数据交换必须要经过 CPU 中的寄存器,所以速度还是受限,而 DMA
(直接存储器存取)方式的基本思想是在 I/O 设备和内存之间开辟直接的数据交换通路,彻底“解放” CPU。
通道控制方式
I/O 通道是指专门负责输入/输出的处理机。I/O 通道方式是 DMA 方式的发展,它可以进一步减少 CPU 的干预,
即把对一个数据块的读(或写)为单位的干预,减少为对一组数据块的读(或写)及有关的控制和管理为单位的干预。
I/O 通道与 DMA 方式的区别是:DMA 方式需要 CPU 来控制传输的数据块大小、传输的内存位置,而通道方式
中这些信息是由通道控制的。另外,每个 DMA 控制器对应一台设备与内存传递数据,而一个通道可以控制多台设备与
内存的数据交换。
10.8 进程打开同一个文件 那么这两个进程得到的文件描述符(
fd)相同
整个系统表包含进程相关信息,如文件在磁盘的位置、访问日期和大小。一个进程打开一个文件,系统打开文件
表就会为打开的文件增加相应的条目。当另一个进程执行 open 时,只不过是在其进程打开表中增加一个条目,并指向
整个系统表的相应条目。通常,系统打开文件表的每个文件时,还用一个文件打开计数器(Open Count),以记录多少
进程打开了该文件。每个关闭操作 close 则使 count 递减,当打开计数器为 0 时,表示该文件不再被使用。系统将回收
分配给该文件的内存空间等资源,若文件被修改过,则将文件写回外存,并将系统打开文件表中相应条目删除,最后释
放文件的文件控制块(File Control Block, FCB)。
所以文件描述符不同,count++,不同的 count 不同
10.9 select epoll
select 原理概述
调用 select 时,会发生以下事情:
1.
从用户空间拷贝 fd_set 到内核空间;
2.
注册回调函数__pollwait;
3.
遍历所有 fd,对全部指定设备做一次 poll(这里的 poll 是一个文件操作,它有两个参数,一个是文件 fd 本身,
一个是当设备尚未就绪时调用的回调函数__pollwait,这个函数把设备自己特有的等待队列传给内核,让内核把
当前的进程挂载到其中);
4.
当设备就绪时,设备就会唤醒在自己特有等待队列中的【所有】节点,于是当前进程就获取到了完成的信号。
poll 文件操作返回的是一组标准的掩码,其中的各个位指示当前的不同的就绪状态(全 0 为没有任何事件触发),
根据 mask 可对 fd_set 赋值;
5.
如果所有设备返回的掩码都没有显示任何的事件触发,就去掉回调函数的函数指针,进入有限时的睡眠状态,
再恢复和不断做 poll,再作有限时的睡眠,直到其中一个设备有事件触发为止。
6.
只要有事件触发,系统调用返回,将 fd_set 从内核空间拷贝到用户空间,回到用户态,用户就可以对相关的
fd 作进一步的读或者写操作了。
epoll 原理概述
调用 epoll_create 时,做了以下事情:1.
内核帮我们在 epoll 文件系统里建了个 file 结点;
2.
在内核 cache 里建了个红黑树用于存储以后 epoll_ctl 传来的 socket;
3.
建立一个 list 链表,用于存储准备就绪的事件。
调用 epoll_ctl 时,做了以下事情:
1.
把 socket 放到 epoll 文件系统里 file 对象对应的红黑树上;
2.
给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪 list 链表
里。
调用 epoll_wait 时,做了以下事情:
观察 list 链表里有没有数据。有数据就返回,没有数据就 sleep,等到 timeout 时间到后即使链表没数
据也返回。而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句
柄而已,所以,epoll_wait 仅需要从内核态 copy 少量的句柄到用户态而已。
对比
select 缺点:
1.
最大并发数限制:使用 32 个整数的 32 位,即 32*32=1024 来标识 fd,虽然可修改,
但是有以下第二点的瓶颈;
2.
效率低:每次都会线性扫描整个 fd_set,集合越大速度越慢;
3.
内核/用户空间内存拷贝问题。
epoll 的提升:
1.
本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制;
2.
效率提升:只有活跃的 socket 才会主动的去调用 callback 函数;
3.
省去不必要的内存拷贝:epoll 通过内核与用户空间 mmap 同一块内存实现。
①了解内存管理页面置换算法(
LRU,Java 中如何实现(
LinkedHashMap))
④了解死锁与饥饿区别
⑥了解如何预防死锁(银行家算法、破坏条件等等)
⑦实现阻塞队列
⑧生产者消费者模型实现
32 位系统的最大寻址空间? 2 的 32 次方 4GB
不同进程打开了同一个文件,那么这两个进程得到的文件描述符(
fd)相同吗?
操作系统如何实现输出
三级缓存原理
内存管理: 固定分区 动态分区 段 页 都讲讲32 位系统的内存寻址空间多大, 具体分为哪几种形态?库函数和系统调用有什么区别?
操作系统内一个进程的内存分段以及对应的作用
10.10 物理地址 虚拟地址 逻辑地址
物理地址(空间)
用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚按发送到内存总线上的电信号相对应。物理地址由
32 位或 64 位无符号整数表示
虚拟地址:就是在分段 分页的 基础上,比如说将地址分为 32 位,如图 虚拟地址就是页号加上偏移量这种的表示方法,页号与偏移量结合去找到页表中在物
理内存中对应的页。
逻辑地址(Logical Address):
包含在机器语言指令中用来指定一个操作数或一条指令的地址,
每个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始
的地方到实际地址之间的距离。十一 Linux 命令
10.1 Vim
(1) 打开与退出vi file:打开文件 file
:q :退出 vi 编辑器
:wq:保存缓冲区的修改并退出编辑器
:q!:不保存直接退出
:w 保存缓冲区内容至默认的文件
:w file 保存缓冲区内容至 file 文件
(2) 插入文本
a : 在当前光标的右边插入文本
A : 在当前光标行的末尾插入文本
i : 在当前光标的左边插入文本
I : 在当前光标所在行的开始处插入文本
o: 在当前行在下面新建一行
O:在当前行的上面新建一行
R:替换当前光标位置以及以后的若干文本
J:连接光标所在行和下一行
(3) 删除文本
x: 删除一个字符
dd: 删除一行
ndd: 删除 n 行
u: 撤销上一次操作
U: 撤销对当前行的所有操作
(4) 搜索
/word 从前向后搜索第一个出现的 word
?word 从后向前搜索第一个出现的 word
(5) 设置行号
:set nu 在屏幕上显示行号
:set nonu 取消行号
10.2 linux 如何查看端口被哪个进程占用?
lsof -i:端口号https://www.cnblogs.com/bonelee/p/7735479.html
10.3 查看进程打开了哪些文件
lsof -p pid
10.4 top
PID:进程的 ID
USER:进程所有者
PR:进程的优先级别,越小越优先被执行
NInice:值
VIRT:进程占用的虚拟内存
RES:进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S 表示休眠,R 表示正在运行,Z 表示僵死状态,N 表示该进程优先值为负数
%CPU:进程占用 CPU 的使用率
%MEM:进程使用的物理内存和总内存的百分比
TIME+:该进程启动后占用的总的 CPU 时间,即占用 CPU 使用时间的累加值。
COMMAND:进程启动命令名称
top -p 进程 id 查看具体的进程的内存占用
Top -u 用户名 查看用户的进程内存
输入 top 后出来数据了
然后输入 M 按照内存排序,输入 P 按照 CPU 排序 输入
T 按照占用 CPU 的时间排序
10.5 查看 cpu 核的个数主频
cat /proc/cpuinfo
10.6 Linux 如何创建守护进程
(1)创建子进程,父进程退出。
经过这步以后,子进程就会成为孤儿进程(父进程先于子进程退出, 此时的子进程,成为孤儿进程,会被 init 进程收养)。
使用 fork()函数,如果返回值大于 0,表示为父进程,exit(
0),父进程退出,子进程继续。
(
2)在子进程中创建新会话,使当前进程成为新会话组的组长。使用 setsid()函数,如果当前进程不是进程组的组长,则为当前进程创建一个新的会话期,使当前进程成为这个会话组
的首进程,成为这个进程组的组长。
(
3)改变当前目录为根目录。
由于守护进程在后台运行,开始于系统开启,终止于系统关闭,所以要将其目录改为系统的根目录下。进程在执行时,
其文件系统不能被卸下。
(
4)重新设置文件权限掩码。
进程从父进程那里继承了文件创建掩码,所以可能会修改守护进程存取权限位,所以要将文件创建掩码清除,
umask(
0);
(
5)关闭文件描述符。
子进程从父进程那里继承了打开文件描述符。所以使用 close 即可关闭。
10.7 Linux 管道机制原理
实际上,管道是一个固定大小的缓冲区。在 Linux 中,该缓冲区的大小为 1 页,即 4K 字节,使
得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能
变满,当这种情况发生时,随后对管道的 write()调用将默认地被阻塞,等待某些数据被读取,以便腾
出足够的空间供 write()调用写。
· 读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发
生时,一个随后的 read()调用将默认地被阻塞,等待某些数据被写入,这解决了 read()调用返回文件
结束的问题。
10.8 查看进程下的线程
https://www.cnblogs.com/EasonJim/p/8098217.html
top -H -p 进程 ID
10.9 linux 锁
1.互斥锁
互斥锁只能有对一个线程使用,就是用来互斥的。
以下是互斥锁的基本操作3. 自旋锁
自旋锁上锁后让等待线程进行忙等待而不是睡眠阻塞,而信号量是让等待线程睡眠阻塞。
自旋锁的忙等待浪费了处理器的时间,但时间通常很短,在 1 毫秒以下。
10.10 查看行数指令(比如第 100 行到第 150 行 top IP)
1.要显示一百行到一百五十行的内容怎么输入命令
sed -n "100,150p" +文件名
sed 命令是一个选取命令,它后面单引号括起来的 100,150
就表示 100 行到 150 行的意思,100,150 后面的英文字母 p 是 print 显示的意思。
3.
有一个文件 ip.txt,每行一条 ip 记录,共若干行,下面哪个命令可以实现“统计出现次数最多的前 3 个 ip 及
其次数”?
sort ip.txt | uniq -c | sort -rn | head -n 3
首先 sort 进行排序,将重复的行都排在了一起,然后使用 uniq -c 将重复的行的次数放在了行首,在用 sort -rn 进
行反向和纯文本排序,这样就按照重复次数从高到低进行了排列,最后利用 head -n 3 输出行首的三行。
4. Linux 下 给定一个文件,里面存放的是 IP 地址,统计各个 IP 地址出现的次数
sort ip.txt | uniq -c | sort -rn
5.统计文件中出现最多的前 10 个单词10.11 linux 进程调度
1、 进程调度的作用
,进程调度就是对进程进行调度,即负责选择下一个要运行的进程.通过合理的调度,系统资源才能最大
限度地发挥作用,多进程才会有并发执行的效果.最终要完成的目标就是为了最大限度的利用处理器时
间.即,只要有可以执行的进程,那么就总会有进程正在执行.当进程数大于处理器个数时,某一时刻总会
有一些进程进程不能执行.这些进程等待运行.在这些等待运行的进程中选择一个合适的来执行,是调
度程序所需完成的基本工作.
2、调度策略
先给一张直观图
【1】考虑到进程类型时:I/O 消耗型进程 pk 处理器消耗型进程.
I/O 消耗型进程:指进程大部分时间用来提交 I/O 请求或者是等待 I/O 请求.处理器消耗型进程:与 I/O 消
耗型相反,此类进程把时间大多用在执行代码上.此时调度策略通常要在两个矛盾的目标中寻找平衡:
进程响应时间短(优先 I/O 消耗型进程)和最大系统利用率(优先处理器消耗型进程).linux 为了保证交互
式应用,所以对进程的响应做了优化,即更倾向于优先调度 I/O 消耗型进程.
【2】考虑到进程优先级时
调度算法中最基本的一类就是基于优先级的调度.调度程序总是选择时间片未用尽而且优先级最高的
进程运行.
linux 实现了一种基于动态优先级的调度方法.即:一开始,先设置基本的优先级,然后它允许调度程序根
据需要加,减优先级.
eg:如果一个进程在 I/O 等待上消耗的时间多于运行时间,则明显属于 I/O 消耗型进程,那么根据 1 中的
考虑,应该动态提高其优先级.
linux 提供了两组独立的优先级范围:
1)nice 值:范围从-20 到+19.默认值是 0,值越小,优先级越高.nice 值也用来决定分配给进程的时间片的
长短.
2)实时优先级:范围为 0 到 99.注意,任何实时进程的优先级都高于普通的进程.【3】考虑到进程时间片时
时间片是一个数值,它表明进程在被抢占前所能持续运行的时间.调度策略必须规定一个默认的时间片.
时间片过长,则会影响系统的交互性.时间片过短,则会明显增大因进程频繁切换所耗费的时间.调度程
度提供较长的默认时间片给交互式程序.此外,linux 调度程序还能根据进程的优先级动态调整分配给
它的时间片,从而保证了优先级高的进程,执行的频率高,执行时间长.当一个进程的时间片耗尽时,则认
为进程到期了,此时不能在运行.除非所有进程都耗尽了他们的时间片,此时系统会给所有进程重新
10.12 零拷贝技术
10.14 系统调用与库函数的区别10.15 free
我们按照图中来一细细研读(数字编号和图对应)
1,total:物理内存实际总量
2,used:这块千万注意,这里可不是实际已经使用了的内存哦,这里是总计分配给缓存(包含 buff
ers 与 cache )使用的数量,但其中可能部分缓存并未实际使用。
3,free:未被分配的内存
4,shared:共享内存
5,buffers:系统分配的,但未被使用的 buffer 剩余量。注意这不是总量,而是未分配的量
6,cached:系统分配的,但未被使用的 cache 剩余量。buffer 与 cache 的区别见后面。
7,buffers/cache used:这个是 buffers 和 cache 的使用量,也就是实际内存的使用量,这个非常重
要了,这里才是内存的实际使用量哦
8, buffers/cache free:未被使用的 buffers 与 cache 和未被分配的内存之和,这就是系统当前实
际可用内存。千万注意,这里是 三者之和,也就是第一排的 free+buffers+cached,可不仅仅是未被
使用的 buffers 与 cache 的和哦,还要加上 free(未分配的和)
9,swap,这个我想大家都理解,交换分区总量,使用量,剩余量
我想我说得很清晰了
10.16 cache 和 buffer 的区别:
cache 在 cpu 和内存之间,它的速度比内存快,但是造价高缓冲区 buffer 主要存在于 RAM 中,作为 CPU 暂时存储数据的区域,例如,当计算机和其他设
备具有不同的速度时, buffer 存储着缓冲的数据, 这样计算机就可以完成其他任务了
Cache:高速缓存,是位于 CPU 与主内存间的一种容量较小但速度很高的存储器。由于 CPU
的速度远高于主内存,CPU 直接从内存中存取数据要等待一定时间周期,Cache 中保存着 CPU 刚用
过或循环使用的一部分数据,当 CPU 再次使用该部分数据时可从 Cache 中直接调用,这样就减少了
CPU 的等待时间,提高了系统的效率。Cache 又分为一级 Cache(L1 Cache)和二级 Cache(L2
Cache),L1 Cache 集成在 CPU 内部,L2 Cache 早期一般是焊在主板上,现在也都集成在 CPU
内部,常见的容量有 256KB 或 512KB L2 Cache。
Buffer:缓冲区,一个用于存储速度不同步的设备或优先级不同的设备之间传输数据的区域。通
过缓冲区,可以使进程之间的相互等待变少,从而使从速度慢的设备读入数据时,速度快的设备的操
作进程不发生间断。
Free 中的 buffer 和 cache:(它们都是占用内存):
buffer : 作为 buffer cache 的内存,是块设备的读写缓冲区
cache: 作为 page cache 的内存, 文件系统的 cache
如果 cache 的值很大,说明 cache 住的文件数很多。如果频繁访问到的文件都能被 cache 住,
那么磁盘的读 IO 必会非常小。
10.13 其它的小问题
1.查看所有端口的占用情况 netstat,
2.怎么查看一个服务器是否正常运作 ps aux
3.创建用户命令 adduser 用户名
4.Linux:
fork 和 wait,有什么作用 fork 用来创建子进程 wait 如果子进程状态已经改变,那
么 wait 调用会立即返回。否则调用 wait 的进程将会阻塞直到有子进程改变状态或者有信号
来打断(这里所指的状态的改变包括:子进程终止;子进程被一个信号终止来;子进程被一
个信号恢复。)这个调用。 wait 是父进程用来等待来获取子进程的状态信息,获取到以后
清除掉子进程。
5.Linux 线程 其实在 Linux 中,新建的线程并不是在原先的进程中,而是系统通过一个系
统调用 clone() 。该系统 copy 了一个和原先进程完全一样的进程,并在这个进程中执行线
程函数。不过这个 copy 过程和 fork 不一样。copy 后的进程和原先的进程共享了所有的变
量,运行环境。这样,原先进程中的变量变动在 copy 后的进程中便能体现出来。
6.内存中 buffer 和 swap,cache 的区别
Swap:读取数据到内存,内存不够用,这时候把部分内存中的数据写入到磁盘上,这部分
磁盘空间就是 swap
Buffer:buffer(缓冲)是为了提高内存和硬盘(或其他 I/O 设备)之间的数据交换的速度而
设计的。 当程序要写入磁盘时候,不必等写入磁盘这个操作结束再去执行其他的,可以直
接写入到 buffer
Cache:cache(缓存)是为了提高 cpu 和内存之间的数据交换速度而设计的.
从磁盘读取
的先存入到 cacahe,以便下次再次访问时候使用。
7.linux 查看进程的运行堆栈信息命令-gstack gstack 进程 id8.linux 查看进程消耗的资源
https://www.cnblogs.com/sparkbj/p/6148817.html
可以使用一下命令查使用内存最多的 10 个进程
查看占用 cpu 最高的进程
ps aux|head -1;ps aux|grep -v PID|sort -rn -k +3|head
或者 top (然后按下 M,注意这里是大写)
查看占用内存最高的进程
ps aux|head -1;ps aux|grep -v PID|sort -rn -k +4|head
或者 top (然后按下 P,注意这里是大写)
9.smp SMP 的全称是"对称多处理"(Symmetrical Multi-Processing)技术,是指在一个计
算机上汇集了一组处理器(多 CPU),各 CPU 之间共享内存子系统以及总线结构。
进程文件里有哪些信息,,
sed 和 awk 的区别
Linux 命令(有一个文件被锁住,如何查看锁住它的线程,,)
linux 如何查找文件
十一.安全加密
http://www.ruanyifeng.com/blog/2011/08/what_is_a_digit
al_signature.html
11.1 数字签名
2.验证过程发送者:将报文通过 hash 算法生成摘要,用私钥加密生成签名。
接收者:使用公钥解密数字签名,得到摘要 A,再对报文进行 Hash 算法得到摘
要 B,比较 A 和 B,一致则表示没有被修改。
11.2 数字证书
数字证书则是由证书认证机构(CA, Certificate Authority)对证书申请者真
实身份验证之后,用 CA 的根证书对申请人的一些基本信息以及申请人的公钥进
行签名(相当于加盖发证书机构的公章)后形成的一个数字文件。
左图是 CA 证书
数字证书验证过程:CA 机构的公钥已经是在浏览器发布前提前嵌入到浏览器内部了,
所以 CA 的公钥是真实可靠的(如果 CA 机构被黑客攻陷,那么也可能是不可靠的),然后
服务器发送自己的公钥给 CA(用 CA 的公钥进行加密),CA 对服务器的发来的内容解密得
到服务器的公钥,然后 CA 对服务器的公钥进行颁发数字证书(就是数字签名),发给服务
器,服务器收到以后,将数字证书,公开密钥发送给客户端,客户端用 CA 的公开密钥验证
得到服务器的公开密钥,然后这样客户端就得到了真正可靠的服务器的公开密钥。
11.3 公私钥
公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个
公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。
使用这个密钥对的时候,如果用其中一个密钥加密一段数据,必须用另一个密钥解密。
比如用公钥加密数据就必须用私钥解密,如果用私钥加密也必须用公钥解密,否则解密将不
会成功。
11.4 非对称加密 RSA
非对称加密算法需要两个密钥:公开密钥(
publickey)和私有密钥(privatekey)。公
开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解
密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
RSA:
RSA 算法的步骤主要有以下几个步骤:
1、选择 p、q 两个超级大的质数 ,都是 1024 位,
2、令 n = p * q。取 φ(n) =(p-1) * (q-1)。 计算与 n 互质的整数的个数。
3、取 e ∈ 1 < e < φ(n) ,( n , e )作为公钥对,正式环境中取 65537。
可以打开任意一个被认证过的 https 证书,都可以看到。
4、令 ed mod φ(n) = 1,计算 d,( n , d ) 作为私钥对。 计算 d 可以
利用扩展欧几里的算法进行计算
5、销毁 p、q。密文 = 明文 ^ e mod n , 明文 = 密文 ^ d mod n。
利用蒙哥马利方法进行计算,也叫反复平方法,非常简单、
其中(n,e)是公钥
(n,d)是私钥
11.5 对称密钥 DES
DES:DES 算法是一种分组加密机制,将明文分成 N 个组,然后对各个组
进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密
文。
对称密钥就是加密使用的密钥是一致的。
11.6 DH 加密算法
Diffie-Hellman 算法概述:
(
1)Alice 与 Bob 确定两个大素数 n 和 g,这两个数不用保密
(
2)Alice 选择另一个大随机数 x,并计算 A 如下:A=g^x mod n
(3)Alice 将 A 发给 Bob
(
4)Bob 选择另一个大随机数 y,并计算 B 如下:B=g^y mod n
(5)Bob 将 B 发给 Alice
(
6)计算 Alice 的秘密密钥 K1 如下:K1=B^x mod n
(
7)计算 Bob 的秘密密钥 K2 如下:K2=A^y mod n K1=K2,因此 Alice 和 Bob 可以
用其进行加解密
11.7 SHA MD5
MD5:MD5 消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以
产生出一个 128 位(16 字节)的散列值(
hash value),用于确保信息传输完整一致。
SHA:安全散列算法(英语:Secure Hash Algorithm,缩写为 SHA)是一个密码散列函数家族,是 FIPS 所认证
的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不
同,它们对应到不同字符串的机率很高。十二.代码
12.1 读写文件(BufferedReader)
import java.io.*;
public class ReadWriteTxt {
public static void main(String args[]) {
try { // 防止文件建立或读取失败,用 catch 捕捉错误并打印,也可
以 throw
/* 读入 TXT 文件 */
String pathname = "input.txt"; // 绝对路径或相对路径都可以,
写入文件时演示相对路径
File filename = new File(pathname); // 要读取以上路径的
input.txt 文件
InputStreamReader reader = new InputStreamReader(
new FileInputStream(filename)); // 建立一个输入流
对象 reader
BufferedReader br = new BufferedReader(reader); // 建立一
个对象,它把文件内容转成计算机能读懂的语言
String line;
//网友推荐更加简洁的写法
while ((line = br.readLine()) != null) {
// 一次读入一行数据
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
try {
/* 写入 Txt 文件 */
File writename = new File("output.txt"); // 相对路径,如果
没有则要建立一个新的 output.txt 文件
writename.createNewFile(); // 创建新文件
BufferedWriter out = new BufferedWriter(newFileWriter(writename));
out.write("我会写入文件啦 1\r\n"); // \r\n 即为换行
out.write("我会写入文件啦 2\r\n"); // \r\n 即为换行
out.flush(); // 把缓存区内容压入文件
out.close(); // 最后记得关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}
}
12.2 反射
//Test01.java
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
//Exam.java
class Exam{
private String field1="私有属性";
public String field2="公有属性";
public void fun1(){
System.out.println("fun1:这是一个 public 访问权限方法");
}
private void fun2(){
System.out.println("fun2:这是一个 private 访问权限方法");
}
private void fun3(String arg){
System.out.println("fun3:这是一个 private 访问权限且带参数的方
法,参数为:"+arg);
}
}
public class ReflectTest {
public static void main(String args[]){
Exam e=new Exam();
try {
Field field1 = e.getClass().getDeclaredField("field1");
Field field2 = e.getClass().getDeclaredField("field2");
field1.setAccessible(true);
System.out.println("field1: "+field1.get(e));
field1.set(e,"重新设置一个 field1 值");System.out.println("field1: "+field1.get(e));
System.out.println("field2: "+field2.get(e));
field2.set(e,"重新设置一个 field2 值");
System.out.println("field2: "+field2.get(e));
} catch (NoSuchFieldException e1) {
e1.printStackTrace();
}catch (IllegalArgumentException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
}
try {
Method method1 = e.getClass().getDeclaredMethod("fun1");
method1.invoke(e);
Method method2 = e.getClass().getDeclaredMethod("fun2");
method2.setAccessible(true);
method2.invoke(e);
Method method3 =
e.getClass().getDeclaredMethod("fun3",String.class);
method3.setAccessible(true);
method3.invoke(e,"fun3 的参数");
} catch (NoSuchMethodException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}catch (IllegalAccessException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalArgumentException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (InvocationTargetException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}13.3 快排
public class Solution {
/**
* @param A: an integer array
* @return: nothing
*/
public void sortIntegers2(int[] A) {
// write your code here
quicksort(A,0,A.length-1);
}
public void quicksort(int[] A,int begin,int end)
{
int i = begin;
int j = end;
if(i >= j)
{
return;
}
int keng = A[i];
while(i < j)
{
while(i<j && A[j] > keng)
{
j--;
}
if(i<j && A[j] <= keng)
{
A[i] = A[j];
i++;
}
while(i<j && A[i] < keng)
{
i++;
}
if(i<j && A[i] >= keng)
{
A[j] = A[i];
j--;
}
}
A[i] = keng;
quicksort(A,begin,i-1);
quicksort(A,i+1,end);}
}
12.3 LRU
https://blog.csdn.net/hxqneuq2012/article/deta
ils/52709652
import java.util.HashMap;
public class Main {
int capacity;
HashMap<Integer, Node> map = new HashMap<Integer, Node>();
Node head = null;
Node end = null;
public Main(int capacity) {
this.capacity = capacity;
}
public int get(int key) {
if (map.containsKey(key)) {
Node n = map.get(key);
remove(n);
setHead(n);
printNodes("get");
return n.value;
}
printNodes("get");
return -1;
}
public void remove(Node n) {
if (n.pre != null) {
n.pre.next = n.next;
} else {
head = n.next;
}
if (n.next != null) {
n.next.pre = n.pre;
} else {
end = n.pre;
}}
public void setHead(Node n) {
n.next = head;
n.pre = null;
if (head != null)
head.pre = n;
head = n;
if (end == null)
end = head;
}
public void set(int key, int value) {
if (map.containsKey(key)) {
Node old = map.get(key);
old.value = value;
remove(old);
setHead(old);
} else {
Node created = new Node(key, value);
if (map.size() >= capacity) {
map.remove(end.key);
remove(end);
setHead(created);
} else {
setHead(created);
}
map.put(key, created);
}
printNodes("set");
}
public void printNodes(String explain) {
System.out.print(explain + ":" + head.toString());
Node node = head.next;
while (node != null) {
System.out.print(node.toString());node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
Main lruCacheTest = new Main(5);
lruCacheTest.set(1, 1);
lruCacheTest.set(2, 2);
lruCacheTest.set(3, 3);
lruCacheTest.set(4, 4);
lruCacheTest.set(5, 5);
System.out.println("lruCacheTest.get(1):" +
lruCacheTest.get(1));
lruCacheTest.set(6, 6);
System.out.println("lruCacheTest.get(2):" +
lruCacheTest.get(2));
}
}
class Node {
int key;
int value;
Node pre;
Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return this.key + "-" + this.value + " ";
}
}
十三.面经
13.1 作业帮面经
一面挂1. 循环有序数组找一个数
import java.util.*;
public class Main {
public static void main(String[] arg) {
int [] temp = {3,2};
System.out.println(find(temp,2));
}
public static int find(int [] array,int n)
{
if(array.length == 1)
{
if(array[0] == n)
return 0;
return -1;
}
int start = 0;
int end = array.length-1;
if(start >= end)
return -1;
int mid = (start + end) / 2;
while(start <= end)
{
mid = (start + end) / 2;
int flag = 0;
if(array[mid] == n)
return mid;
if(array[start] == n)
return start;
if(array[end] == n)
return end;
if(array[mid] > array[start])
{
if(array[mid] > n && n > array[start])
{
end = mid - 1;
}
else
{
start = mid + 1;
}
}else {
if(array[mid] < n && n < array[end])
{
start = mid + 1;}
else
{
end = mid - 1;
}
}
}
return -1;
}
}
十四.项目
14.1. jieba 分词原理
jieba 分词的原理
jieba 介绍:
一、支持三种分词模式:
精确模式,试图将句子最精确地切开,适合文本分析;
全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引
擎分词。
二、jieba 自带了一个叫做 dict.txt 的词典, 里面有 2 万多条词, 包含了词条出现的次数
(这个次数是于作者自己基于人民日报语料等资源训练得出来的)和词性. 这个第一条的 trie
树结构的词图扫描, 说的就是把这 2 万多条词语, 放到一个 trie 树中, 而 trie 树是有名的前
缀树, 也就是说一个词语的前面几个字一样, 就表示他们具有相同的前缀, 就可以使用 trie
树来存储, 具有查找速度快的优势。
三、jieba 分词应该属于概率语言模型分词
概率语言模型分词的任务是:在全切分所得的所有结果中求某个切分方案 S,使
得 P(S)最大。
jieba 用到的算法:
一、基于 Trie 树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成
的有向无环图(DAG)
1. 根据 dict.txt 生成 trie 树。字典在生成 trie 树的同时, 也把每个词的出现次数转
换为了频率;
2. 对待分词句子, 根据 dict.txt 生成的 trie 树, 生成 DAG, 实际上通俗的说, 就是
对待分词句子, 根据给定的词典进行查词典操作, 生成几种可能的句子切分。
jieba 的作者在
DAG 中记录的是句子中某个词的开始位
置, 从 0 到 n-1(n 为句子的长度), 每个开始位置作为字典的键, value是个list, 其中保存了可能的词语的结束位置(通过查字典得到
词, 开始位置+词语的长度得到结束位置)注:所以可以联想到,
jieba 支持全模
式
分词,能把句子中所有的可以成词的词语都扫描出来
例如:{0:[1,2,3]} 这样一个简单的 DAG, 就是表示 0 位置开始, 在 1,2,3 位置都是
词, 就是说 0~1, 0~2,0~3 这三个起始位置之间的字符, 在 dict.txt 中是词语.可看示例切分词
图。
二、采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合
1.查找待分词句子中已经切分好的词语(我觉得这里应该是全模式下的分词 list),
对该词语查找该词语出现的频率(次数/总数), 如果没有该词(既然是基于词典查找进行的分
词, 应该是有的), 就把词典中出现频率最小的那个词语的频率作为该词的频率, 也就是说
P(某词语)=FREQ.get(‘某词语’,min_freq)
2.根据动态规划查找最大概率路径的方法, 对句子从右往左反向计算最大概率
(一些教科书上可能是从左往右, 这里反向是因为汉语句子的重心经常落在后面, 就是落在
右边, 因为通常情况下形容词太多, 后面的才是主干, 因此, 从右往左计算, 正确率要高于
从左往右计算, 这个类似于逆向最大匹配), P(NodeN)=1.0,
P(NodeN-1)=P(NodeN)*Max(P(倒数第一个词))…依次类推, 最后得到最大概率路径, 得到
最大概率的切分组合.
Python
Python 如何写爬虫
python 全局锁
python 爬虫分为哪几种,分别是
你爬虫那个项目中是怎么解决反爬虫问题的?
Git
git 常用命令、有哪些目录或区域 git 是怎么管理代码的,提交错误的话,怎么撤销
介绍数据仓库
计算机磁盘
为什么磁盘 I/O 阻塞代价很大
SSD 没有磁头,为什么 I/O 代价还很高Socket
其它
项目中权限管理如何实现的
非对称加密
SHA,MD5
如何设计一个秒杀系统(看来你对限流不是很熟悉啊)
程序出现问题,如何定位(哪一行代码)
20 亿 QQ 号的插入与查找最小存储开销实现方案(提示:位图)
读过 JDK 源码吗
提升访问网页效率的方法(缓存:客户端缓存,cdn 缓存,服务器缓存,多线程,负载均衡之
类)
分布式服务中,某个服务速度很慢,如何排查(发散性较强,从计算机网络,到多线程到数据库结
构都能说说)
怎么定位查找
①了解分布式缓存、Zookeeper、阿里 dubbo、Nginx 等
②了解 NoSQL(Redis 等)
③了解 Hadoop 大数据相关知识
简述 Select poll 和 epoll,还有 direct io 和 buffer io 区别(一脸蒙蔽…)
C 的拷贝构造函数,深拷贝和浅拷
分布式架构中,怎么保证数据的一致性
1.pubilc A{ public void test(){} }
public B extends A{ protected void test(){} }
这样有问题吗?为什么?
2.public A{ public long test(){} }
public B extends A{ public int test(){} }
都不行
int i=0; Integer i1=0; Integer i2=new Integer(
0); 输出 i==i1;i==i2;i1==i2 分别是
false 还是 true
分别返回 true,true,false。
jdk 1.5 之后 有了自动装箱和拆箱 所以 前两个是 true 后两个 是不同的对象maven 冲突如何解决;
大型论坛网站难免会出现敏感评论, 如何过滤敏感评论
自我介绍:
项目:
1.
对你来说影响最大的一个项目(该面试中有关项目问题都针对该项目展开)?
2.
项目哪一部分最难攻克?如何攻克?、
个人建议:大家一定要选自己印象最深的项目回答,首先按模块,然后组成
人员,最后你在项目中的角色和发挥 的作用。全程组织好语言,最好不要有停顿,面试官可以
看出你对项目的熟悉程度
3.
你觉得你在项目运行过程中作为组长是否最大限度发挥了组员的优势?具体事
例?
4.
职业规划,今天想发展的工作方向
5.
项目里我遇到过的最大的困难是什么
6.
实验室的新来的研一,你会给他们什么学习上的建议,例如对于内核源码的枯
燥如何克服
7.
如何协调团队中多人的工作
8.
当团队中有某人的任务没有完成的很好,如何处理
9.
平时看些什么书,技术 综合
10.
项目解决的什么问题 用到了哪些技术
11.
怎么预防 bug 日志 jvm 异常信息 如何找问题的根源(统计表格)
12.
你是怎么学习的,说完会让举个例子
13.
实习投了哪几个公司?为什么,原因
14.
最得意的项目是什么?为什么?(回答因为项目对实际作用大,并得到认可)
15.
最得意的项目内容,讲了会
16.
你简历上写的是最想去的部门不是我们部门,来我们部门的话对你有影响麽?
17.
你除了在学校还有哪些方式去获取知识和技术?
18.
你了解阿里文化和阿里开源吗?
19.
遇到困难解决问题的思路?
20.
我觉得最成功的一件事了,我说能说几件吗,说了我大学明白明白了
自己想干什么,选择了自己喜欢的事,大学里学会了和自己相处,自己一个人的
时候也不会感觉无聊,精神世界比较丰富,坚持锻炼,健身,有个很不错的身体,
然后顿了顿笑着说,说,有一个对我很好的女朋友算吗?
21.
压力大的时候怎么调整?多个任务冲突了你怎么协调的?
22.
家里有几个孩子,父母对你来北京有什么看法?
23.
职业生涯规划
24.
你在什么情况下可能会离职25.
对你影响最大的人
26.
1. 优点 3 个,以及缺点 2. 说说你应聘这个岗位的优势 3. 说说家庭 4. 为什么
想来网易,用过网易的哪些产品,对比下有什么好的地方 5. 投递了哪些公司,对第一份工
作怎么看待
27.
为什么要选择互联网(楼主偏底层的)
28.
为什么来网易(看你如何夸)
29.
.在校期间怎样学习
30.
经常逛的技术性网站有哪些?
31.
举出你在开发过程中遇到的原先不知道的 bug, 通过各种方式定位 bug 并最终
成功解决的例子
32.
举出一个例子说明你的自学能力
7 次面试记录,除了京东基本上也都走到了很后面的阶段。硬要说经验可能有三
点:
不会就不会。我比较爽快,如果遇到的不会的甚至是不确定的,都直接说:
“对不起,
我答不上来”之类的。
一技之长。中间件和架构相关的实习经历,让我基本上和面试官都可以聊的很多,
也可以看到,我整个过程没有多少算法题。是因为面试官和你聊完项目就知道你能
做事了。其实,面试官很不愿意出算法题的(
BAT 那个档次除外),你能和他扯技
术他当然高兴了。关键很多人只会算法(逃)。
基础非常重要。面试官只要问 Java 相关的基础,我都有自信让一般的面试官感觉惊
讶,甚至学到新知识(之前遇到的阿里的面试官,我并没有做到,还被按在地上摩
擦)。
说话时面带微笑