本文为农行研发中心面试题的汇总,如果需要pdf版本的可以从我的资源中进行下载
Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap
有哪些线程安全的容器?
vactor、hashtable、concurrentHashMap、copyOnWriteArrayList是线程安全的。
一、同步集合类:hashtable、vector
同步集合包装类,Collections.synchronizedMap()和Collections.synchronizedList()
二、 并发集合类:ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteHashSet
① List 和 Set 实现了 Collection 接口,List 的元素有序可重复、Set 的元素无序不可重复,Map 是以键值对存储元素的。
Set不能根据索引获取元素,检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
② List 的实现包括 ArrayList(数组实现)、LinkedList(链表实现)、Vector(线程安全的 ArrayList) 和 Stack(继承 Vector,有栈的语义)。
③ Set 的实现包括 HashSet(通过 HashMap 实现,元素就是 HashMap 的 Key,Value 是一个 Object 类型的常量)、LinkedHashSet(通过 LinkedHashMap 实现)和 TreeSet(可以对元素排序,通过实现 Compare 接口或 Comparator 接口)。
④ Map 的实现主要包括 HashMap、LinkedHashMap(通过 LinkedList 维护插入顺序) 和 TreeMap(可以按 Key 排序,通过实现 Compare 接口或 Comparator 接口)。
ArrayList 是容量可变列表,使用数组实现,扩容时会创建更大的数组,把原有数组复制到新数组。支持对元素的随机访问,但插入与删除速度慢。ArrayList 实现了 RandomAcess 接口,如果类实现了该接口,使用索引遍历比迭代器更快。
LinkedList 本质是双向链表,与 ArrayList 相比增删速度更快,但随机访问慢。除继承 AbstractList 外还实现了 Deque 接口,该接口具有队列和栈的性质。成员变量被 transient 修饰,原理和 ArrayList 类似。优点是可以将零散的内存单元通过附加引用的方式关联起来,形成按链路顺序查找的线性结构,内存利用率高。
ArrayList和LinkedList的区别
ArrayList比LinkedList内存高效的原因
ArrayList随机访问比LinkedList快的原因
① HashMap 继承自 AbstractMap,HashTable 继承自 Dictionary。
② HashMap 中键值都可以为 null,HashTable 的键值都不允许为 null。
③ HashMap 线程不安全,HashTable 通过 synchronized 保证了线程安全。
java.util.HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。
**HashSet底层是采用HashMap实现的。**HashSet的add()方法调用HashMap的put()方法实现,如果键已经存在,map.put()放回的是旧值,添加失败。如果添加成功map.put()方法返回的是null,HashSet.add()方法返回的true,则添加的元素可以作为map中的key。
HashSet存放的是哈希值,HashSet存储元素的顺序并不是按照存入时的顺序(和List显然不同),是按照哈希值来存的,所以取数据也是按照哈希值取。HashSet不存入重复元素的规则:使用hashcode和equals。 那么HashSet是如何检查重复?HashSet会通过元素的hashcode()和equals()方法进行判断,当试图将元素加入到Set集合中,HashSet首先会使用对象的hashcode来判断对象加入的位置。同时也会与其他已经加入的对象的hashcode进行比较,如果没有相等的hashcode,HashSet就认为这个对象之前不存在,如果之前存在同样的hashcode值,就会进一步的比较equals()方法,如果equals()比较返回结果是true,那么认为该对象在集合中的对象是一模一样的,不会将其加入;如果比较返回的是false,那么HashSet认为新加入的对象没有重复,可以正确加入。
add(Object element): 向列表的尾部添加指定的元素。
size(): 返回列表中的元素个数。
get(int index): 返回列表中指定位置的元素,index从0开始。
clear(): 从列表中移除所有元素。
isEmpty(): 判断列表是否包含元素,不包含元素则返回 true,否则返回false。
传统的for循环遍历,基于计数器的
迭代器遍历,Iterator(可以在对集合遍历的同时进行添加删除等操作)
foreach循环遍历
方法重载发生在同一个类中,是让类以统一的方式处理不同类型数据的一种手段。调用方法时通过传递给它们的不同个数和类型的参数来决定具体使用哪个方法,这就是多态性。重载是指我们可以定义一些名称相同的方法,通过定义不同的参数来区分这些方法,然后再调用时,Java虚拟机就会根据不同的参数列表来选择合适的方法执行。也就是说,当一个重载方法被调用时,Java用参数的类型或个数来决定实际调用的重载方法。因此,每个重载方法的参数的类型或个数必须是不同。
方法的重载在实际应用中也会经常用到。不仅是一般的方法,构造方法也可以重载。
方法重载的规则:
(1)方法名相同
(2)参数列表不同(参数的个数、类型或者顺序不同)
(3)与返回值类型无关
(4)与访问权限无关
方法重写发生在有继承关系的子类中。在Java程序中,类的继承关系可以产生一个子类,子类继承父类,它具备了父类所有的特征,继承了父类所有的方法和变量。当子类需要修改父类的一些方法进行扩展,增大功能,称为重写,也叫称为覆写或覆盖。重写的好处在于子类可以根据需要,定义特定于自己的行为。
在重写方法时,需要遵循以下的规则:
(1)方法名相同
(2)参数列表相同
(3)返回类型相同
(4)父类的访问权限修饰符的限制一定要大于被子类重写方法的访问权限修饰符,所以如果某一个方法在父类中的访问权限是private,那么就不能在子类中对其进行重写。如果重新定义,也只是定义了一个新的方法,不会达到重写的效果。
如果子类将父类中的方法重写了,调用的时候肯定是调用被重写过的方法。如果现在一定要调用父类中的方法,那么super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。
泛型本质是参数化类型,解决不确定对象具体类型的问题。
泛型的好处:① 类型安全,不存在 ClassCastException。② 提升可读性,编码阶段就显式知道泛型集合、泛型方法等处理的数据类型。
泛型用于编译阶段,编译后的字节码文件不包含泛型类型信息,因为虚拟机没有泛型类型对象,所有对象都属于普通类。例如定义 List
或 List
,在编译后都会变成 List
。
在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射,缺点是破坏了封装性及泛型约束。
如何通过反射获取方法?
==
既可以用于比较基本数据类型,又可以在对象之间进行比较。
equals
只能用于对象之间的比较,默认使用 ==
比较,也可以重写自定义比较规则。
每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。为了在集合中正确使用,一般需要同时重写 equals 和 hashCode,要求 equals 相同 hashCode 必须相同,hashCode 相同 equals 未必相同。
方法 | 说明 |
---|---|
equals | 检测对象是否相等,默认使用 == 比较,可以重写该方法自定义规则。 |
hashCode | 每个对象都有默认散列码,值由对象存储地址得出。字符串散列码由内容导出,值可能相同。 |
toString | 默认打印表示对象值的一个字符串。 |
clone | 默认声明为 protected,只能由本类对象调用,且是浅拷贝。一般重写 clone 方法需要实现 Cloneable 接口并声明为 public,如果没有实现 Cloneable 接口会抛出 CloneNotSupport 异常。 |
finalize | GC 判断垃圾时,如果对象没有与 GC Roots 相连会被第一次标记,之后判断对象是否有必要执行 finalize 方法,有必要则由一条低调度优先级的 Finalizer 线程执行。虚拟机会触发该方法但不保证结束,防止方法执行缓慢或发生死循环。只要对象在 finalize 方法中重新与引用链相连,就会在第二次标记时移出回收集合。由于运行代价高且具有不确定性,在 JDK9 标记为过时方法。 |
getClass | 返回对象所属类的 Class 对象。 |
wait | 阻塞持有该对象锁的线程。 |
notify | 唤醒持有该对象锁的线程,notify 随机唤醒一个线程,notifyAll 唤醒全部线程。 |
封装是对象功能内聚的表现形式,在抽象基础上决定信息是否公开及公开等级。主要任务是对属性、数据、敏感行为实现隐藏,使对象关系变得简单,降低耦合。
继承用来扩展类,子类可继承父类的部分属性和行为,使模块具有复用性。
多态以封装和继承为基础,根据运行时对象实际类型使同一行为具有不同表现形式。多态指在编译层面无法确定最终调用的方法体,在运行期由 JVM 动态绑定,调用合适的重写方法。由于重载属于静态绑定,本质上重载结果是完全不同的方法,因此多态一般专指重写。
Java 对象在 JVM 退出时会全部销毁,如果需要将对象持久化就要通过序列化实现,将内存中的对象保存在二进制流中,需要时再将二进制流反序列化为对象。对象序列化保存的是对象的状态,属于类属性的静态变量不会被序列化。
常见的序列化有三种:① Java 原生序列化,实现 Serializabale
标记接口,兼容性最好,但不支持跨语言,性能一般。序列化和反序列化必须保持序列化 ID 的一致,一般使用 private static final long serialVersionUID
定义序列化 ID,如果不设置编译器会根据类的内部实现自动生成该值。② Hessian 序列化,支持动态类型、跨语言。③ JSON 序列化,将数据对象转换为 JSON 字符串,抛弃了类型信息,反序列化时只有提供类型信息才能准确进行。相比前两种方式可读性更好。
序列化通常使用网络传输对象,容易遭受攻击,因此不需要进行序列化的敏感属性应加上 transient 关键字,把变量生命周期仅限于内存,不会写到磁盘。
数据类型 | 内存大小 |
---|---|
byte | 1 B |
short | 2 B |
int | 4 B |
long | 8 B |
float | 4 B |
double | 8 B |
char | 英文 1B,中文 UTF-8 占 3B,GBK 占 2B。 |
boolean | 单个变量 4B / 数组 1B |
(1)字符修改上的区别(主要)
String:不可变字符串;
StringBuffer:可变字符串、效率低、线程安全;
StringBuilder:可变字符序列、效率高、线程不安全;
(2)初始化上的区别,String可以空赋值,后者不行,报错
异常体系结构
异常也是一种对象,java当中定义了许多异常类,并且定义了基类java.lang.Throwable作为所有异常的超类。Java语言设计者将异常划分为两类:Error和Exception,其体系结构大致如下图所示:
Throwable:有两个重要的子类:Exception(异常)和 Error(错误),两者都包含了大量的异常处理类。
1、Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。
这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。
2、Exception(异常):程序本身可以捕获并且可以处理的异常。
Exception这种异常又分为两类:运行时异常和编译异常。
编译异常(受检异常):比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
运行时异常(不受检异常):RuntimeException类及其子类表示 JVM 在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
在java应用中,异常的处理机制分为抛出异常和捕获异常。
捕获异常
try代码块:用于捕获异常。其后可以接零个或者多个catch块。如果没有catch块,后必须跟finally块,来完成资源释放等操作
catch代码块:用于捕获异常,并在其中处理异常。
finally代码块:无论是否捕获异常,finally代码总会被执行。如果try代码块或者catch代码块中有return语句时,finally代码块将在方法返回前被执行。
try-catch-finally代码块的执行顺序:
A)try没有捕获异常时,try代码块中的语句依次被执行,跳过catch。如果存在finally则执行finally代码块,否则执行后续代码。
B)try捕获到异常时,如果没有与之匹配的catch子句,则该异常交给JVM处理。如果存在finally,则其中的代码仍然被执行,但是finally之后的代码不会被执行。
C)try捕获到异常时,如果存在与之匹配的catch,则跳到该catch代码块执行处理。如果存在finally则执行finally代码块,执行完finally代码块之后继续执行后续代码;否则直接执行后续代码。另外注意,try代码块出现异常之后的代码不会被执行。
抛出异常
1、throws抛出异常
如果一个方法可能抛出异常,但是没有能力处理该异常或者需要通过该异常向上层汇报处理结果,可以在方法声明时使用throws来抛出异常。这就相当于计算机硬件发生损坏,但是计算机本身无法处理,就将该异常交给维修人员来处理。
public methodName throws Exception1,Exception2….(params){}
其中Exception1,Exception2…为异常列表一旦该方法中某行代码抛出异常,则该异常将由调用该方法的上层方法处理。如果上层方法无法处理,可以继续将该异常向上层抛。
2、throw抛出异常
在方法内,用throw来抛出一个Throwable类型的异常。一旦遇到到throw语句,后面的代码将不被执行。然后,便是进行异常处理——包含该异常的try-catch最终处理,也可以向上层抛出。注意我们只能抛出Throwable类和其子类的对象。
throw newExceptionType;
Java 接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。
实现接口和继承关键字(implements,extends),Java中接口的成员特点
成员变量:只有共有的静态常量,所以实现类中不能对值进行修改(默认修饰符:public static final )
构造方法:接口不能实例化对象,所以接口没有构造方法
成员方法:接口中每一个方法也是隐式抽象的,默认修饰符:public abstract
接口里面除了抽象方法还可以有其它的方法吗?
JDK 1.8 以后,接口里可以有默认方法和静态方法
JDK 1.9 以后,接口里可以有私有方法允许将方法定义为 private。
抽象类和接口的区别
普通类和抽象类区别
java调用父类构造方法
子类继承父类,子类的构造方法必须调用super()即父类的构造方法,而且必须放在构造方法的第一行
如果父类"只"有无参构造方法,且不打算重写子类的构造方法,为节省代码量,子类构造方法可以不写,系统默认调用父类无参构造方法super(),即默认会在子类的构造方法中的第一行加上父类的无参构造方法
Java中堆区和栈区大小,具体JVM怎么调度
类加载机制了解吗,如何能避免重复加载的
java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。其中类装载器的作用其实就是类的加载。来看类的加载的概念:
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们的顺序如下图所示:
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
类加载器,什么时候会用到自定义类加载
1、Java语言系统自带有三个类加载器:
Bootstrap ClassLoader :启动类加载器;Extention ClassLoader :扩展的类加载器;Appclass Loader:应用程序类加载器。这三种类加载器的加载顺序是什么呢?
Bootstrap ClassLoader > Extention ClassLoader > Appclass Loader
什么时候会用到自定义类加载
多态
多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作
多态存在的三个必要条件
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError,例如一个递归方法不断调用自己。该异常有明确错误堆栈可供分析,容易定位问题。
判断垃圾
引用计数:在对象中添加一个引用计数器,如果被引用计数器加 1,引用失效时计数器减 1,如果计数器为 0 则被标记为垃圾。简单高效,但在 Java 中很少使用,因为存在对象循环引用的问题,导致计数器无法清零。
可达性分析:通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程走过的路径称为引用链,如果某个对象到 GC Roots 没有任何引用链相连则会被标记为垃圾。可作为 GC Roots 的对象包括:虚拟机栈和本地方法栈中引用的对象、类静态属性引用的对象、常量引用的对象。
GC 算法
标记清除:分为标记和清除阶段,首先从每个 GC Roots 出发依次标记有引用关系的对象,最后清除没有标记的对象。如果堆包含大量对象且大部分需要回收,必须进行大量标记清除,效率低。
存在内存空间碎片化问题,分配大对象时容易触发 Full GC。
标记复制:为解决内存碎片,将可用内存按容量划分为大小相等的两块,每次只使用其中一块,主要用于新生代。对象存活率高时要进行较多复制操作,效率低。如果不想浪费空间就需要有额外空间分配担保,老年代一般不使用此算法。
标记整理:老年代使用标记整理算法,标记过程与标记清除算法一样,但不直接清理可回收对象,而是让所有存活对象都向内存空间一端移动,然后清理掉边界以外的内存。
Serial:最基础的收集器,使用复制算法、单线程工作,进行垃圾收集时必须暂停其他线程。Serial 是客户端模式的默认新生代收集器,对于处理器核心较少的环境,由于没有线程开销,可获得最高的单线程收集效率。
ParNew:Serial 的多线程版本,ParNew 是虚拟机在服务端模式的默认新生代收集器。
Parallel Scavenge:基于复制算法、多线程工作的新生代收集器,目标是高吞吐量。
Serial Old:Serial 的老年代版本,使用整理算法,是客户端模式的默认老年代收集器。
Parellel Old:Parallel Scavenge 的老年代版本,支持多线程,基于整理算法。
CMS:以获取最短回收停顿时间为目标,基于清除算法,过程分为四个步骤:初始标记、并发标记、重新标记、并发清除。缺点:① 对处理器资源敏感,并发阶段虽然不会导致用户线程暂停,但会降低吞吐量。② 无法处理浮动垃圾,有可能出现并发失败而导致 Full GC。③ 基于清除算***产生空间碎片。
G1:开创了面向局部收集的设计思路和基于 Region 的内存布局,主要面向服务端,最初设计目标是替换 CMS。可面向堆任何部分来组成回收集进行回收,衡量标准不再是分代,而是哪块内存中垃圾的价值最大。价值即回收所获空间大小以及回收所需时间的经验值,G1 在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值最大的 Region。运作过程:初始标记、并发标记、最终标记、筛选回收。
ZGC:JDK11 中加入的具有实验性质的低延迟垃圾收集器,目标是尽可能在不影响吞吐量的前提下,实现在任意堆内存大小都可以把停顿时间限制在 10ms 以内的低延迟。基于 Region 内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术实现可并发的标记整理。ZGC 的 Region 具有动态性,是动态创建和销毁的,并且容量大小也是动态变化的。
JVM 把描述类的数据从 Class 文件加载到内存,并对数据进行验证、解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
加载:通过一个类的全限定类名获取对应的二进制流,在内存中生成对应的 Class 实例,作为方法区中这个类的访问入口。
验证:确保 Class 文件符合约束,防止因载入有错字节流而遭受攻击。包含:文件格式验证、元数据验证、字节码验证、符号引用验证。
准备:为类静态变量分配内存并设置零值,该阶段进行的内存分配仅包括类变量,不包括实例变量。
解析:将常量池内的符号引用替换为直接引用。
初始化:直到该阶段 JVM 才开始执行类中编写的代码,根据程序员的编码去初始化类变量和其他资源。
双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父加载器。
一个类加载器收到了类加载请求,不会自己去尝试加载,而将该请求委派给父加载器,每层的类加载器都是如此,因此所有加载请求最终都应该传送到启动类加载器,只有当父加载器反馈无法完成请求时,子加载器才会尝试。
类跟随它的加载器一起具备了有优先级的层次关系,确保某个类在各个类加载器环境中都是同一个,保证程序的稳定性。
final 类不能被继承,所有成员方法都会被隐式地指定为 final 方法,final 方法不能被重写。
final 变量表示常量,只能被赋值一次,赋值后值不再改变。
内存语义
① 继承 Thread 类并重写 run
方法。实现简单,但不符合里氏替换原则,不可以继承其他类。
② 实现 Runnable 接口并重写 run
方法。避免了单继承局限性,实现解耦。
③实现 Callable 接口并重写 call
方法。可以获取线程执行结果的返回值,并且可以抛出异常。
在 JAVA中,用 Thread 类代表线程,所有线程对象,都必须是Thread类或者Thread类子类的实例。每个线程的任务就是执行一段顺序执行的代码,JAVA使用线程执行体来容纳这段代码。
第一种,通过继承Thread类创建线程类
通过继承Thread类来创建并启动多线程的步骤如下:
1、定义一个类继承Thread类,并重写Thread类的run()方法,run()方法的方法体就是线程要完成的任务,因此把run()称为线程的执行体;
2、创建该类的实例对象,即创建了线程对象;
3、调用线程对象的start()方法来启动线程;
第二种,通过实现Runnable接口创建线程类
这种方式创建并启动多线程的步骤如下:
1、定义一个类实现Runnable接口;
2、创建该类的实例对象obj;
3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;
4、调用线程对象的start()方法启动该线程;
第三种,通过Callable和Future接口创建线程
通过实现Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程的执行体,那么,是否可以直接把任意方法都包装成线程的执行体呢?从JAVA5开始,JAVA提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:
1、call()方法可以有返回值;
2、call()方法可以声明抛出异常;
从这里可以看出,完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是call()方法。但问题是:Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。还有一个原因就是:call()方法有返回值,call()方法不是直接调用,而是作为线程执行体被调用的,所以这里涉及获取call()方法返回值的问题。
于是,JAVA5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现了Future接口,并实现了Runnable接口,所以FutureTask可以作为Thread类的target,同时也解决了Callable对象不能作为Thread类的target这一问题。
通过继承Thread类实现多线程:
优点:
1、实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程;
缺点:
1、线程类已经继承Thread类了,就不能再继承其他类;
2、多个线程不能共享同一份资源(如前面分析的成员变量 i );
通过实现Runnable接口或者Callable接口实现多线程:
优点:
1、线程类只是实现了接口,还可以继承其他类;
2、多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况。
缺点:
1、通过这种方式实现多线程,相较于第一类方式,编程较复杂;
2、要访问当前线程,必须调用Thread.currentThread()方法。
综上:
一般采用第二类方式实现多线程。
run()和start()的区别可以用一句话概括:单独调用run()方法,是同步执行;通过start()调用run(),是异步执行。
调用 start()
方法方可启动线程并使线程进入就绪状态,直接执行 run()
方法的话不会以多线程的方式执行。
Runnable接口和Callable接口的区别
Runnable需要实现run()方法,Callable需要实现call()方法;run()方法不能返回值,call()方法可以返回值
Runnable不能抛出checked exception, Callable可以抛出checked exception
为什么要线程同步
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100。取钱成功了,账户余额是0。那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
线程同步的方法
(1)同步方法:
即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
代码如:public synchronized void save(){}
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
(2)同步代码块:
即有synchronized关键字修饰的语句块。由于java的每个对象都有一个内置锁,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
(3)使用特殊域变量(volatile)实现线程同步:
它的原理是每次线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
//需要同步的变量加上volatile
private volatile int account = 100;
(4)使用重入锁实现线程同步:
ReentrantLock 类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和代码块具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
注:如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
有时候我们必须知道是否当前线程持有锁,怎么知道
如果用synchronized,用Thread.holdsLock(lockObj)
获取,它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。
如果使用 Lock(juc下的),则用 lock.isHeldByCurrentThread() (
不能用 Thread.holdsLock(lockObj))
Java 采用共享内存模型,线程间的通信总是隐式进行,整个通信过程对程序员完全透明。
volatile 告知程序任何对变量的读需要从主内存中获取,写必须同步刷新回主内存,保证所有线程对变量访问的可见性。
synchronized 确保多个线程在同一时刻只能有一个处于方法或同步块中,保证线程对变量访问的原子性、可见性和有序性。
等待通知机制指一个线程 A 调用了对象的 wait
方法进入等待状态,另一线程 B 调用了对象的 notify/notifyAll
方法,线程 A 收到通知后结束阻塞并执行后序操作。对象上的 wait
和 notify/notifyAll
完成等待方和通知方的交互。
如果一个线程执行了某个线程的 join
方法,这个线程就会阻塞等待执行了 join
方法的线程终止,这里涉及等待/通知机制。join
底层通过 wait
实现,线程终止时会调用自身的 notifyAll
方法,通知所有等待在该线程对象上的线程。
管道 IO 流用于线程间数据传输,媒介为内存。PipedOutputStream 和 PipedWriter 是输出流,相当于生产者,PipedInputStream 和 PipedReader 是输入流,相当于消费者。管道流使用一个默认大小为 1KB 的循环缓冲数组。输入流从缓冲数组读数据,输出流往缓冲数组中写数据。当数组已满时,输出流所在线程阻塞;当数组首次为空时,输入流所在线程阻塞。
ThreadLocal 是线程共享变量,但它可以为每个线程创建单独的副本,副本值是线程私有的,互相之间不影响。
保证变量对所有线程可见:当一条线程修改了变量值,新值对于其他线程来说立即可见。
禁止指令重排序优化:使用 volatile 变量进行写操作,汇编指令带有 lock 前缀,lock 引发两件事:① 将当前处理器缓存行的数据写回系统内存。②使其他处理器的缓存无效。相当于对缓存变量做了一次 store 和 write 操作,让 volatile 变量的修改对其他处理器立即可见。
写 volatile 变量时,把该线程工作内存中的值刷新到主内存;读 volatile 变量时,把该线程工作内存值置为无效,从主内存读取。
可以使用 type
查看键的数据结构,包括:string、hash、list、set、zset,这些是 Redis 对外的数据结构。实际上每种数据结构都有底层的内部编码,Redis 根据场景选择合适的内部编码,可以使用 object encoding
查看。
string
概念:键是字符串,值可以是字符串(JSON,XML)、数字(整形、浮点数)、二进制(图片、音频、视频),最大不超过 512 MB。
命令:set、get、setex、setnx、mset、mget、incr、decr。
内部编码:① int(< 8B)。② embstr(不大于 39 字节)。③ raw(大于 39 字节)。
应用场景:① 缓存:Redis 作为缓存,MySQL 作为存储层,首先从 Redis 获取数据,如果失败就从 MySQL 获取并将结果写回 Redis 并添加过期时间。② 计数:Redis 可以实现快速计数功能,例如视频每播放一次就用 incr 把播放数加 1。③ 共享 Session:一个分布式 Web 服务将用户的 Session 信息保存在各自服务器,但会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问负载到不同服务器上,用户刷新一次可能会发现需要重新登陆。为解决该问题,可以使用 Redis 将用户的 Session 进行集中管理,每次用户更新或查询登录信息都直接从 Redis 获取。
hash
概念:键值本身又是一个键值对结构,哈希类型中的映射关系叫 field-value, value 是指 field 对应的值而不是键对应的值。
命令:hset、hget、hdel、hlen、hexists。
内部编码:① ziplist(field <= 512 且 value <= 64B)。② hashtable(field > 512 或 value > 64B)。
list
概念:存储多个有序字符串,每个字符串称为元素,一个列表最多可以存储 232-1 个元素。可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引的元素等。列表是一种比较灵活的数据结构,可以充当栈和队列,在实际开发中有很多应用场景。list 有两个特点:① 元素有序,可以通过索引获取某个元素或某个范围的元素。② 元素可以重复。
命令:lpush、rpop、lrange、lindex、llen。
内部编码:① ziplist(key <= 512 且 value <= 64B)。② linkedlist(key > 512 或 value > 64B)。③ quicklist。
应用场景:lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。
set
概念:保存多个字符串元素,和 list 不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 232-1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
命令:sadd、sremove、scard、sismember、spop。
内部编码包括:① intset(key <= 512 且 element 是整数)。② hashtable(key > 512 或 element 不是整数)。
应用场景:sadd = 标签、spop = 生成随机数,比如抽奖、sinter = 社交需求。
zet
概念:有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和 list 使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。
命令:zadd、zremove、zscore、zrank、zcount。
内部编码:① ziplist(key <= 128 且 member <= 64B)。② skiplist(key > 128 或 member > 64B)。
应用场景:有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用 zadd 和 zincrby。如果需要将用户从榜单删除,可以使用 zrem。如果要展示获取赞数最多的十个用户,可以使用 zrange。
算法剔除:① FIFO 先进先出,判断存储时间,离当前时间最远的数据优先淘汰。② LRU 最近最少使用,判断最近使用时间,离当前时间最远的数据优先被淘汰。③ LFU 最不常用,被使用次数最少的数据优先淘汰。每个数据块都有一个引用计数,按照引用计数排序,具有相同计数的数据块按时间排序。数据一致性最差。
超时剔除:给缓存设置过期时间,例如 Redis 的 expire 命令。数据一致性较差。
主动更新:在真实数据更新后立即更新缓存,可以利用消息系统实现。数据一致性强,但可能导致脏数据,建议结合超时剔除使用。
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
缓存穿透指查询不存在的数据,即缓存和数据库中都没有的数据,缓存层和存储层都不会命中,导致不存在的数据每次请求都要到存储层查询,可能会使后端负载增大。
解决:① 缓存空对象,如果一个查询返回结果为 null,仍然缓存,为其设置很短的过期时间。② 布隆过滤器,将所有可能存在的数据映射到一个足够大的 Bitmap 中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被拦截。
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力,导致服务崩溃。
解决:① 加锁互斥,当一个线程访问后,缓存数据会被重建。② 热点数据不过期。直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决:1、过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
2、热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
3、加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。
脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
不可重复读:指对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻读。例如事务T1批量对一个表中某一列列值为1的数据修改为2的变更,但是在这时,事务T2对这张表插入了一条列值为1的数据,并完成提交。此时,如果事务T1查看刚刚完成操作的数据,发现还有一条列值为1的数据没有进行修改,而这条数据其实是T2刚刚提交插入的,这就是幻读。
未提交读:事务中的修改即使没有提交,对其他事务也是可见的。事务可以读取其他事务修改完但未提交的数据,这种问题称为脏读。这个级别还存在不可重复读和幻读,很少使用。
提交读:多数数据库的默认隔离级别,事务只能看见已提交事务的修改。存在不可重复读,两次执行同样的查询可能会得到不同结果。
可重复读(MySQL默认的隔离级别):解决了不可重复读,保证同一个事务中多次读取同样的记录结果一致,InnoDB 通过 MVCC 解决。但无法解决幻读,幻读指当某个事务在读取某个范围内的记录时,会产生幻行。
可串行化:最高隔离级别,通过强制事务串行执行避免幻读。在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际很少使用,只有非常需要确保数据一致性时考虑。
事务是一组原子性的 SQL 语句,当有任何一条语句因崩溃或其他原因无法执行时,所有语句都不会执行。事务内的语句要么全部执行成功,要么全部执行失败。
原子性:一个事务在逻辑上是必须不可分割的最小单元,整个事务中的所有操作要么全部成功,要么全部失败。
一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。
隔离性:针对并发事务而言,要隔离并发运行的多个事务之间的影响,数据库提供了多种隔离级别。
持久性:一旦事务提交成功,其修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。
按功能分类:
内连接:等值连接、非等值连接、自连接
外连接:左外连接、右外连接、全外连接(全外连接=内连接的交集+表1 有表2 没有的记录+表2 有表1没有的记录)
交叉连接:无顺序关系=笛卡尔乘积
类型 | 含义 |
---|---|
左外连接 | 以左表为主表,可以查询左表存在而右表为 null 的记录。 |
右外连接 | 以右表为主表,可以查询右表存在而左表为 null 的记录。 |
内连接 | 查询左右表同时满足条件的记录,两边都不可为 null。 |
MyISAM 和 InnoDB 的区别
InnoDB 支持事务,MyISAM 不支持事务。
InnoDB 支持外键,而 MyISAM 不支持。
InnoDB 是聚集索引,MyISAM 是非聚集索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。而 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。因此当INSERT(插入)或UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。
如何选择:
是否要支持事务,如果要请选择 InnoDB,如果不需要可以考虑 MyISAM;
如果表中绝大多数都只是读查询,可以考虑 MyISAM,如果既有读写也挺频繁,请使用InnoDB。
系统奔溃后,MyISAM恢复起来更困难,能否接受,不能接受就选 InnoDB;
MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的。如果你不知道用什么存储引擎,那就用InnoDB,至少不会差。
Memory
① 如果需要快速访问数据且这些数据不会被修改,重启以后丢失也没有关系,可以使用 Memory 表。② 数据保存在内存,不需要磁盘 IO,表的结构在重启后会保留,数据会丢失。③ 支持哈希索引,查找速度快。④ 使用表锁,并发性能低。
① like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。
② or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效
③ 组合索引,不是使用第一列索引,根据最左前缀匹配原则,索引失效。
④ 隐式类型转换,如varchar不加单引号的话可能会自动转换为int型,使索引无效,产生全表扫描。
⑤ 在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。
⑥ Where后的查询字段尽量减少使用运算和函数。
聚簇索引不是一种索引类型,而是一种数据存储方式。InnoDB 的聚簇索引实际上在同一个结构中保存了 B 树索引和数据行。当表有聚簇索引时,它的行数据实际上存放在索引的叶子页中,由于无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
优点:可以把相关数据保存在一起;将索引和数据保存在同一个 B 树中,获取数据比非聚簇索引要更快。
缺点:如果数据全部在内存中会失去优势;更新代价高,强制每个被更新的行移动到新位置;插入行或主键更新时,可能导致页分裂,占用更多磁盘空间。
① 使用EXPLAIN关键词检查SQL。EXPLAIN可以分析你的查询语句或是表结构的性能瓶颈,其查询结果还会告诉你的索引主键被如何利用的,你的数据表是如何被搜索和排序的,是否有全表扫描等;
② 建立索引,避免全表扫描:考虑在 WHERE 和 ORDER BY 涉及的列上建立索引,同时注意避免索引失效的情况。
③ 避免子查询:多表关联尽量用join,减少子查询的使用。
④ 尽量使用limit进行分页批量查询,不要一次全部获取。
⑤ 绝对避免select *的使用,尽量select具体需要的字段,减少不必要字段的查询;
⑥ 避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤,这个处理需要排序,总计等操作。如果能通过WHERE子句限制记录的
数目,那就能减少这方面的开销。
⑦ 使用用户自定义变量:用户自定义变量是一个用来存储内容的临时容器,在连接 MySQL 的整个过程中都存在,可以在任何可以使用表达式的地方使用自定义变量,避免重复查询刚刚更新过的数据。
1、delete 是DML 操作,可以进行回滚;drop 是DDL,会隐式提交,所以,不能回滚。
2、delete 一条一条进行删除,可以加where 条件,所以速度慢;truncate 语句是删除全部数据,不允许加where,但是速度快
3、如果要删除的表中有自增长列:用delete 删除后,再插入数据,自增长列的值从断点开始;而用truncate 删除后再插入数据,自增长列的值从1 开始
4、delete 删除语句有返回值,会返回受影响的行数,truncate 语句没有返回值
5、drop 语句删除表结构及所有数据,并将表所占用的空间全部释放。
总结:
1、在速度上,一般来说,drop> truncate > delete。
2、在使用drop 和truncate 时一定要注意,虽然可以恢复,但为了减少麻烦,还是要慎重。
3、如果想删除部分数据用delete,注意带上where 子句,回滚段要足够大;
视图是一个虚拟表,是存储在数据库中的查询 SQL 语句,视图只是一个逻辑,具体结果在引用视图时动态生成。
优点:① 具有安全性,可以进行权限控制,创建只读视图,公开给特定用户。② 可以简化复杂的查询,保存其逻辑。
B-Tree:大多数引擎都支持这种索引,但底层使用不同结构,例如 NDB 使用 T-Tree,InnoDB 使用 B+ Tree。所有的值都是顺序存储的,并且每个叶子页到根的距离相同。B-Tree 索引能够加快访问数据的速度,存储引擎不再需要进行全表扫描来获取数据,而是从索引的根节点开始搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。叶子节点的指针指向的是被索引的数据,而不是其他节点页。
Hash: 哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,索引自身只需存储对应的哈希值,所以索引结构十分紧凑,这让哈希索引的速度非常快。
空间索引:MyISAM 的一个特殊索引类型,用作地理数据存储。
全文索引:MyISAM 的一个特殊的 B-Tree 索引,一共有两层。第一层是所有关键字,然后对于每一个关键字的第二层,包含的是一组相关的文档指针。用于通过关键字匹配进行查询。
范式是数据库设计规范,范式越高则数据库冗余越小,但查询也更复杂,一般只需满足第三范式。
范式 | 含义 |
---|---|
第一范式 | 每列都是不可再分的数据单元。 |
第二范式 | 在第一范式的基础上消除部分依赖,非主键列完全依赖于主键列。 |
第三范式 | 在第二范式的基础上消除传递依赖,非主键列只依赖于主键列。 |
选择唯一性索引:唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
建立索引:对查询频次较高且数据量比较大的表建立索引。为经常需要排序、分组和联合操作的字段建立索引。如果 WHERE 子句中的组合比较多,应当挑选最常用、过滤效果最好的列的组合。
使用前缀索引:对于 BLOB、TEXT 或很长的 VARCHAR 列必须使用前缀索引,MySQL 不允许索引这些列的完整长度。
**控制数量:**索引越多代价越高,对于 DML 频繁的表,索引过多会导致很高的维护代价。
**使用短索引:**假如构成索引的字段长度比较短,那么在储块内就可以存储更多的索引,提升访问索引的 IO 效率。
**合适的索引顺序:**当不需要考虑排序和分组时,将选择性最高的列放在前面。索引的选择性是指不重复的索引值和数据表的记录总数之比,索引的选择性越高则查询效率越高。
删除重复索引:MySQL 允许在相同列上创建多个索引,重复索引需要单独维护。
**联合索引:**当单个索引字段查询数据很多,区分度都不是很大时,则需要考虑建立联合索引来提高查询效率
游标是处理数据的一种方法,为了查看或者处理结果集中的数据,游标提供了在结果集中一次一行或者多行前进或向后浏览数据的能力。可以把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。
① max 求最大值。② min 求最小值。③ count 统计数量。④ avg 求平均值。⑤ sum 求和。
1、主键是一种约束,唯一索引是一种索引,两者在本质上是不同的。
2、主键创建后一定包含一个唯一性索引,唯一性索引并不一定就是主键。
3、唯一性索引列允许空值,而主键列不允许为空值。
4、主键列在创建时,已经默认为空值++ 唯一索引了。
5、一个表最多只能创建一个主键,但可以创建多个唯一索引。
6、主键更适合那些不容易更改的唯一标识,如自动递增列、身份证号等。
7、主键可以被其他表引用为外键,而唯一索引不能
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
例:CREATE INDEX index_name ON table(column(10 or 20));
like是模糊查询,如果%在前面相当于是全表查询不使用索引,如果%在后面会用到索引
union会自动压缩多个结果集合中的重复结果,而union all则将所有的结果全部显示出来,不管是不是重复。
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
Union All:对两个结果集进行并集操作,包括重复行,不进行排序;
分区:就是把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的
数据分区是一种物理数据库的设计技术,它的目的是为了在特定的SQL操作中减少数据读写的总量以缩减响应时间。
分区并不是生成新的数据表,而是将表的数据均衡分摊到不同的硬盘,系统或是不同服务器存储介子中,实际上还是一张表。另外,分区可以做到将表的数据均衡到不同的地方,提高数据检索的效率,降低数据库的频繁IO压力值。
分页:当要显示的数据,一页显示不全,需要分页提交sql 请求
LIMIT offset,size
; offset 代表要显示条目的起始索引(起始索引从0 开始),size 代表要显示的条目个数
数据查询语言DQL:数据查询语言DQL 基本结构是由SELECT 子句,FROM 子句,WHERE 子句组成的查询块:
数据操纵语言DML:数据操纵语言DML 主要有三种形式:插入:INSERT、更新:UPDATE、删除:DELETE
数据定义语言DDL:数据定义语言DDL 用来创建数据库中的各种对象-----表、视图、索引、同义词、聚簇等如:
CREATE TABLE/VIEW/INDEX/SYN/CLUSTER 表/视图/索引/同义词/簇
DDL 操作是隐性提交的!不能rollback
数据控制语言DCL:数据控制语言DCL 用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,对数据库实行监视等。如:
GRANT:授权。
ROLLBACK [WORK] TO [SAVEPOINT]:回退到某一点。回滚—ROLLBACK;回滚命令使数据库状态回到上次最后提交的状态。其格式为:SQL>ROLLBACK;
COMMIT [WORK]:提交。
where:
having:
where 早于 group by 早于 having
where子句在聚合前先筛选记录,也就是说作用在group by 子句和having子句前,而 having子句在聚合后对组记录进行筛选
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。
SQL注入攻击的总体思路
1:寻找到SQL注入的位置
2:判断服务器类型和后台数据库类型
3:针对不同的服务器和数据库特点进行SQL注入攻击
如何防御SQL注入
但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。
1、检查变量数据类型和格式
2、过滤特殊符号
3、绑定变量,使用预编译语句
视图的可更新性和视图中查询的定义有关系,以下类型的视图是不能更新的。
包含以下关键字的sql语句:分组函数、distinct、group by、having、union或者union all
常量视图
Select中包含子查询
join
from一个不能更新的视图
where子句的子查询引用了from子句中的表
锁是网络数据库中的一个非常重要的概念,当多个用户同时对数据库并发操作时,会带来数据不一致的问题,所以,锁主要用于多用户环境下保证数据库完整性和一致性。
数据库锁出现的目的:处理并发问题
并发控制的主要采用的技术手段:乐观锁、悲观锁和时间戳。
锁分类
从数据库系统角度分为三种:排他锁、共享锁、更新锁。
从程序员角度分为两种:一种是悲观锁,一种乐观锁。
悲观锁(Pessimistic Lock):顾名思义,很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问或修改那部分数据
传统的关系数据库里用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
悲观锁按使用性质划分
共享锁(Share Lock):S锁,也叫读锁,用于所有的只读数据操作。共享锁是非独占的,允许多个并发事务读取其锁定的资源。性质:
排他锁(Exclusive Lock):X锁,也叫写锁,表示对数据进行写操作。如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了。性质:
更新锁:U锁,在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。性质:
悲观锁按作用范围划分为:行锁、表锁。行锁:锁的作用范围是行级别。表锁:锁的作用范围是整张表。
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以,不会上锁。但是在更新的时候会判断一下在此期间别人有没有更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
乐观锁的实现方式:版本号(version)、时间戳(使用数据库服务器的时间戳)待更新字段、所有字段
数据库并发需要使用事务来控制,事务并发问题需要数据库锁来控制,所以数据库锁是跟并发控制和事务联系在一起的。
并发操作会导致数据的不一致性,主要包括“丢失数据”,“不可重复读”,“读脏数据等。
还有就是,并发控制会造成活锁和死锁。
活锁指的是T1封锁了数据R,T2同时也请求封锁数据R,T3也请求封锁数据R,当T1释放了锁之后,T3会锁住R,T4也请求封锁R,则T2就会一直等待下去,这种处理方法就是采用“先来先服务”策略;
死锁就是我等你,你又等我,双方就会一直等待下去,比如:T1封锁了数据R1,正请求对R2封锁,而T2封住了R2,正请求封锁R1,这样就会导致死锁,死锁这种没有完全解决的方法,只能尽量预防,预防的方法有:①一次封锁发,指的是一次性把所需要的数据全部封锁住,但是这样会扩大了封锁的范围,降低系统的并发度;②顺序封锁发,指的是事先对数据对象指定一个封锁顺序,要对数据进行封锁,只能按照规定的顺序来封锁,但是这个一般不大可能的。
诊断和判断死锁有两种方法,一是超时法,二是等待图法。超时法就是如果某个事物的等待时间超过指定时限,则判定为出现死锁;等待图法指的是如果事务等待图中出现了回路,则判断出现了死锁。对于解决死锁的方法,只能是撤销一个处理死锁代价最小的事务,释放此事务持有的所有锁,同时对撤销的事务所执行的数据修改操作必须加以恢复。
不可以,所有select的字段,除聚合函数中的字段,都必须在group by中出现,例如:从表组中选择A,B,C,count(degreeD),sum(E),特殊的B,C,除了聚合函数count(D)和sum(E)之外,字段A、B和C必须出现在group by中。
考察sql的执行顺序,from ,where,group by ,having ,select,order by; 所以在select的时候如果非group by列,要自己聚合,比如count,sum,first_value之类的。如果不聚合是有问题的
常用的线性结构有 一维数组,链表,栈,队列,双队列,循环队列,串。
常见的非线性结构有二维数组,多维数组,广义表,树(二叉树等),图。
排序算法是稳定的 稳定是什么意思 哪些排序算法是稳定的 哪些需要的空间大??
稳定:冒泡排序、插入排序、归并排序。
不稳定:希尔排序、选择排序、堆、快速排序。
快速排序属于交换排序,是不稳定的排序算法。
首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。
快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,一趟时间复杂度 O(n),整个算法的时间复杂度与划分趟数有关。最好情况是每次划分选择的中间数恰好将当前序列等分,经过 log(n) 趟划分便可得到长度为 1 的子表,这样时间复杂度 O(nlogn)。最坏情况是每次所选中间数是当前序列中的最大或最小元素,这使每次划分所得子表其中一个为空表 ,这样长度为 n 的数据表需要 n 趟划分,整个排序时间复杂度 O(n²)。
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
插入排序的平均时间复杂度也是 O(n^2),空间复杂度为常数阶 O(1)
除最后一层无任何子节点外,每一层上的所有节点都有两个子节点的二叉树,第 n 层有 2n-1 个节点,n 层一共有 2n-1 个节点。
红黑树本质上是二叉查找树,额外引入了 5 个约束条件:① 节点只能是红色或黑色。② 根节点必须是黑色。③ 所有 NIL 节点都是黑色的。④ 一条路径上不能出现相邻的红色节点。⑤ 在任何递归子树中,根节点到叶子节点的所有路径上包含相同数目的黑色节点。这五个条件保证了红黑树增删查的最坏时间复杂度均为 O(logn)。红黑树的任何旋转在 3 次之内均可完成。
红黑树平衡性不如 AVL 树,它持的只是一种大致平衡,节点数相同的情况下,红黑树的高度可能更高,平均查找次数会高于 AVL 树。
在插入时,红黑树和 AVL 树都能在至多两次旋转内恢复平衡,在删除时由于红黑树只追求大致平衡,因此至多三次旋转可以恢复平衡,而 AVL 树最多需要 O(logn) 次。面对频繁地插入与删除红黑树更加合适。
单链表的最后一个元素的next为null,而循环链表的最后一个元素的next为第一个元素地址。循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。
head.next == head
B 树中每个节点同时存储 key 和 data,而 B+ 树中只有叶子节点才存储 data,非叶子节点只存储 key。InnoDB 对 B+ 树进行了优化,在每个叶子节点上增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的 B+ 树,提高区间访问的性能。
由于 B+ 树在非叶子节点上不含数据信息,因此在内存中能够存放更多的 key,数据存放得更紧密,利用率更高。
B+ 树的叶子节点都是相连的,对整棵树的遍历只需要对叶子节点进行一次线性遍历,而 B 树则需要每层递归遍历。
B 树的优点是,由于每个节点都包含 key 和 value,经常访问的元素可能离根节点更近,访问也更迅速。
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
给定 N 个权值构成 N 个节点,构建出一颗带权路径和最小的二叉树就是哈夫曼树。
Hash,也可以称为“散列”,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出(也就是多对一的关系)。
哈希表的构造
在所有的线性数据结构中,数组的定位速度最快,因为它可通过数组下标直接定位到相应的数组空间,就不需要一个个查找。而哈希表就是利用数组这个能够快速定位数据的结构解决以上的问题的。
"数组可以通过下标直接定位到相应的空间”,对就是这句,哈希表的做法其实很简单,就是把Key通过一 个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标 的数组空间里,而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分 利用到数组的定位性能进行数据定位。
链表长度大查询效率低怎么办
红黑树还会退化成链表嘛?
讲讲hash和红黑树,哪个效率高
hash怎么实现的 。你知道java中哪些地方用到hash和红黑树了吗
二叉树的前序遍历中序遍历后续遍历的叶子节点的先后顺序会发生改变吗?
不会,前序是:根左右;中序是:左根右;后序是:左右根。无论怎么遍历,叶子节点的次序都是左在前右在后。
数据结构,什么是同步什么是异步
同步:必须等对方回应我之后,我才会继续做下一件事情
一个任务依赖于另一个任务时,只有被依赖的任务完成,依赖任务才算完成。(要么都成功,要么都失败,是可靠的)
异步:不必等对方回应我,我继续做我的其他事情
不需要等待被依赖的任务完成,只需要通知被依赖任务要完成什么,依赖的任务自己完成,就算完成。(被依赖任务是否完成,依赖的任务不知道,所以是不可靠),被依赖任务通过,状态,通知和回调返回结果。
贪心算法是什么 应用场景有什么
求解一个问题时有多个步骤,每个步骤都选择当下最优的那个解,而不用考虑整体的最优解。通常,当我们面对的问题拥有以下特点的时候,就可以考虑使用贪心算法。应用场景例如分发糖果、找零钱、最多无重叠子区间(区间右边界升序排列)等
动态规划是什么?
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
重叠子问题、最优子结构、状态转移方程就是动态规划三要素。
应用场景:斐波那契数列、爬楼梯、最长递增子序列,最长公共子序列、矩阵最小路径和、背包问题等
数组的增删改查时间复杂度
字符串全排列,口述实现思想。 我答了递归回溯法,然后问了递归的流程,递归出口等等
① cookie 只能存储 ASCII 码,而 session 可以存储任何类型的数据。
② session 存储在服务器,而 cookie 存储在客户浏览器中,容易被恶意查看。。
③ session 的运行依赖 session id,而 session id 存在 cookie 中,叫做 JSESSIONID。如果浏览器禁用了 cookie ,同时 session 也会失效(可以通过其它方式实现,比如在 url 中传递 session_id)。
初始 A 和 B 均处于 CLOSED 状态,B 创建传输进程控制块 TCB 并进入 LISTEND 状态,监听端口是否收到连接请求。
A 向 B 发送连接请求报文,SYN=1,ACK=0,SYN 不可以携带数据,但要消耗一个序号,发送后 A 进入 SYN-SENT 同步已发送状态。
B 收到 A 的连接请求报文后,进入 SYN-RCVD 同步已接收状态,如果同意建立连接就会发送给 A 一个连接响应报文,SYN=1,ACK=1,ACK 可以携带数据,不携带的话则不消耗序号。
A 收到 B 的确认后还要对该确认再进行一次确认,发送后 A 进入 ESTABLISHED 状态,B 接收到该报文后也进入 ESTABLISHED 状态,客户端会稍早于服务器端建立连接。
三次握手的原因:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
参考链接
网络中对资源的需求超过可用量的情况就叫拥塞,当吞吐量明显小于理想吞吐量时就出现了轻度拥塞。拥塞控制就是减少注入网络的数据,减轻路由器和链路的负担,这是一个全局性问题,涉及网络中的所有路由器和主机,而流量控制是一个端到端的问题。
TCP 的拥塞控制算法包括了慢启动、拥塞避免和快重传、快恢复。慢启动和拥塞避免是 TCP 的强制部分,差异在于对收到的 ACK 做出反应时拥塞窗口增加的方式,慢启动比拥塞避免增加得更快。快恢复是推荐部分,对 TCP 发送方不是必须的。
慢启动:拥塞窗口 cwnd 以一个 MSS 最大报文段开始,每当传输的报文段首次被确认就增加一个 MSS。因此每经过一个 RTT 往返时间,拥塞窗口就会翻倍,发送速率也会翻倍。结束慢启动的情况:① 发生超时事件,发送方将 cwnd 设为 1,重新开始慢启动,并将慢启动阈值设置为 cwnd/2。② 当拥塞窗口达到慢启动阈值时就结束慢启动而进入拥塞避免模式。③ 如果检测到三个冗余的 ACK,TCP 就会执行快重传并进入快恢复状态。
拥塞避免:一旦进入拥塞避免状态,cwnd 值大约是上次拥塞时的 1/2,距离拥塞并不遥远。因此 TCP 不会每经过一个 RTT 就将 cwnd 翻倍,而是较为保守地在每个 RTT 后将 cwnd 加 1。发生超时事件时,拥塞避免和慢启动一样,将 cwnd 设为 1,并将慢启动阈值设置为 cwnd/2。
快恢复:有时个别报文段丢失,但网络中并没有出现拥塞,如果使用慢启动会降低传输效率。这时应该使用快重传来让发送方尽早知道出现了个别分组的丢失,快重传要求接收端不要等待自己发送数据时再捎带确认,而是要立即发送确认。即使收到了乱序的报文段也要立即发出对已收到报文段的重复确认。当发送方连续收到三个冗余 ACK 后就知道出现了报文段丢失的情况,会立即重传并进入快恢复状态。在快恢复中,会调整慢启动阈值为 cwnd/2,并进入拥塞避免状态。
滑动窗口以字节为单位。发送端有一个发送窗口,窗口中的序号是允许发送的序号,窗口的后沿是已发送且确认的序号,窗口的前沿是不允许发送的序号。窗口的后沿可能不动(没有收到新的确认),也有可能前移(收到了新的确认),但不会后移(不可能撤销已经确认的数据)。窗口的前沿一般是向前的,可能不动(没有收到新的请求或对方的接收窗口变小),也可能收缩(TCP 强烈不建议这么做,因为发送端在收到通知前可能已经发送了很多数据,将产生错误)。
① TCP 是面向连接的,发送数据前必须先建立连接,发送某些预备报文段;UDP 无连接,发送数据前不需要建立连接。
② TCP 连接是点对点的,只能是单个发送方和单个接收方之间的连接;UDP 支持一对一、一对多和多对多通信。
③ TCP 提供可靠的交付服务,通过 TCP 传送的数据无差错、不丢失、不重复,按序到达;UDP 使用尽最大努力交付,不保证可靠性,主机不需要维持复杂的连接状态。
④ TCP 是面向字节流的,TCP 不保证接收方的数据块和发送方的数据块具有对应大小的关系,但接收方的字节流必须和发送方的字节流完全一样。应用程序必须有能力识别收到的字节流,把它还原成应用层数据;UDP 面向报文,对应用层报文添加首部后就交付 IP 层。
⑤ TCP 有拥塞控制;UDP 没有拥塞控制,网络拥塞不会降低源主机的发送速率,这对某些实时应用很重要,如视频会议。
参考链接
校验和:如果接收方比对校验和与发送方不一致,那么数据一定传输有误。但是如果接收方比对校验和与发送方一致,数据不一定传输成功。
序列号:TCP传输时将每个字节的数据都进行了编号,这就是序列号
确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。序列号的作用不仅仅是应答的作用,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是TCP传输可靠性的保证之一。
超时重传:发送方没有介绍到响应的ACK报文原因可能有两点:
TCP在解决这个问题的时候引入了一个新的机制,叫做超时重传机制。简单理解就是发送方在发送完数据后等待一个时间,时间到达没有接收到ACK报文,那么对刚才发送的数据进行重新发送。如果是刚才第一个原因,接收方收到二次重发的数据后,便进行ACK应答。如果是第二个原因,接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送ACK应答。
连接管理:连接管理就是三次握手与四次挥手的过程,保证可靠的连接。
流量控制
拥塞控制
1、HTTPS 协议需要到 CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。
2、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
3、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
① 客户发送它支持的算法列表以及一个不重数。不重数就是在协议的生存期只使用一次的数,用于防止重放攻击,每个 TCP 会话使用不同的不重数,可以使加密密钥不同,重放记录无法通过完整性检查。
② 服务器从该列表中选择一种对称加密算法(例如 AES),一种公钥加密算法(例如 RSA)和一种报文鉴别码算法,然后把它的选择、证书,一个不重数返回给客户。
③ 客户通过 CA 提供的公钥验证证书,成功后提取服务器的公钥,生成一个前主密钥 PMS 并发送给服务器。
④ 客户和服务器独立地从 PMS 和不重数中计算出仅用于当前会话的主密钥 MS,然后通过 MS 生成密码和报文鉴别码密钥。此后客户和服务器间发送的所有报文均被加密和鉴别。
常见的加密算法可以分成三类,对称加密算法,非对称加密算法和Hash算法。
对称加密:指加密和解密使用相同密钥的加密算法,是可逆的。
优点:加密速度快
缺点:密钥的传递和保存是一个问题,参与加密和解密的双方使用的密钥是一样的,这样密钥就很容易泄露。
常见的对称加密算法:DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6和AES
非对称加密:指加密和解密使用不同密钥的加密算法,也称为公私钥加密,是可逆的(即可解密)。
优点:加密和解密的密钥不一致,公钥是可以公开的,只需保证私钥不被泄露即可,这样就密钥的传递变的简单很多,从而降低了被破解的几率。
缺点:加密速度慢
常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)
Hash算法
Hash算法特别的地方在于它是一种单向算法,用户可以通过Hash算法对目标信息生成一段特定长度的唯一的Hash值,却不能通过这个Hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。
常见的Hash算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1
301:网页永久移动到新位置,get或者head请求会自动将请求转移到新位置,post请求需要用户自己请求新的url,如果收藏为标签,会自动更新标签
302:临时移动,不会自动转移到新位置
304:自从上次请求后,网页没有修改过,不会返回网页内容,客户的缓存资源是最新的,要客户端使用缓存,节省带宽
401:未授权的操作
402: 不明
403:服务器拒绝客户端的请求
404:找不到请求的网页
5xx:服务器内部错误,500是服务器内部错误,无法完成请求,503,服务不可用,可能停机或者超载
总结:
2xx:请求成功
3xx:请求成功,重定向了,服务器内容移动
4xx:请求失败,客户端原因,可能是格式不正确
5xx:请求失败,服务器原因
死锁就是有两个或者多个进程由于竞争资源而造成阻塞的现象,如果无外力作用,这种局面就会一直持续下去。
**1、互斥条件:**指在一段时间内某资源只能由一个进程占用。(只有一副钥匙)
2、请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,且对自己已获得的其它资源保持不放。(拿着红钥匙的人在没有归还红钥匙的情况下,又索要蓝钥匙)
**3、不剥夺条件:**指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。(只要人不主动归还钥匙,就可以一直占着钥匙)
**4、环路等待条件:**指在发生死锁时,必然存在一个进程——资源的环形链。即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。(拿着红钥匙的人在等待蓝钥匙,而拿着蓝钥匙的人又在等待红钥匙)
要避免死锁只要破坏以上四个条件中的任意一个即可。
方法一:破坏互斥条件,在两个线程跑之前,能给每个线程单独拷贝一份钥匙的副本,就能有效的避免死锁了。如果系统拷贝那副钥匙的成本极高,而线程又很多的话,这种方法就不适用
方法二:破坏请求和保持条件
任何一个线程“贪心”,都可能会导致死锁。大致就是说有了一把钥匙还没还就要另一把。这里我们可以通过规定在任何情况下,一个线程获取一把钥匙之后,必须归还了钥匙之后才能请求另一把钥匙,就可以有效解决这个问题。
方法三:破坏不剥夺条件
除非线程自己还钥匙,否则线程会一直占有钥匙,是形成不可剥夺条件的原因。这里,我们可以通过设置一个”最长占用时间“的阈值来解决这个问题——如果过了10分钟仍然没有进入下一个步骤,则归还已有的钥匙。这样的话,两个线程都能取到所需的钥匙继续下去了。
方法四:破坏环路等待条件
会出现死锁的两两组合,一定都是一个线程先取了红钥匙而另一个线程先取了蓝钥匙,从而导致了可能形成了“环路等待”。所以我们可以强制规定任何线程取钥匙的顺序只能是 “先取蓝钥匙再取红钥匙”的话,就能避免死锁。
可以采用优先级调度,使用剥夺式。
① 先来先服务 FCFS,从后备队列选择最先进入的作业,调入内存。
② 短作业优先 SJF,从后备队列选择估计运行时间最短的作业,调入内存。平均等待时间、平均周转时间最少。
③ 优先级调度算法,分为非剥夺式和剥夺式。
④ 高响应比优先算法,综合了 FCFS 和 SJF,同时考虑了每个作业的等待时间和估计的运行时间。
⑤ 时间片轮转算法,遵循先来先服务原则,但是一次只能运行一个固定的时间片。
若干指令组成的程序段,用来实现某个特定功能,具有原子性,在执行过程中不可中断。P 是阻塞原语,将进程从运行态转为阻塞态,直到另一个进程唤醒它;V 是唤醒原语,将被阻塞的进程唤醒。
① 可执行状态,正在运行或等待运行。② 可中断的等待状态。③ 不可中断的等待状态。④ 停止状态。⑤ 终止状态(僵尸进程)。
① 进程控制块 PCB :进程存在的唯一标识,包括进程描述信息、控制信息、资源分配信息等。
② 程序段:能被进程调度到 CPU 执行的代码。
③ 数据段:进程对应的程序加工处理的原始数据。
tar。
ps。
cat、more、less。cat 一次性显示全部文件,more 是以页的形式查看。
① zip/unzip:压缩文件/解压缩,兼容 Linux 与 Windows,可以压缩多个文件或目录。
② gzip/gunzip:压缩文件/解压缩 gzip 文件,压缩单个文件,压缩率相对低,CPU 开销低。
③ xz/unxz:压缩/解压缩 xz 文件,压缩单个文件,压缩率高,时间相对长,解压快,CPU 开销高。
tail - n 10
vim 可以编辑文件内容,cat 只能查看。
ls,-t。
chmod
chmod 777 文件
chmod -R 777 文件夹
功能:将文件/文件夹的读写运行权限赋予给 文件/文件夹所有者(7) 、同组的用户(7)、其他组用户(7)
chmod 754 文件
chmod -R 754 文件夹
功能:文件/文件夹的读写运行权限赋予文件所有者,把读和运行的权限赋予群组用户,把读的权限赋予其他用户
操作文件或目录的用户,有3种不同类型:文件所有者、群组用户、其他用户。最高位表示文件所有者的权限值,中间位表示群组用户的权限值,最低位则表示其他用户的权限值,所以,chmod 777中,三个数字7分别对应上面三种用户,权限值都为7。
文件或目录的权限又分为3种:只读、只写、可执行。
权限 | 权限数值 | 二进制 | 具体作用 |
---|---|---|---|
r | 4 | 00000100 | read,读取。当前用户可以读取文件内容,当前用户可以浏览目录。 |
w | 2 | 00000010 | write,写入。当前用户可以新增或修改文件内容,当前用户可以删除、移动目录或目录内文件。 |
x | 1 | 00000001 | execute,执行。当前用户可以执行文件,当前用户可以进入目录。 |
依照上面的表格,权限组合就是对应权限值求和,如下:
7=4+2+1 读写运行权限
5=4+1 读和运行权限
4=4 只读权限
模式一:normal模式:作用主要是用来浏览,输入各种和在文档中移动。
模式二:编辑模式:用于对文件的编辑:
常用的插入命令:a在光标位置后编辑,i在光标位置前编辑,o在下一行插入;
A在光标所在行的末尾编辑,I在光标所在行的行头编辑,O在光标所处的上一行编辑。
模式三:命令模式
模式四:可视化模式
tail:
-n 是显示行号;相当于nl命令;例子如下:
tail -100f test.log 实时监控100行日志
tail -n 10 test.log 查询日志尾部最后10行的日志;
tail -n +10 test.log 查询10行之后的所有日志;
head:
跟tail是相反的,tail是看后多少行日志;例子如下:
head -n 10 test.log 查询日志文件中的头10行日志;
head -n -10 test.log 查询日志文件除了最后10行的其他所有日志;
cat:
cat -n test.log |grep “debug” 查询关键字的日志
https://www.runoob.com/w3cnote/linux-common-command-2.html
**ls命令:**就是 list 的缩写,通过 ls 命令不仅可以查看 linux 文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)查看目录信息等等。
**cd 命令:**cd(changeDirectory) 命令语法:cd [目录名]
pwd 命令:pwd 命令用于查看当前工作目录路径。
**mkdir 命令:**mkdir 命令用于创建文件夹。
rm 命令:删除一个目录中的一个或多个文件或目录,如果没有使用 -r 选项,则 rm 不会删除目录。如果使用 rm 来删除文件,通常仍可以将该文件恢复原状。
mv 命令:移动文件或修改文件名
cp 命令:将源文件复制至目标文件,或将多个源文件复制至目标目录。
查看磁盘空间命令:Linux 查看磁盘空间可以使用 df 和 du 命令。
文件查找命令:
find pathname -options [-print -exec -ok ...]
grep 命令:强大的文本搜索命令,grep(Global Regular Expression Print) 全局正则表达式搜索。
wc 命令:wc(word count)功能为统计指定的文件中字节数、字数、行数,并将统计结果输出
1、父组件通过props向下传递数据给子组件
2、当子组件需要向父组件传递数据时:在子组件中,通过$emit()来触发事件;在父组件中,通过v-on来监听子组件事件。
3、通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
4、当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。总的来说,keep-alive用于保存组件的渲染状态。
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
https://blog.csdn.net/zl834205311/article/details/81773511
XSS攻击全称跨站脚本攻击(Cross Site Scripting); CSRF(Cross-site request forgery)跨站请求伪造
css盒子模型 又称框模型 (Box Model) ,包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素。
在 W3C盒子模型(标准盒模型)中,width 和 height 指的是内容区域content的宽度和高度。
标准盒模型下盒子的大小 = content + border + padding + margin
IE盒子模型中,width 和 height 指的是内容区域content+border+padding的宽度和高度。
怪异盒模型下盒子的大小=width(content + border + padding) + margin
新建一个空对象,将this指向空对象的引用
设置通过this.声明的对象属性
返回this
知乎的解答:https://www.zhihu.com/question/28586791
没有副作用叫做幂等
这里指的是非ajax的浏览器请求
携带数据的格式不一样:浏览器发送get请求限定只能通过url的方式,所以浏览器发送get请求的方式是地址栏和a标签href,所以get的参数只能以key/value的形式放在url中进行传递,get不如post安全/要想安全还需要https,而post请求来自表单提交,表单数据被浏览器编码到body里,支持传递不同形式的数据
数据大小的限制:由于浏览器的url有长度限制,所以限制了get请求的数据大小有限制
幂等与否:get请求是幂等的,可以重复请求,没有副作用,post方法有副作用,form标签submit提交请求的时候是不幂等的,不能重复请求,所以不能做缓存,而get可以做缓存,不能把post保存为标签,尝试重新提交表单浏览器会提醒
试想一下,如果POST请求被浏览器缓存了,那么下单请求就可以不向服务器发请求,而直接返回本地缓存的“下单成功界面”,却又没有真的在服务器下单。
接口中的请求比较自由,没有浏览器那么多限制,所以有一些接口规范。但是没看出来浏览器实现表单提交的post和rest中创建资源的post有什么不同