Java面试宝典

一.Java 基础

1. Java 基础知识

1.1面向对象的特征有哪些方面

封装
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。

继承
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

多态性
多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
方法的重写Overriding 和重载Overloading 是Java 多态性的不同表现。重写Overriding 是父类与子类之间多态性的一种表现,重载Overloading 是一个类中多态性的一种表现。

1.2 String 和StringBuffer,StringBuilder 的区别是什么?String 为什么是不可变的?

可变性
String:类中使用final 关键字字符数组保存字符串, private final char value[] ,所以String 对象是不可变的
StringBuilder 与 StringBuffer :都继承自AbstractStringBuilder 类,在AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用final 关键字修饰,所以这两种对象都是可变的

线程安全性
String :中的对象是不可变的(因为有final修饰),也就可以理解为常量,线程安全
AbstractStringBuilder 是StringBuilder 与StringBuffer 的公共父类,定义了一些字符串的基本操作,如expandCapacity.append.insert.indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

对于三者使用的总结:
1.操作少量的数据= String
2.多线程操作字符串缓冲区下操作大量数据= StringBuffer
3.单线程操作字符串缓冲区下操作大量数据= StringBuilder

1.3 自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;

1.4 == 与equals

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址)

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖(重写)equals() 方法。则通过equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类覆盖了equals() 方法。一般,我们都覆盖equals() 方法来两个对象的内容相等;若它们的内容相等,则返回true (即,认为这两个对象相等)。

public class test1 {
	public static void main(String[] args) {
		String a = new String("ab"); // a 为一个引用
		String b = new String("ab"); // b 为另一个引用,对象的内容一样
		String aa = "ab"; // 放在常量池中
		String bb = "ab"; // 从常量池中查找
		if (aa == bb) // true
			System.out.println("aa==bb");
		if (a == b) // false,非同一对象
			System.out.println("a==b");
		if (a.equals(b)) // true
			System.out.println("aEQb");
		if (42 == 42.0) { // true
			System.out.println("true");
		}
	}
}

1.5 关于final 关键字的一些总结

final 关键字主要用在三个地方:变量、类、方法。

变量:对于一个final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

:当用final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为final 方法。

方法:使用final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java 实现版本中,会将final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java 版本已经不需要使用final 方法进行这些优化了)。类中所有的private 方法都隐式地指定为final。

1.6 Java 中的异常处理

在Java 中,所有的异常都有一个共同的祖先java.lang 包中的Throwable 类。Throwable: 有两个重要的子类:
在这里插入图片描述
Exception(异常) 和Error(错误) ,二者都是Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。
Exception(异常):是程序本身可以处理的异常。
异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。

🎈异常处理总结
try 块:用于捕获异常。其后可接零个或多个catch 块,如果没有catch 块,则必须跟一个fifinally 块。
catch 块:用于处理try 捕获到的异常。
fifinally 块:无论是否捕获或处理异常,fifinally 块里的语句都会被执行。当在try 块或catch 块中遇到return 语句时,fifinally 语句块将在方法返回之前被执行。

🧨在以下4 种特殊情况下,fifinally 块不会被执行:
1.在fifinally 语句块中发生了异常。
2.在前面的代码中用了System.exit()退出程序。
3.程序所在的线程死亡。
4.关闭CPU。

1.7 获取用键盘输入常用的的两种方法

方法1:通过Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();

方法2:通过BufffferedReader
BufferedReader input = new BufferedReader(new
InputStreamReader(System.in));
String s = input.readLine();

1.8 接口和抽象类的区别是什么

抽象类是对事物本身的抽象描述,接口是对行为方式的抽象描述

  1. 接口的方法默认是public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法
  2. 接口中的实例变量默认是final 类型的,而抽象类中则不一定
  3. 一个类可以实现多个接口,但最多只能实现一个抽象类
  4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
  5. 接口不能用new 实例化,但可以声明,但是必须引用一个实现该接口的对象从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

备注:在JDK8 中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。

1.9 重载和重写的区别

重载Overload: 发生在同一个类中,方法名必须相同参数类型不同.个数不同.顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写Override: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private 则子类就不能重写该方法。

1.10 int 和 Integer 有什么区别

Java 提供两种不同的类型:引用类型和原始类型(或内置类型)

Int 是java 的原始数据类型。
Integer 是java 为int 提供的封装类。

Java 为每个原始类型提供了封装类。

原始类型封装类
booleanBoolean
charCharacter
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble

引用类型和原始类型的行为完全不同,并且它们具有不同的语义。
引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。

1.11 final, finally, finalize 的区别

final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

finally :异常处理语句结构的一部分,表示总是执行。

finalize:是Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

1.12 Object 类的常见方法总结

Object 类是一个特殊的类,是所有类的父类。它主要提供了以下11 个方法:

方法1: public final native Class<?> getClass()
// native 方法,用于返回当前运行时对象的Class 对象,使用了final 关键字修饰,故不允许子类重写。

方法2public native int hashCode()
// native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

方法3public boolean equals(Object obj)
// 用于比较2 个对象的内存地址是否相等,String 类对该方法进行了重写用户比较字符串的值是否相等。

方法4protected native Object clone() throws CloneNotSupportedException
// naitive 方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象x,表达式x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object 本身没有实现Cloneable 接口,所以不重写clone 方法并且进行调用的话会发生CloneNotSupportedException 异常。

方法5public String toString()
// 返回类的名字@实例的哈希码的16 进制的字符串。建议Object 所有的子类都重写这个方法。

方法6public final native void notify()
// native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。

方法7public final native void notifyAll()
// native 方法,并且不能重写。跟notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

方法8public final native void wait(long timeout) throws InterruptedException
// native 方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而wait 方法释放了锁。timeout 是等待时间。

方法9public final void wait(long time, int nanos) throws InterruptedException
// 多了nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是0-999999)。所以超时的时间还需要加上nanos 毫秒。

方法10public final void wait() throws InterruptedException
// 跟之前的2 个wait 方法一样,只不过该方法一直等待,没有超时时间这个概念。

方法11protected void finalize() throws Throwable { }
// 实例被垃圾回收器回收的时候触发的操作。

1.13 栈、堆、方法区的区别

🎨1.栈内存中放哪些东西?
基本类型的变量,例如int a=3中的a;
对象的引用变量,例如Thread t=new Thread();中的t。
当在代码块中定义一个变量时,Java就在栈中为这个变量分配内存空间;当超过变量的作用域后,Java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

🎃2.堆内存中存放哪些东西?
存放由new创建的对象和数组
在堆中存放的内存,由Java虚拟机垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量持有的内容等于数组或者对象在堆内存中的首地址。在栈中的这个特殊的变量,就成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

🎁3.静态区/方法区
方法区(method)也叫做静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量
方法区中包含的都是在整个程序中永远唯一的元素,例如class,static变量。
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量放在相邻的另一块区域。

堆内存和栈内存,两者的区别?
①引用变量是普通变量,定义时在栈内存中分配,引用变量在程序运行到作用域外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放。数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉,这个也是Java比较占内存的主要原因。实际上,栈中的引用变量指向堆内存中的变量,这就是Java中的指针。
②通俗来讲,堆是用来存放对象的,而栈是用来执行程序的。
③jvm只有一个堆区(heap),被所有线程共享;
每个线程包含一个栈区(stack),每个栈中的数据都是私有的,其他的栈不能访问,但是同一个栈中变量是共享的;分为3个部分:基本类型变量区,执行环境上下文,操作指令区。

栈是一种线形集合,其添加和删除元素的操作应在同一段完成。栈按照后进先出的方式进行处理。
堆是栈的一个组成元素

为什么要有堆和栈?这样设计有什么好处?
①Java自动管理堆和栈,程序员不能直接地设置栈和堆。
②Java的堆是一个运行时数据区。堆是由JVM的垃圾回收器自动管理的。堆的优势是可以在程序运行时,动态地分配内存
大小,但是正是由于这个原因,它的存取速度较慢。
③栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小和生存期是必须确定的,缺乏灵活性。
栈有一个很重要的特性,就是存在栈中的数据可以共享。

小题

  1. &和&&的区别
    &运算符:两侧的表达式的结果均为真时,整个运算结果才为真。
    &&操作符:第一个表达式为 false时,结果为 false,并且不再计算第二个表达式
  2. short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
    short s1 = 1; s1 = s1 + 1; (错误,s1+1 运算结果是int 型,需要强制转换类型)
    short s1 = 1; s1 += 1;(正确
  3. Math.round(11.5)等于多少? Math.round(-11.5)等于多少?
    Math.round(11.5) == 12
    Math.round(-11.5) == -11
    round 方法返回与参数最接近的长整数,参数加1/2 后求其floor.
  4. String s = new String(“xyz”);创建了几个String Object?
    两个(一个是“xyx”,一个是指向“xyx”的引用对象s)
  5. 用最有效率的方法算出2 乘以8 等于几?
    2 << 3 (有C 背景的程序员特别喜欢问这种问题)
  6. 跳出循环for (int i = 0; i < 10; i++)
    braek:跳出本层循环,执行本层循环下面的语句。直接跳出for
    continue:终止本次循环,进入下一次循环。从i=1跳到i=2
  7. String s = new String(“xyz”);创建了几个String Object?
    两个(一个是“xyx”,一个是指向“xyx”的引用对象s)

2. Java 集合框架

2.1 ArrayList 与LinkedList 异同

1.是否保证线程安全: ArrayList 和LinkedList 都是不同步的,也就是不保证线程安全;

2.底层数据结构
ArrayList 底层使用的是Object 数组;
LinkedList 底层使用的是双向链表数据结构
(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别);

3.插入和删除是否受元素位置的影响
ArrayList :采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置i 插入和删除元素的话( add(int index, E element) )时间复杂度就为O(n-i)。因为在进行上述操作的时候集合中第i 和第i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
LinkedList :采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n)。

4.是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。

5.内存空间占用: ArrayList 的空间浪费主要体现在在list 列表的结尾会预留一定的容量空间,而LinkedList 的空间花费则体现在它的每一个元素都需要消耗比ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

2.2 HashMap 的底层实现

map.put(key1,value1):

  1. 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值(非常大)经过某种算法计算以后,得到在Entry数组中的存放位置。
    情况1:如果此位置上的数据为空,此时的key1-value1添加成功
  2. 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
    情况2:如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功
  3. 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
    情况3:如果equals()返回false:此时key1-value1添加成功
  4. 如果equals()返回true:使用value1替换value2。

在这里插入图片描述

2.3 HashMap 和Hashtable 的区别

线程是否安全
HashMap 是非线程安全的;
HashTable 是线程安全的;
HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用ConcurrentHashMap 吧!);

效率
因为线程安全的问题,HashMap 要比HashTable 效率高一点。
另外,HashTable 基本被淘汰,不要在代码中使用它;

对Null key 和Null value 的支持
HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。
HashTable 中put 进的键值只要有一个null,直接抛出NullPointerException。

初始容量大小和每次扩充容量大小的不同
①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
②创建时如果给定了容量初始值,那么Hashtable 会直接使用你给定的大小,而HashMap 会将其扩充为2 的幂次方大小(HashMap 中的tableSizeFor() 方法保证,下面给出了源代码)。也就是说HashMap 总是使用2 的幂作为哈希表的大小,后面会介绍到为什么是2 的幂次方。

底层数据结构:JDK1.8 以后的HashMap 在解决哈希冲突时有了较大
的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜
索时间。Hashtable 没有这样的机制。

2.4 HashSet 和HashMap 区别

如果你看过HashSet 源码的话就应该知道:HashSet 底层就是基于HashMap 实现的。

HashSet 的源码非常非常少,因为除了clone() 方法.writeObject()方法.readObject()方法是HashSet 自己不得不实现之外,其他方法都是直接调用HashMap 中的方法。
在这里插入图片描述

2.5 集合框架底层数据结构总结

🎈List
ArrayList: Object 数组
Vector: Object 数组
LinkedList双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

🧨Set
HashSet(无序,唯一): 基于HashMap 实现的,底层采用HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承于HashSet,并且其内部是通过LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于Hashmap 实现一样,不过还是有一点点区别的。
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

🎃Map
HashMapJDK1.8 之前HashMap 由数组+链表组成的,数组是HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap: LinkedHashMap 继承自HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:HashTable: 数组+链表组成的,数组是HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)

2.6 ArrayList 与Vector 区别

Vector 类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象。但是一个线程访问Vector 的话代码要在同步操作上耗费大量的时间。

ArrayList 不是同步的,所以在不需要保证线程安全时时建议使用ArrayList。

3. Java 多线程

3.1 说一说自己对于synchronized 关键字的了解

synchronized 关键字解决的是多个线程之间访问资源的同步性
synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

在Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。

庆幸的是在Java 6 之后Java 官方对从JVM 层面对synchronized 较大优化,所以现在的synchronized 锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

3.2 说说自己是怎么使用synchronized 关键字,在项目中用到了吗?

1.修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁

2.修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
也就是给当前类加锁,会作用于类的所有对象实例,
因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new 了多少个对象,只有一份,所以对该类的所有对象都加了锁)。
所以如果一个线程A 调用一个实例对象的非静态synchronized 方法,而线程B 需要调用这个实例对象所属类的静态synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized 方法占用的锁是当前类的锁,而访问非静态synchronized 方法占用的锁是当前实例对象锁。

3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
和synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到static 静态方法和synchronized(class)代码块上都是是给Class 类上锁。
这里再提一下:synchronized 关键字加到非static 静态方法上是给对象实例上锁。
另外需要注意的是:尽量不要使用synchronized(String a) 因为JVM 中,字符串常量池具有缓冲功能!

3.3 说说JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗?

JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

3.4 谈谈synchronized 和ReenTrantLock 的区别

(1)两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0 时才能释放锁。

(2) synchronized 依赖于JVM 而ReenTrantLock 依赖于API
synchronized 是依赖于JVM 实现的,前面我们也讲到了虚拟机团队在JDK1.6 为
synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是JDK 层面实现的(也就是API 层面,需要lock() 和unlock 方法配合try/fifinally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

(3)ReenTrantLock 比synchronized 增加了一些高级功能
相比synchronized,ReenTrantLock 增加了一些高级功能。
主要来说主要有三点:等待可中断;可实现公平锁; 可实现选择性通知(锁可以绑定多个条件)
ReenTrantLock 提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
ReenTrantLock 可以指定是公平锁还是非公平锁。而synchronized 只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReenTrantLock 默认情况是非公平的,可以通过ReenTrantLock 类的ReentrantLock(boolean fair) 构造方法来制定是否是公平的。synchronized 关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock 类当然也可以实现,但是需要借助于Condition 接口与newCondition() 方法。Condition 是JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock 对象中可以创建多个Condition 实例(即对象监视器),线程对象可以注册在指定的Condition 中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll()方法进行通知时,被通知的线程是由JVM选择的,用ReentrantLock 类结合Condition 实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition 接口默认提供的。而synchronized 关键字就相当于整个Lock 对象中只有一个Condition 实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition 实例的signalAll()方法只会唤醒注册在该Condition 实例中的所有等待线程。如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。

3.5 sleep() 和 wait() 有什么区别?

sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。

wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

3.6 请说出你所知道的线程同步的方法。

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM 确定唤醒哪个线程,而且不是按优先级。

Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

二. Java Web

1.JDBC 技术

1.1 说下原生JDBC 操作数据库流程?

第一步:Class.forName()加载数据库连接驱动
第二步:DriverManager.getConnection()获取数据连接对象;
第三步:根据SQL 获取sql 会话对象,有2 种方式Statement.PreparedStatement ;
第四步:执行SQL 处理结果集,执行SQL 前如果有参数值就设置参数值setXXX();
第五步:关闭结果集.关闭会话.关闭连接

1.2 说说事务的概念,在JDBC 编程中处理事务的步骤。

  1. 事务是作为单个逻辑工作单元执行的一系列操作。
  2. 一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性(ACID) 属性,只有这样才能成为一个事务处理步骤:
  3. conn.setAutoComit(false);设置提交方式为手工提交
  4. conn.commit()提交事务
  5. 出现异常,回滚conn.rollback();

1.3 JDBC 的脏读是什么?哪种数据库隔离级别能防止脏读?

当我们使用事务时,有可能会出现这样的情况,有一行数据刚更新,与此同
时另一个查询读到了这个刚更新的值。

这样就导致了脏读,因为更新的数据还没有进行持久化,更新这行数据的业务可能会进行回滚,这样这个数据就是无效的。

数据库的TRANSACTIONREADCOMMITTED ,TRANSACTIONREPEATABLEREAD 和TRANSACTION_SERIALIZABLE 隔离级别可以防止脏读

2.网路通讯部分

2.1 TCP 与UDP 区别?

🎨UDP:
1.是面向无连接, 将数据及源的封装成数据包中,不需要建立连接
2.每个数据报的大小在限制64k 内
3.因无连接,是不可靠协议
4.不需要建立连接,速度快

👓TCP
1.建立连接,形成传输数据的通道.
2.在连接中进行大数据量传输,以字节流方式
3.通过三次握手完成连接,是可靠协议
4.必须建立连接效率会稍低.聊天.网络视频会议就是UDP

2.2 说一下什么是Http 协议?

客户端和服务器端之间数据传输的格式规范,格式简称为“超文本传输协议”。
是一个基于请求与响应模式的.无状态的.应用层的协议,基于TCP 的连接方式。

2.3 get 与post 请求区别?

  1. get 重点在从服务器上获取资源,post 重点在向服务器发送数据
  2. get 传输数据是通过URL 请求,以field(字段)= value 的形式,置于URL 后,并用"?“连接,多个请求数据间用”&"连接,如http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的;
  3. post 传输数据通过Http 的post 机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的;
  4. Get 传输的数据量小,因为受URL 长度限制,但效率较高;
    Post 可以传输大量数据,所以上传文件时只能用Post 方式;
  5. Get 是不安全的,因为URL 是可见的,可能会泄露私密信息,如密码等;
  6. Post 较get 安全性较高;
  7. get 方式只能支持ASCII 字符,向服务器传的中文字符可能会乱码。
    post 支持标准字符集,可以正确传递中文字符。

2.4 http 中重定向和请求转发的区别?

本质区别:转发是服务器行为,重定向是客户端行为。

请求转发:一次强求,浏览器地址不变,访问的是自己本身的web 资源,传输的数据不会丢失。
重定向:两次请求,浏览器地址发生变化,可以访问自己web 之外的资源,传输的数据会丢失。

3. Cookie 和Session

Cookie 是web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个web 服务器存储cookie。以后浏览器再给特定的web 服务器发送请求时,同时会发送所有为该服务器存储的cookie。

Session 是存储在web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的Web 页之间跳转时,存储在Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。

Cookie 和session 的不同点:
1.无论客户端做怎样的设置,session 都能够正常工作。当客户端禁用cookie 时将无法使用cookie。
2.在存储的数据量方面:session 能够存储任意的java 对象,cookie 只能存储String 类型的对象。

4.Jsp 和Servlet

4.1 Servlet 的执行流程

Servlet 的执行流程也就是servlet 的生命周期,当服务器启动的时候生命周期开始,然后通过init()《启动顺序根据web.xml 里的startup-on-load 来确定加载顺序》方法初始化servlet,再根据不同请求调用doGet 或doPost 方法,最后再通过destroy()方法进行销毁。

4.2 doGet 和doPost 的区别

doGet 和doPost 都是接受用户请求的方法,doGet 处理get 请求,doPost处理post 请求,doGet 用于地址栏提交,doPost 用于表单提交,在页面提交数据时,get 的数据大小有限制4k,post 没有限制,get 请求提交的数据会在地址栏显示,post 不显示,所以post 比get 安全。

4.3 Jsp 和Servlet 的区别

你可以将JSP 当做一个可扩充的HTML 来对待。
虽然在本质上JSP 文件会被服务器自动翻译为相应的Servlet 来执行。
可以说Servlet 是面向Java 程序员而JSP 是面向HTML 程序员的,除此之外两者功能完全等价。

5. Ajax &Jquery

5.1 谈谈你对Ajax 的认识?

Ajax 是一种创建交互式网页应用的的网页开发技术;Asynchronous JavaScript and XML”的缩写。

通过异步模式,提升了用户体验。
优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用。
Ajax 引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。

Ajax 的最大特点:
可以实现局部刷新,在不更新整个页面的前提下维护数据,提升用户体验度。

三. 数据库

3.1 SQL 之连接查询

左连接(左外连接)以左表为基准进行查询,左表数据会全部显示出来,右表如果和左表匹配的数据则显示相应字段的数据,如果不匹配,则显示为NULL;

右连接(右外连接)以右表为基准进行查询,右表数据会全部显示出来,右表如果和左表匹配的数据则显示相应字段的数据,如果不匹配,则显示为NULL;

全连接就是先以左表进行左外连接,然后以右表进行右外连接。内连接:显示表之间有连接匹配的所有行。

3.2 SQL 之聚合函数

聚合函数是对一组值执行计算并返回单一的值的函数,它经常与SELECT语句的GROUP BY 子句一同使用。

  1. AVG 返回指定组中的平均值,空值被忽略;例:select prd_no,avg(qty) from sales group by prd_no
  2. COUNT 返回指定组中项目的数量。例:select count(*) from sales;
  3. MAX 返回指定数据的最大值;MIN 返回指定数据的最小值;SUM 返回指定数据的和,只能用于数字列,空值被忽略。例:select prd_no,max(qty) from sales group by prd_no;
  4. 使用group by 子句对数据进行分组;对group by 子句形成的组运行聚集函数计算每一组的值;最后用having 子句去掉不符合条件的组;having 子句中的每一个元素也必须出现在select 列表中。有些数据库例外,如oracle. 例:select prd_no,max(qty) from sales group by prd_no having prd_no>10

3.3 SQL 之SQL 注入

举例:
select admin from user where username=‘admin’ or ‘a’=‘a’ and pass='‘or ‘a’=‘a’
防止SQL 注入,使用预编译语句是预防SQL 注入的最佳方式,如select admin from user where username=?And password=?
使用预编译的SQL 语句语义不会发生改变,在SQL 语句中,变量用问号? 表示。像上面例子中,username 变量传递的’admin’ or ‘a’=‘a’ 参数,也只会当作username 字符串来解释查询,从根本上杜绝了SQL 注入攻击的发生。
注意:使用mybaits 时mapper 中#方式能够很大程度防止SQL 注入,$方式无法防止SQL 注入.

3.4 SQL Select 语句完整的执行顺序

查询中用到的关键词主要包含六个,并且他们的顺序依次为select–from–where–group by–having–order by。其中select 和from 是必须的,其他关键词是可选的

这六个关键词的执行顺序如下:
from:需要从哪个数据表检索数据
where:过滤表中数据的条件
group by:如何将上面过滤出的数据分组
having:对上面已经分组的数据进行过滤的条件
select:查看结果集中的哪个列,或列的计算结果
order by :按照什么样的顺序来查看返回的数据

3.5 存储引擎

概念
数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询. 更新和删除数据。不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。
存储引擎主要有: 1. MyIsam , 2. InnoDB, 3. Memory, 4. Archive, 5.Federated 。

3.6 索引

索引(Index)是帮助MySQL 高效获取数据的数据结构。

索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

常见的查询算法,顺序查找,二分查找,二叉排序树查找,哈希散列法,分块查找,平衡多路搜索树B树(B-tree)
MySQL 数据库几个基本的索引类型:普通索引、唯一索引、主键索引、全文索引、组合索引

索引的优点
创建唯一性索引,保证数据库表中每一行数据的唯一性
大大加快数据的检索速度,这也是创建索引的最主要的原因
加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。

索引的缺点
创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大
当对表中的数据进行增加.删除和修改的时候,索引也要动态的维护,降低了数据的维护速度

常见索引原则有
选择唯一性索引:唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
为经常需要排序.分组和联合操作的字段建立索引.
为常作为查询条件的字段建立索引。
限制索引的数目:越多的索引,会使更新表变得很浪费时间。
尽量使用数据量少的索引:如果索引的值很长,那么查询的速度会受到影响。
尽量使用前缀来索引:如果索引字段的值很长,最好使用值的前缀来索引。
删除不再使用或者很少使用的索引
最左前缀匹配原则,非常重要的原则。
尽量选择区分度高的列作为索引:区分度的公式是表示字段不重复的比例
索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。
尽量的扩展索引,不要新建索引。

3.6.1 普通索引

是最基本的索引,它没有任何限制。它有以下几种创建方式:
(1)直接创建索引

CREATE INDEX index_name ON table(column[length]))

(2)修改表结构的方式添加索引

ALTER TABLE table_name ADD INDEX index_name ON (column[length]))

(3)创建表的时候同时创建索引

CREATE TABLE `table` (
	`id` int(11) NOT NULL AUTO_INCREMENT ,
	`title` char(255) CHARACTER NOT NULL ,
	`content` text CHARACTER NULL ,
	`time` int(10) NULL DEFAULT NULL ,
	PRIMARY KEY (`id`),
	INDEX index_name (title[length])
)

(4)删除索引

DROP INDEX index_name ON table

3.6.2 唯一索引

与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
(1)创建唯一索引

CREATE UNIQUE INDEX indexName ON table(column[length])

(2)修改表结构

ALTER TABLE table_name ADD UNIQUE indexName ON (column[length])

(3)创建表的时候直接指定

CREATE TABLE `table` (
	`id` int(11) NOT NULL AUTO_INCREMENT ,
	`title` char(255) CHARACTER NOT NULL ,
	`content` text CHARACTER NULL ,
	`time` int(10) NULL DEFAULT NULL ,
	UNIQUE indexName (title[length])
);

3.6.3 主键索引

是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:

CREATE TABLE `table` (
	`id` int(11) NOT NULL AUTO_INCREMENT ,
	`title` char(255) NOT NULL ,
	PRIMARY KEY (`id`)
);

3.6.4 组合索引

指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合

ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);

3.6.5 全文索引

主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where 语句的参数匹配。fulltext 索引配合match against 操作使用,而不是一般的where语句加like。它可以在create table,alter table ,create index 使用,不过目前只有char.varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用CREATE index创建fulltext 索引,要比先为一张表建立fulltext 然后再将数据写入的速度快很多。
(1)创建表的适合添加全文索引

CREATE TABLE `table` (
	`id` int(11) NOT NULL AUTO_INCREMENT ,
	`title` char(255) CHARACTER NOT NULL ,
	`content` text CHARACTER NULL ,
	`time` int(10) NULL DEFAULT NULL ,
	PRIMARY KEY (`id`),
	FULLTEXT (content)
);

(2)修改表结构添加全文索引

ALTER TABLE article ADD FULLTEXT index_content(content)

(3)直接创建全文索引

CREATE FULLTEXT INDEX index_content ON article(content)

3.7 数据库三范式

范式是具有最小冗余的表结构。

3.8 数据库事务

事务
(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行.要么都不执行。事务是一个不可分割的工作逻辑单元事务必须具备以下四个属性,简称ACID 属性:
原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。
一致性(Consistency):当事务完成时,数据必须处于一致状态。
隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。
持久性(Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性。

事务的四种隔离级别

  1. Read uncommitted 读未提交
    就是一个事务可以读取另一个未提交事务的数据。
  2. Read committed 读已提交
    就是一个事务要等另一个事务提交后才能读取数据。
  3. Repeatable read 可重复读
    就是在开始读取数据(事务开启)时,不再允许修改操作
  4. Serializable 序列化
    Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读.不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

在MySQL 数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读)
而在Oracle 数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed 级别。

3.9 存储过程

一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。

存储过程优化思路:

  1. 尽量利用一些SQL 语句来替代一些小循环,例如聚合函数,求平均函数等。
  2. 中间结果存放于临时表,加索引。
  3. 少使用游标。SQL 是个集合语言,对于集合运算具有较高性能。而cursors 是过程运算。比如对一个100 万行的数据进行查询。游标需要读表100 万次,而不使用游标则只需要少量几次读取。
  4. 事务越短越好。SQLserver 支持并发操作。如果事务过多过长,或者隔离级别过高,都会造成并发操作的阻塞,死锁。导致查询极慢,cpu 占用率极地。
  5. 使用try-catch 处理错误异常。
  6. 查找语句尽量不要放在循环内。

3.10 数据库并发策略

并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳。
乐观锁
乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;时间戳就是不加锁,通过时间戳来控制并发出现的问题。
悲观锁
悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问那部分数据。
时间戳
时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁机制,但是这种方法可以大大提高数据库处理的并发量,以上悲观锁所说的加“锁”,其实分为几种锁,分别是:排它锁(写锁)和共享锁(读锁)。

两种锁的使用场景
多读乐观,多写悲观
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

乐观锁常见的两种实现方式

  1. 版本号机制
    一般是在数据表中加上一个数据版本号version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程A 要更新数据值时,在读取数据的同时也会读取version 值,在提交更新时,若刚才读取到的version 值为当前数据库中的version 值相等时才更新,否则重试更新操作,直到更新成功。
  2. CAS 算法即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
    CAS 算法涉及到三个操作数
    需要读写的内存值V
    进行比较的值A
    拟写入的新值B
    当且仅当V 的值等于A 时,CAS 通过原子方式用新值B 来更新V 的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

乐观锁的缺点

  1. ABA 问题
    如果一个变量V 初次读取的时候是A 值,并且在准备赋值的时候检查到它仍然是A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS 操作就会误认为它从来没有被修改过。这个问题被称为CAS 操作的"ABA"问题。
    JDK 1.5 以后的AtomicStampedReference 类就提供了此种能力,其中的compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
  2. 循环时间长开销大
    自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU 带来非常大的执行开销。如果JVM 能支持处理器提供的pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU 流水线被清空(CPU pipeline flush),从而提高CPU 的执行效率。
  3. 只能保证一个共享变量的原子操作
    CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时CAS 无效。但是从JDK 1.5 开始,提供了AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS 操作.所以我们可以使用锁或者利用AtomicReference 类把多个共享变量合并成一个共享变量来操作。

3.11 分区分表

分库分表有垂直切分和水平切分两种。

垂直切分:将表按照功能模块.关系密切程度划分出来,部署到不同的库上。例如,我们会建立定义数据库workDB.商品数据库payDB.用户数据库userDB.日志数据库logDB 等,分别用于存储项目数据定义表.商品定义表.用户数据表.日志数据表等。
水平切分:当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如userID 散列,进行划分,然后存储到多个结构相同的表,和不同的库上。例如,我们的userDB 中的用户数据表中,每一个表的数据量都很大,就可以把userDB 切分为结构相同的多个userDB:part0DB.part1DB 等,再将userDB 上的用户数据表userTable,切分为很多userTable:userTable0.userTable1 等,然后将这些表按照一定的规则存储到多个userDB上。

3.12 应该使用哪一种方式来实施数据库分库分表,这要看数据库中数据量的瓶颈所在,并综合项目的业务类型进行考虑。

如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰. 低耦合,那么规则简单明了.容易实施的垂直切分必是首选。
如果数据库中的表并不多,但单表的数据量很大.或数据热度很高,这种情况之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估,考虑数据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。在现实项目中,往往是这两种情况兼而有之,这就需要做出权衡,甚至既需要垂直切分,又需要水平切分。我们的游戏项目便综合使用了垂直与水平切分,我们首先对数据库进行垂直切分,然后,再针对一部分表,通常是用户数据表,进行水平切分。

单库多表
随着用户数量的增加,user 表的数据量会越来越大,当数据量达到一定程度的时候对user 表的查询会渐渐的变慢,从而影响整个DB 的性能。如果使用MySQL, 还有一个更严重的问题是,当需要添加一列的时候,MySQL 会锁表, 期间所有的读写操作只能等待。
可以将user 进行水平的切分,产生两个表结构完全一样的user_0000,user_0001 等表,user_0000 + user_0001 + …的数据刚好是一份完整的数据。
多库多表
随着数据量增加也许单台DB 的存储空间不够,随着查询量的增加单台数据库服务器已经没办法支撑。这个时候可以再对数据库进行水平区分。分库分表规则举例: 通过分库分表规则查找到对应的表和库的过程。如分库分表的规则是user_id 除以4 的方式,当用户新注册了一个账号,账号id的123,我们可以通过id 除以4 的方式确定此账号应该保存到User_0003表中。当用户123 登录的时候,我们通过123 除以4 后确定记录在User_0003 中。

3.13 MySQL 读写分离

在实际的应用中,绝大部分情况都是读远大于写。MySQL 提供了读写分离的机制,所有的写操作都必须对应到Master,读操作可以在Master 和Slave机器上进行,Slave 与Master 的结构完全一样,一个Master 可以有多个Slave,甚至Slave 下还可以挂Slave,通过此方式可以有效的提高DB 集群的每秒查询率. 所有的写操作都是先在Master 上操作,然后同步更新到Slave上,所以从Master 同步到Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。

此外,可以看出Master 是集群的瓶颈,当写操作过多,会严重影响到Master 的稳定性,如果Master 挂掉,整个集群都将不能正常工作。所以,

  1. 当读压力很大的时候,可以考虑添加Slave 机器的分式解决,但是当Slave机器达到一定的数量就得考虑分库了。
  2. 当写压力很大的时候,就必须得进行分库操作。

3.14 MySQL 常用SQL 查询语句优化方法

  1. 应尽量避免在where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
  2. 对查询进行优化,应尽量避免全表扫描,首先应考虑在where 及orderby 涉及的列上建立索引。
  3. 应尽量避免在where 子句中对字段进行null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num is null。可以在num 上设置默认值0,确保表中num 列没有null 值,然后这样查询:select id from t where num=0
  4. 尽量避免在where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or num=20。
    可以这样查询:
    select id from t where num=10
    union all
    select id from t where num=20

3.15 SQL优化

1.优化说明
(1)有数据表明,用户可以承受的最大等待时间为8 秒。数据库优化策略有很多,设计初期,建立好的数据结构对于后期性能优化至关重要。因为数据库结构是系统的基石,基础打不好,使用各种优化策略,也不能达到很完美的效果
(2)数据库优化的几个方面可以看出来,数据结构.SQL.索引是成本最低,且效果最好的优化手段。
在这里插入图片描述

(3)性能优化是无止境的,当性能可以满足需求时即可,不要过度优化。

2.优化方向
(1)SQL 以及索引的优化
首先要根据需求写出结构良好的SQL,然后根据SQL 在表中建立有效的索引。但是如果索引太多,不但会影响写入的效率,对查询也有一定的影响。
(2)合理的数据库是设计
根据数据库三范式来进行表结构的设计。设计表结构时,就需要考虑如何设计才能更有效的查询。

3.具体优化手段:

  1. 尽量少用(或者不用)sqlserver 自带的函数
    select id from t where substring(name,1,3) = ’abc’
    select id from t where datediff(day,createdate,’2005-11-30′) = 0
    可以这样查询:
    select id from t where name like ‘abc%’
    select id from t where createdate >= ‘2005-11-30’ and createdate < ‘2005-12-1’
  2. 连续数值条件,用BETWEEN 不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
  3. Update 语句,如果只更改1.2 个字段,不要Update 全部字段,否则频繁调用会引起明显的性能消耗
  4. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型
  5. 不建议使用select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理
  6. 表与表之间通过一个冗余字段来关联,要比直接使用JOIN 有更好的性能
1.使用Join:
过程:(产生笛卡尔成绩==on过滤==》添加外部行 ) *2==where过滤  ==select显示
select * from tag
join tag_post on tag_post.tag_id = tag.id 
join post on tag_post.post_id = post.id
where tag.tag=’mysql’;
 
2.不使用join:
过程:(where过滤 ==》网络返回给应用处理 )  *3Select * from tag where tag=’mysql’;
Select * from tag_post where tag_id=1234;
Select * from post where id in(123,456,567,9989,8909);
  1. select count(*) from table;这样不带任何条件的count 会引起全表扫描

连接池调优
我们的应用为了实现数据库连接的高效获取.对数据库连接的限流等目的,通常会采用连接池类的方案,即每一个应用节点都管理了一个到各个数据库的连接池。随着业务访问量或者数据量的增长,原有的连接池参数可能不能很好地满足需求,这个时候就需要结合当前使用连接池的原理.具体的连接池监控数据和当前的业务量作一个综合的判断,通过反复的几次调试得到最终的调优参数。

合理使用索引
索引一般情况下都是高效的。但是由于索引是以空间换时间的一种策略,索引本身在提高查询效率的同时会影响插入.更新.删除的效率,频繁写的表不宜建索引。
选择合适的索引列,选择在where,group by,order by,on 从句中出现的列作为索引项,对于离散度不大的列没有必要创建索引。主键已经是索引了,所以primay key 的主键不用再设置unique 唯一索引

四. SpringMVC 框架

4.1 什么是SpringMVC ?简单介绍下你对SpringMVC的理解?

SpringMVC 是一个基于Java 的实现了MVC 设计模式的请求驱动类型的轻量级Web 框架,通过把Model,View,Controller 分离,将web 层进行职责解耦,把复杂的web 应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。

六. Mybatis 框架

6.1 #{}和${}的区别是什么?

#{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将sql 中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL 注入,提高系统安全性。

6.2 Mybatis 是如何进行分页的?分页插件的原理是什么?

Mybatis 使用RowBounds 对象进行分页,它是针对ResultSet 结果集执行的内存分页,而非物理分页。可以在SQL 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect 方言,添加对应的物理分页语句和物理分页参数。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hvitur

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值