基础一
JVM-内存区域分配
所有线程的共享数据区:
方法区:存储每一个类的结构信息 运行时常量池
堆区:最大 大部分类实例、对象、数组
每一个线程的一块私有数据区:
虚拟机栈:存储局部变量、操作数栈、动态链接、方法返回地址 为执行java方法服务
本地方法栈:功能与虚拟机栈类似,为native方法服务
pc寄存器:存放当前正在执行的字节码指令的地址
HotSpot 对象创建
-
类加载检查
-
分配内存
2.1 内存分配的方式:
指针碰撞:要求内存规整 标记-整理
空闲列表: 堆内存不规整情况 标记-清除
2.2 内存分配并发:
CAS+失败重试
TLAB:预先在 Eden 区分配内存,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配 -
初始化零值
-
设置对象头 类的元数据信息、对象的哈希码、对象的 GC 分代年龄
-
执行 init 方法
对象包含对象头,实例数据,对齐填充
只要使用new方法,便需要创建新的对象
String
直接使用双引号声明出来的 String 对象会直接存储在常量池中。
使用new创建的字符串对象在堆内存分配。
String str3 = str1 + str2 会在堆内存上创建新的对象 String str4 = “string” 存储在常量池中
System.out.println(str3 == str4);//false
JVM-类加载机制
1加载-2验证-3准备(内存初始化)-4解析(将常量池的符号引用替换为直接引用,符号引用是用一组符号来描述所引用的目标,直接引用是指向目标的指针)-5初始化(执行类构造器、类变量赋值、静态语句块)
JVM-内存分配(堆上的内存分配)
-
新生代:
进入条件:优先选择在新生代的Eden区被分配 -
老年代:
进入条件:
大对象经过第一次MinorGC仍存在,能被Survivor容纳
如果Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象进入老年代
如果Survivor空间无法容纳新生代中Minor GC之后还存活的对象
JVM-GC回收机制
对象优先在eden区分配、大对象直接进入老年代、长期存活的对象将进入老年代
对象计数器:对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁。当年龄增加到一定程度(15岁),晋升到老年代
引用计数器 回收对象:每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的,表示对象已死亡
可达性分析 回收对象:通过一系列的GC Roots的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时则此对象是不可用的。
如何回收:新生代使用复制算法,老年代使用标记-清理或者标记-整理
复制算法:将可用内存按容量划分为Eden、from survivor、to survivor,分配的时候使用Eden和一个survivor,Minor GC后将存活的对象复制到另一个survivor,然后将原来已使用的内存一次清理掉。这样没有内存碎片。
标记-清除:首先标记出所有需要回收的对象,标记完成后统一回收被标记的对象。会产生大量碎片,导致无法分配大对象从而导致频繁GC。
标记-整理:首先标记出所有需要回收的对象,让所有存活的对象向一端移动。
分代收集:新生代使用复制算法,老年代使用标记-清除或标记-整理算法
新生代GC(Minor GC)发起条件:当Eden区不足以继续分配对象
老年代GC(Full GC)条件:
1、调用System.gc
2、老年代空间不足
3、方法区空间不足
并发:有可能交替执行
JVM-垃圾收集器
串行收集器:一个线程,慢 并行收集器:可控的吞吐量,最大停顿时间(复制,标记-整理)
并发收集器:以最短停顿时间为目标 精确控制停顿时间且垃圾回收效率最高
CMS针对老年代,有初始标记、并发标记、重新标记、并发清除四个过程,标记阶段会Stop The World,使用标记-清除算法,所以会产生内存碎片。
Serial 收集器:串行 新生代使用复制算法,老年代使用标记-整理算法
ParNew 收集器: Serial的多线程版本
Parallel Scavenge 收集器:复制算法的多线程收集器 吞吐量(高效率的利用 CPU) 提供很多参数供用户找到最合适的停顿时间或最大吞吐量
CMS(Concurrent Mark Sweep)收集器:一种以获取最短回收停顿时间为目标的收集器。 并发,让垃圾收集和用户线程同时工作
G1收集器:面向服务器 并行与并发 分代收集
JVM指令重排序
重排序类型
- 编译器优化的重排序:编译器在不改变单线程程序语义的前提下(代码中不包含synchronized关键字),可以重新安排语句的执行顺序。
- 指令级并行的重排序:采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
- 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
重排序可能会导致多线程程序出现内存可见性问题。
基础二
数据类型
缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123)会使用缓存池中的对象,多次调用会取得同一个对象的引用。
编译器会在自动装箱过程中调用valueOf方法,多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,会引用相同对象
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
String
不可变:String参数不可变,线程安全
String Pool
字符串常量池:保存字符串字面量
String s = new String(“abc”):一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
抽象类和接口
抽象类:abstract 抽象类不能被实例化,需要继承抽象类才能实例化其子类
接口:接口的字段默认都是 static 和 final 的。接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
容器
List
- Arraylist: 数组 增删慢查询快 自动扩容 1.5倍 线程不安全
- Vector: 线程安全
- LinkedList: 双向链表 增删快查询慢
- CopyOnWriteArrayList:线程安全 适合读多写少的场景 读取时不加锁
- ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
- BlockingQueue:通过链表、数组等方式实现,阻塞队列,适合用作数据共享的通道
Map
- HashMap: 1.8以前数组+链表 1.8之后红黑树 多线程Put操作时会出现覆盖 支持一个Null Key 容器默认大小为16 负载因子为0.75 当size>16*0.75时发生扩容
- ConcurrentHashMap:
- Segment数组结构和HashEntry数组结构,包含若干个Segment对象组成的数组 某个Segment对象守护整个散列表的若干个桶每一个桶是由若干个HashEntry对象连接起来的链表;最大并发个数为桶数(默认16),加锁的时候就是锁住整个segment
- 1.8之后直接使用Node数组+链表+红黑树,并发控制使用synchronized和CAS操作
- LinkedHashMap:于拉链式散列结构即由数组和链表或红黑树组成 双向
- Hashtable:线程安全,尽量使用CurrentHashMap保证线程安全,同一把锁,锁住整个结构,竞争激烈 数组+链表
- TreeMap: 红黑树(自平衡的排序二叉树)
- ConcurrentSkipListMap:跳表的实现,进行快速查找
HashMap
原理
数组结构,新建一个hashmap,初始化一个数组 transient Entry[] table;数组中的元素持有指向下一个元素的引用,构成链表。
当我们往hashmap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素
public V put(K key, V value){
return putVal(hash(Key),key,value, false, true);
}
static final int hash(Object key){
int h;
return (key == null?)0:(h= key.hashCode() ^ (h>>>16))
}
将其不带符号的右移16位,再与key的hashcode做异或运算
Set
- HashSet:无序,唯一,基于HashMap实现,先计算HashCode,判断是否有重复值,存在重复值调用equals判断是否真的相同
- LinkedHashSet: LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现的
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
定制排序用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
重写compareTo方法实现按年龄来排序
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* TODO重写compareTo方法实现按年龄来排序
*/
@Override
public int compareTo(Person o) {
if (this.age > o.getAge()) {
return 1;
} else if (this.age < o.getAge()) {
return -1;
}
return age;
}
}
泛型
什么是泛型?为什么要使用泛型?
参数化类型 定义时使用参数化,使用时传入具体类型
特性
只在编译阶段有效
泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
泛型的使用
泛型类
public class GenericClass {
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public static class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
public static class Service{
public int a;
public float b;
public String c;
public Service(int a, float b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
}
public static void main(String[]args){
Generic<Integer> a = new Generic<Integer>(new Integer(2));
Generic<String> b = new Generic<String>("2");
System.out.println(a.getKey()+" "+b.getKey());
Generic g1 = new Generic(1);
Generic g2 = new Generic("1");
Generic g3 = new Generic(3.44);
Service service= new Service(1,2,"对象");
Generic g4 = new Generic(service);
System.out.println(g1.getKey()+" "+g2.getKey()+" "+g3.getKey()+" "+((Service)g4.getKey()).getC());
}
}
泛型接口
public interface Generator<T> {
public T next();
}
public class AnimalGenerator implements Generator<String>{
private String[]animals = new String[]{"cat","dog"};
@override
public String next(){
Random rand = new Random();
return animals[rand.nextInt[3]];
}
}
泛型通配符
一般使用?代替具体的类型实参,不是类型形参,可以吧?看成所有类型的父类
当具体类型不确定时使用
Generic<Integer> gg = new Generic<>(3);
Generic<Number> nn = new Generic<>(4);
showValue(gg);
showValue(nn);
public static void showValue(Generic<?>obj){
System.out.println(obj.getKey());
}
泛型方法
泛型类在实例化类时指明泛型的具体类型;泛型方法在调用方法时指明泛型的具体类型。
public <T> T genericMethod(){}
public <T> void show_T(T t){
Systerm.out.println(t.toString());
}
如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法:
public class StaticGenerator<T>{
public static <T> void show(T t){
}
}
泛型数组
//定义
List<String>[] ls1 = new ArrayList[10];
List<?>[] ls = new ArrayList<?>[10];
List<Integer> integers= new ArrayList<>();
integers.add(11);
ls[0] = integers;
System.out.println(ls[0].get(0));
Integer i = (Integer) ls[0].get(0); //需要做显式的类型转换
String i = (String) ls[0].get(0); //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
反射机制
什么是反射
运行状态,对于任意一个类(对象)都能知道(调用)这个类的所有属性和方法动态获取信息以及动态调用对象方法的功能
用途
遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。
反射机制的相关类
- Class类:代表类的实体,表示类和接口
- getClassLoader():获取类的加载器
- getClasses():返回一个数组,包含该类所有公共类和接口类的对象
- getDeclaredClasses():返回一个数组,数组中包含该类中所有类和接口类的对象
- getName(): 获得类的完整路径名字
- getField(String name): 获得某个公有的属性对象
- getFields(): 获得所有公有的属性对象
- getDeclaredField(String name):获取某个属性对象
- getDeclaredFields(): 获得所有属性对象
- getMethod(String name, Class…<?> parameterTypes):获得该类某个公有的方法
- getMethods():获得该类所有公有的方法
- getDeclaredMethod(String name, Class…<?> parameterTypes):获得该类某个方法
- getDeclaredMethods():
- FIeld类:代表类的成员变量
- equals(Object obj) :属性与obj相等则返回true
- get(Object obj) 获得obj中对应的属性值
- set(Object obj, Object value) 设置obj中对应属性值
- Method类:代表类的方法
- invoke(Object obj, Object… args) 传递object对象及参数调用该对象对应的方法
- Constructor类:代表类的构造方法
- getConstructor(Class…<?> parameterTypes) :获得该类中与参数类型匹配的公有构造方法
- getConstructors(): 获得该类的所有公有构造方法
使用反射
获取Class对象
public class fanshe {
public static class Demo{
public int a;
public int b;
}
public static void main(String[]args){
Demo demo = new Demo();
// 第一种方式
Class demoClass = demo.getClass();
System.out.println(demoClass.getName());
//第二种方式
Class demoClass1 = Demo.class;
System.out.println(demoClass1.getName() == demoClass.getName());
// 第三种方式
try {
Class demoClass2 = Class.forName("interview.fanshe$Demo");
System.out.println(demoClass2.getName() == demoClass1.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
获取构造函数
public static class Demo{
public int a;
public int b;
public Demo(){}
private Demo(int a){
this.a=a;
}
public Demo(int a,int b){
this.a = a;
this.b = b;
}
}
import java.lang.reflect.Constructor;
Constructor[] array = demoClass2.getConstructors();//
System.out.println("获取所有公有构造方法"+array);
Constructor[] array1 = demoClass2.getDeclaredConstructors();//获取所有的构造方法(包括:私有、受保护、默认、公有)
System.out.println("获取所有的构造方法(包括:私有、受保护、默认、公有)"+array1);
try {
Constructor array2 = demoClass2.getConstructor(null);//获取公有、无参的构造方法
System.out.println("获取公有、无参的构造方法"+array2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
Constructor array3 = demoClass2.getDeclaredConstructor(int.class);
System.out.println("获取私有构造方法"+array3);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
获取成员变量
获取成员变量并调用
import java.lang.reflect.Field;
try {
Object obj = demoClass2.getConstructor().newInstance();
Field f = demoClass2.getField("a");
f.set(obj,1);
Demo demo0 = (Demo) obj;
System.out.println(demo0.a);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
Field[] fields = demoClass2.getFields();//获取所有公有的字段
for(Field f : fields){
System.out.println(f);
}
fields = stuClass.getDeclaredFields();//获取所有的字段(包括私有、受保护、默认的)
获取成员方法
public static class Demo{
public int a;
public int b;
public Demo(){}
private Demo(int a){
this.a=a;
}
public Demo(int a,int b){
this.a = a;
this.b = b;
}
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
private String show2(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = \" + "+age);
return "abcd";
}
}
import java.lang.reflect.Method;
Method[] methodArray = stuClass.getMethods();
Method methods[] = demoClass2.getMethods();//获取所有的”公有“方法
for(Method m : methods){
System.out.println(m);
}
try{
methods = demoClass2.getDeclaredMethods();//获取所有的方法,包括私有的
Method m = demoClass2.getMethod("show1", String.class);//获取公有的show1()方法*
System.out.println(m);
Method m1 = demoClass2.getDeclaredMethod("show2", int.class);//获取私有的show2()方法*
m1.setAccessible(true);//解除私有限定
Object obj = demoClass2.getConstructor().newInstance();
Object result = m1.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
反射Main方法
import java.lang.reflect.Method;
try{
Class demoClass = Class.forName("interview.fanshe$Demo");
Method methodMain = demoClass .getMethod("main", String[].class);
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
}catch (Exception e) {
e.printStackTrace();
}
反射运行配置文件内容
public class Demo{
public void show(){
System.out.println("this is show mehod");
}
}
配置文件pro.txt内容;
className = interview.fanshe$Demo
methodName = show
反射执行
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class demoClass = Class.forName(getValue("className"));
//2获取show()方法
Method m = demoClass .getMethod(getValue("methodName"));
//3.调用show()方法
m.invoke(demoClass .getConstructor().newInstance());
}
public static String getValue(String key) throws IOException{
Properties pro = new Properties();
FileReader in = new FileReader("pro.txt")
pro.load(in);
in.close();
return pro.getProperty(key);
}
多线程
状态:新建-就绪-运行-阻塞-就绪-运行-死亡 start() wait() notify() notifyall()
实现:Thread Runnable Callable
线程池的作用:线程创建和销毁的开销大,放回线程池有效利用
线程间通信的方式:
- 等待通知机制 wait() notify() join() interrupted()
- 并发工具: synchronized lock CountDownLatch semaphore CyclicBarrier
锁
定义: 不同线程资源竞争下分配不同线程执行方式的同步工具 获得锁才能访问同步代码
synchronized
保证synchronized修饰的方法和代码块任意时刻只能一个线程执行
原理:
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令, monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置;锁计数器用于锁处于释放(0)还是被占有状态,可重入,一个线程可以占有多次
synchronized 修饰方法是通过ACC_SYNCHRONIZED 标识,指明方法是同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
- wait:释放占有的对象锁释放CPU,进入等待队列,只能通过notify/all继续该进程
- sleep:释放CPU,不释放占有的对象锁,可以在sleep结束后自动继续该进程
- notify: 唤醒等待队列中的一个线程,使其获得锁进行访问
- notifyall::唤醒等待队列中等待该对象锁的全部线程,让其竞争去获得锁
synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。
双重校验锁实现对象单例(线程安全):
public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton(){}
private static Singleton getUniqueInstance(){
if(uniqueInstance == null){
synchronized(Singleton.class){
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
lock
与synchronized相同的语义 必须手动释放锁
性能:资源竞争激烈情况下,lock性能比synchronized好
用法:synchronized可以用在代码块,方法上。lock通过代码实现,需手动释放
原理:synchronized monitorenter monitorexit lock 使用AQS在代码级别实现
volatile
直接与主内存交互,读写保证可见性
禁止JVM进行指令重排序,保证数据可见性,但不能保证原子性
volatile用于解决变量在多个线程之间的可见性,synchronized用于解决多个线程之间访问资源的同步性
ThreadLocal
功能:为每一个线程创建一个专属本地变量存放空间,存取线程的私有数据。
ThreadLocalMap存储结构:ThreadLocal->变量
可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对,value 就是 ThreadLocal 对象调用set方法设置的值。
ThreadLocal 内存泄漏:key为弱引用,value是强引用,GC只回收了key,value无法回收,导致内存泄漏;解决方法是在使用王ThreadLocalMap之后手动调用其remove方法,会删除key为null的记录。
锁粗化和锁消除
锁粗化:某些情况下把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。
for(int i=0;i<size;i++){
synchronized(lock){
}
}
synchronized(lock){
for(int i=0;i<size;i++){
}
}
锁消除:发生在编译器级别的一种锁优化方式
即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除,判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。
线程池
核心参数
- corePoolSize:核心线程数量,决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去
- maximumPoolSize: 线程池允许的最大线程数 会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量
- workQueue:阻塞队列 存储等待执行的任务 分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
- keepAliveTime:(空闲线程)线程没有任务执行时可以保持的时间
- unit:keppAliveTime的时间单位
- threadFactory:线程工厂,创建线程
- rejectHandler:拒绝任务提交时的策略(抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务)
execute方法提交不需要返回值的任务,submit提交需要返回值的任务
ThreadPoolExcutor.execute - 如果运行的线程数 < corePoolSize,直接创建新线程,即使有其他线程是空闲的
- 如果运行的线程数 >= corePoolSize
- 如果插入队列成功,则完成本次任务提交,但不创建新线程
- 如果插入队列失败,说明队列满了
- 如果当前线程数 < maximumPoolSize,创建新的线程放到线程池中
- 如果当前线程数 >= maximumPoolSize,会执行指定的拒绝策略
workQueue任务队列
- 直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,没执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。
- 有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现
- 无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现,这种情况下maximumPoolSize这个参数是无效的
- 优先任务队列:优先任务队列通过PriorityBlockingQueue实现,无界,无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行
拒绝策略
- AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
- CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
- DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
- DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
创建线程池
ThreadPoolExecutor的构造方法实现
通过Executor 框架的工具类Executors来实现
- LFixedThreadPool : 该方法返回一个固定线程数量的线程池
- SingleThreadExecutor: 方法返回一个只有一个线程的线程池。
- CachedThreadPool: 该方法返回一个可根据实际情况调整线程数量的线程池。
信号量Semaphore:阻塞线程且能控制统一时间请求的并发量的工具
CyclicBarrier:可以让一组线程相互等待,当每个线程都准备好之后,所有线程才继续执行的工具类
Java Socket网络编程
通信过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。
创建Socket
使用在包java.net中提供了两个类Socket和ServerSocket
选择端口号时,最好选择一个大于1023的数以防止发生冲突
IO
BIO:InputStream OutputStream Reader Writer 同步阻塞模型
NIO:Channel Buffer Selector IO多路复用的同步非阻塞模型
同步非阻塞:进程先将一个套接字在内核中设置成非阻塞再等待数据准备好,在这个过程中反复轮询内核数据是否准备好,准备好之后最后处理数据返回
AIO:属于事件和回调机制的异步非阻塞模型
Atomic 原子类
不可中断的操作,不受其他线程干扰,原子类都在Java.util.concurrent包里
基本数据类型:AtomicInteger AtomicLong AtomicBoolean
数组类型:AtomicIntegerArray AtomicLongArray AtomicReferenceArray(引用类型数组)
引用类型:AtomicReference AtomicStampedReference:原子更新引用类型里的字段原子类 AtomicMarkableReference :原子更新带有标记位的引用类型
AtomicInteger 的使用
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
多线程环境不使用原子类保证线程安全(基本数据类型):
class Test {
private volatile int count = 0;
//若要线程安全执行执行count++,需要加锁
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
多线程环境使用原子类保证线程安全(基本数据类型)
class Test2 {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
//使用AtomicInteger之后,不需要加锁,也可以实现线程安全。
public int getCount() {
return count.get();
}
}
原理
主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
数组类型原子类–AtomicIntegerArray
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
AQS
用于构建锁和同步器的框架
原理:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH队列:虚拟的双向队列(不存在队列实例,仅存在结点之间的关联关系),AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
常见的问题
程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。
堆和方法区共享,堆用于存放对象,方法区用于存放类信息和常量
问:并发和并行的区别:
并发:同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
并行:单位时间多个任务同时执行
问:sleep()和wait()的区别
答:sleep()没有释放锁,wait()释放锁;wait()后需要notify()唤醒线程,sleep()则会自动苏醒;
问:为什么不直接执行run()方法,需要执行start方法?
答:调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
数据库方面MySQL
- 索引的弊端:插入数据删除整理数据需要重新整理索引,耗时 占空间
Web框架和数据库
Spring
Spring是个包含一系列功能的合集,如快速开发的Spring Boot,支持微服务的Spring Cloud,支持认证与鉴权的Spring Security,Web框架Spring MVC。IOC与AOP依然是核心。
模块:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块
Core组件是核心,Bean组件和Context组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
MVC的流程
1、发送请求 DispatcherServlet拦截器拿到交给HandlerMapping
2、依次调用配置的拦截器,最后找到配置好的业务代码Handler并执行业务方法
3、包装成ModelAndView返回给ViewResolver解析器渲染页面
Bean的生命周期
Bean实例化-值和bean的引用注入到Bean对应的属性-把容器信息注入BeanBe-Bean处理-Bean初始化和销毁
Bean的作用域
- Singleton:始终指向同一对象
- prototype:原型模式每次通过Spring容器获取bean时,容器都创建一个新的实例
- request:在一次Http请求中,容器会返回该Bean的同一实例。不同的Http请求产生新的Bean,该bean仅在当前HttpRequest内有效。
- session:在一次HttpSession中,容器会返回该Bean的同一实例。不同的Session请求创建新的实例,该bean实例仅在当前Session内有效。
- global Session:一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。
IOC(重点)
控制反转:原先是自己主动创建,现在是容器工具创建实例, 面向接口编程和配置文件减少对象之间的耦合
依赖注入:在运行过程中,当你需要这个对象是才给你实例化并注入
IOC容器是一个Map,存放的是各种对象
SpringAOP
面向切面编程 是OO的补充 OOP从上往下 会有重复代码 AOP将和业务无关的重复代码抽取出来 比如权限管理、日志、事务管理 降低耦合度,减少重复代码
实现AOP的方式:
- 采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
- 采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring 中的 bean 的作用域
- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义。
单例bean线程安全:在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。
Spring MVC的工作流程
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- ViewResolver 会根据逻辑 View 查找实际的 View。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
Spring 设计模式
- 工厂模式:创建bean对象
- 代理模式:AOP实现
- 单例模式:Bean默认都是单例
- 模板方法模式:
- 包装器模式:动态切换数据源
- 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式
- 观察者模式:Spring事件驱动模型
将一个类声明为Bean的注解
- @Component:通用标注任意类为Spring组件
- @Repository:对应持久层即 Dao 层,主要用于数据库相关操作
- @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
- @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component:作用于类 通过类路径扫描来自动侦测以及自动装配到Spring容器中
@Bean:作用于方法 告诉了Spring这是某个类的示例,当我需要用它的时候还给我
@Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
Mybatis
ORM持久化框架,支持普通SQL查询,存储过程以及高级映射,通过简单的xml或注解用于配置和原始映射,将接口和对象映射成数据库中记录。
MySQL
存储引擎
InnoDB支持事务 支持外键 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表
InnoDB有行级锁,MyISAM是表级锁
选择MyISAM:系统插入和查询多,不需要事务外键
数据库性能优化(重点)
1、优化SQL语句和索引 在where、groupby、orderby用到的字段上建立索引
2、加缓存
3、主从复制,读写分离(主库负责写,从库负责读)
4、垂直拆分 数据表列的拆分
5、水平切分 数据表行的拆分
SQL优化:
1、对经常查询的列建立索引
2、查询使用精确列名
3、减少子查询,使用join
4、不用not in(使用全表扫描);不用is null not is null(会使索引和索引统计更加复杂)
在某列上建立索引,下次查询时会使用索引查询 最左前缀匹配原则
当表特别大 select * from table where name = xxx and age = xxx 优化策略:创建复合索引将最常用作限制条件的列放在最左边,依次递减。其次还要考虑该列的数据离散程度,如果有很多不同的值的话建议放在左边,name的离散程度也大于age。
事务
一组操作,要么执行,要么不执行
ACID:原子性,一致性,隔离性,持续性
事务隔离级别
- read uncommitted:即便是事务没有commit,但是我们仍然能读到未提交的数据。 脏读
- read committed: 当前会话只能读取到其他事务提交的数据,未提交的数据读不到。 两次读取的结果不同 不可重复读
- repeatable read: 当前会话可以重复读,就是每次读取的结果集都相同,而不管其他事务有没有提交。一个事务的读取数据一致,可数据已经发生改变幻读
- serializable(串行化):其他会话对该表的写操作将被挂起
表锁 行锁
- 表级锁:对整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和
InnoDB引擎都支持表级锁。 - 行级锁:对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
InnoDB 自动给修改操作加锁,给查询操作不自动加锁
行锁相对于表锁来说,优势在于高并发场景下表现更突出
表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁
悲观锁和乐观锁
悲观锁:select for update Synchronized 适合多写的场景
乐观锁:先查询一次数据,然后使用查询出来的数据+1进行更新数据,如果失败则循环 适合读多写少
实现方式:
- 数据版本记录机制(提交版本必须大于记录当前版本才能执行更新)
- 使用时间戳(table增加一个字段,commit时查看是否发生了改变)
- (CAS compare and Swep):非阻塞同步 当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。
CAS和synchronized的使用场景:CAS适合写比较少的场景,synchronized适合写比较多的场景
SQL的执行过程
- 查询语句:权限校验—》查询缓存—》分析器—》优化器—》权限校验—》执行器—》引擎
- 更新语句:分析器----》权限校验----》执行器—》引擎—redo log prepare—》binlog—》redo log commit
索引
加快检索的速度 索引需占物理空间 创建和维护索引耗费时间
类型:BTree索引和哈希索引
原理:使用B+树 一个节点可以存储多个索引的key,一次磁盘IO可以读取到很多key且叶子节点之间还加上了下一个叶子节点的指针
索引优化策略:
- where 、groupby、orderby、join on的列 经常需要检索的列
- 离散度大的列
- 索引字段越小越好
水平分表:为了解决单标数据量过大(数据量达到千万级别)问题。所以将固定的ID hash之后mod,取若0~N个值,然后将数据划分到不同表中,需要在写入与查询的时候进行ID的路由与统计
垂直分表:为了解决表的宽度问题,同时还能分别优化每张单表的处理能力。所以将表结构根据数据的活跃度拆分成多个表,把不常用的字段单独放到一个表、把大字段单独放到一个表、把经常使用的字段放到一个表
分库:面对高并发的读写访问,当数据库无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。因此数据库进行拆分,从而提高数据库写入能力,这就是分库。
MySQL优化规范
命名规范
- 使用小写字母并使用_分割
- 见名识意,不能超过32个字符
- 临时库表必须以 tmp_为前缀并以日期为后缀,备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀
- 所有存储相同数据的列名和列类型必须一致
建表规范
- 第一范式 所有字段不能再分
- 第二范式 在满足第一范式的情况下,不能出现部分依赖
- 第三范式 在满足第二范式的情况下,不能出现传递依赖
设计规范
数据库和表的字符集统一使用 UTF8
所有表和字段都需要添加注释
禁止在数据库中存储图片,文件等大的二进制数据
字段设计规范
- 优先选择符合存储需要的最小的数据类型
- 避免使用 TEXT,BLOB 数据类型(只能使用前缀索引)
- 避免使用 ENUM 类型(ENUM 类型的 ORDER BY 操作效率低,需要额外操作)
- 尽可能把所有列定义为 NOT NULL
- 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间
- 同财务相关的金额类数据必须使用 decimal 类型
索引设计规范
- 单表索引不超过五个
- 索引列选择
- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列
- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
- 不要将符合 1 和 2 中的字段的列都建立一个索引, 通常将 1、2 中的字段建立联合索引效果更好
- 多表 join 的关联列
- 选择索引列的顺序
- 区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)
- 尽量把字段长度小的列放在联合索引的最左侧
- 使用最频繁的列放到联合索引的左侧
- 频繁的查询优先考虑使用覆盖索引(包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引)
- 不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引
SQL开发规范
- 建议使用预编译语句进行数据库操作 只传参数 多次使用
String sql = "insert into t select ?,?";
PreparedStatement statement = con.prepareStatement(sql);
statement.setInt(1, 123456);
statement.setString(2, "abc");
statement.executeUpdate();
statement.close();
- 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询
- 禁止使用不含字段列表的 INSERT 语句
- 避免使用子查询,可以把子查询优化为 join 操作
- 避免使用 JOIN 关联太多的表 不超过5个
- WHERE 从句中禁止对列进行函数转换和计算
操作行为规范
- 超 100 万行的批量写 要分批次进行
- 禁止为程序使用的账号赋予 super 权限
- 程序连接数据库账号遵从权限最小原子
SQL语句笔记
检索
select ‘column1’,‘column2’,‘column3’ from table limit num;
select ‘column1’,‘column2’,‘column3’ from table limit num1, num2;
select distinct 'column' from table;
select distinct 'column1','column2' from table;
排序检索
select ‘column1’,‘column2’,‘column3’
from table
order by 'indexColumn' desc
limit num;
过滤检索
select ‘column1’,‘column2’,‘column3’
from table
where 'indexColumn' = value
limit num;
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn between begin and end
limit num;
select 'column'
from table
where indexColumn is null;
通配符检索
%任意字符,出现任意次数
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn like 'a%'
order by indexCloumn
_任意字符,出现一次
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn like '_a_'
order by indexCloumn
正则检索
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn regexp ‘a’
order by indexCloumn
or
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn regexp ‘app|d'
order by indexCloumn
escape special character
select ‘column1’,‘column2’,‘column3’
from table
where indexColumn regexp '\\.'
order by indexCloumn
计算
字符串处理函数
select column1,upper(column1) as new_column
from table
order by column1
-- Left(str, len) | return left-handside of string
-- Right(str, len) | return right-handside of string
-- Length(str) | return length of string
-- Locate(pat, str, [pos]) | find sub-string location of string
-- SubString(str, pos, len) | get sub-string by index
-- Lower(str) | lower case of string
-- Upper(str) | upper case of string
-- Trim(str) | trim of string
-- LTrim(str) | trim left-handside of string
-- RTrim(str) | trim right-handside of string
-- Soundex(str) | return SOUNDEX value of string
Numeric 处理函数
-- Ceil() | return the smallest integer value not less than the argument
-- Floor() | return the largest integer value not greater than the argument
-- Ceiling() | return the smallest integer value not less than the argument
-- Truncate() | truncate to specified number of decimal places
-- Radians() | return argument converted to radians
-- Conv() | convert numbers between different number bases
-- Round() | round the argument
-- Asin() | return the arc sine
-- Acos() | return the arc cosine
-- Atan() | return the arc tangent
-- Sin() | return the sine of the argument
-- Cos() | return the cosine
-- Tan() | return the tangent of the argument
-- Cot() | return the cotangent
-- Pi() | return the value of pi
-- Ln() | return the natural logarithm of the argument
-- Log() | return the natural logarithm of the first argument
-- Log10() | return the base-10 logarithm of the argument
-- Log2() | return the base-2 logarithm of the argument
-- Exp() | raise to the power of
-- Pow() | return the argument raised to the specified power
-- Abs() | return the absolute value
-- Sqrt() | return the square root of the argument
-- Mod() | return the remainder
-- Sign() | return the sign of the argument
-- Rand() | return a random floating-point value
日期处理函数
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) = '2005-09-01';
SELECT cust_id, order_num
FROM orders
WHERE Year(order_date) = 2005 AND Month(order_date) = 9;
-- more date functions
-- AddDate(expr, days) | add time values (intervals) to a date value
-- AddTime(expr1, expr2) | add time
-- Now() | return datetime
-- CurDate() | return current date
-- CurTime() | return current time
-- Date(expr) | return date part of expr
-- Time(expr) | return time part of expr
-- Year(expr) | return year part of expr
-- Month(expr) | return month part of expr
-- Day(expr) | return day part of expr
-- Hour(expr) | return hour part of expr
-- Minute(expr) | return minute part of expr
-- Second(expr) | return second part of expr
-- DayOfWeek(expr) | return day of expr
-- DateDiff(expr1, expr2) | return expr1 - expr2
数据汇总
统计
Avg() Count() Max() Min() Sum()
select avg(column) as avg_column
from table
select count(column) as count_column
from table
select Sum(column) as sum_column
from table
分组 groupby
GroupBy
SELECT vend_id, Count(*) AS num_prod
FROM products
GROUP BY vend_id;
Having
SELECT cust_id, Count(*) AS orders
FROM orders
GROUP BY cust_id
HAVING Count(*) >= 2;
子查询
select cust_id
from orders
where order_num in(
select order_num
from oorder_items
where prod_id = 'TNT2'
);
Join
内连接和左外连接
SELECT customers.cust_id, orders.order_num
FROM customers INNER JOIN orders
ON customers.cust_id = orders.cust_id;
SELECT customers.cust_id, orders.order_num
FROM customers LEFT OUTER JOIN orders
ON customers.cust_id = orders.cust_id;
组合查询
or
select vend_id,prod_id,prod_price
from products
where prod_price <= 5
or vend_id in (1001,1002)
操作数据
插入数据
insert into customers values('','','','','')
insert into customers ('','','','') values ('','','','')
更新和删除数据
-- UPDATE
UPDATE customers
SET cust_email = 'jack@gmail.com'
WHERE cust_id = 10005;
UPDATE customers
SET cust_email = 'jack@gmail.com', cust_name = 'jack'
WHERE cust_id = 10005;
-- DELETE
DELETE FROM customers
WHERE cust_id = 10006;
视图
创建视图
CREATE VIEW prodcust AS
SELECT cust_name, cust_contact, prod_id
FROM customers, orders, orderitems
WHERE customers.cust_id = orders.cust_id
AND orders.order_num = orderitems.order_num;
使用视图
SELECT cust_name, cust_contact
FROM prodcust
WHERE prod_id = 'TNT2';
Redis
存在内存的数据库,缓存方向和分布式锁方向,使用redis比java自带的map或者guava笨蛋好吃具有一致性
定义:键值对数据库 键值对由字典保存 每个数据库都有一个相应的字典(键空间) 键是一个字符串对象,值可以是包括字符串,列表,哈希表,集合,有序集合在内的任意一种Redis类型对象
主从模式:一个实例作为主机,其余实例作为从机 ,数据完全一致 主机执行写数据命令,从机执行读数据命令, 实现读取分离
主从模式和哨兵模式全局存取变量,浪费内存 使用集群(分布式存储)
集群的优势在于高可用,写的操作多且数据量巨大,且不需要高级功能则考虑集群
哨兵的优势在于高可用,支持高级功能,且能在读的操作较多的场景下工作
主从的优势在于支持高级功能,且能在读的操作较多的场景下工作,但无法保证高可用,不建议在数据要求严格的场景下使用
策略
延迟加载
读:先从缓存读,读不到就从数据库读,读完之后同步到缓存并添加缓存过期时间
写:只写数据库
直写
读:先从缓存读,读不到就从数据库读,读完之后同步到缓存且设置永不过期
写:先写数据库如何同步到缓存,设置为永不过期
如何选择
- 如果需要缓存和数据库保持实时一致,选择直写方式;
- 缓存稳定,可用空间大,写缓存的性能丢失可用接受,选择直写 否则选择延迟加载。
缓存问题
- 缓存击穿:查询一个数据库不存在的数据->设置一个默认值
- 缓存失效:如果缓存失效,就会有多个进程去查询DB,再去设置缓存,导致DB压力大 ->将key的缓存失效时间均匀错开
- 热点Key:所有的流量涌向一个节点,无法通过增加机器容量解决 ->客户端热点key缓存设置过期时间//热点key分散为多个子key,散布在不同机器
通用基础
网络通信协议
TCP/IP:四层网络协议 网络接口层 网络层 传输层 应用层
应用层协议
域名系统
将域名和IP地址相互映射
HTTP
TCP/IP 应用层协议
特点
- 简单快速(method+url)
- 灵活(传输任意类型的数据)
- 无连接(一次只处理一个请求,结束后释放连接)
- 无状态(服务器发送数据后不会记录任何信息)
HTTP/1.0中默认使用短连接,HTTP/1.1起,默认使用长连接 响应头加入Connection:keep-alive
Session 的主要作用就是通过服务端记录用户的状态。
Cookie 一般用来客户端保存用户信息
HTTP 1.0和HTTP 1.1的主要区别是什么?
- 长连接和短连接
- 错误状响应码 HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;
- 缓存处理 HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
- 带宽优化及网络连接的使用 HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content)
HTTP如何工作的
HTTP消息结构
客户端请求消息格式:
- 请求行:请求方法,URL,协议版本
- 请求头部:字段名+值的格式
- 请求数据
服务端响应消息格式:
- 状态行:协议版本号+状态码(http/1.1 200 OK)
- 消息报头:Date时间 + Content-Type + Charset(编码) + Content-Length (application/json;charset=UTF-8)
- 空行
- 响应正文
HTTP请求方法
HTTP1.0:GET, POST 和 HEAD方法
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
HTTP状态码
- 1** 信息,服务器收到请求,需要请求者继续执行操作
- 2** 成功,操作被成功接收并处理
- 3** 重定向,需要进一步的操作以完成请求
- 4** 客户端错误,请求包含语法错误或无法完成请求
- 5** 服务器错误,服务器在处理请求的过程中发生了错误
HTTPS
SSL和HTTP 加密传输,身份认证
加密过程:
- 客户端发送Hello消息 服务器也返回一个Hello消息 包含一些加密算法和SSL版本
- 证书交换 SSL证书包含各种数据,包含所有者名称,相关属性(域名),证书上的公钥,数字签名和关于证书有效期的信息
- 秘钥交换 使用RSA非对称公钥加密算法 客户端生成一个对称密钥,然后用SSL证书里带的服务器公钥将该对称密钥加密。随后发送到服务端,服务端用服务器私钥解密,到此,握手阶段完成。
- 加密通信 对称加密算法
HTTP 和 HTTPS 的区别?
- 端口:HTTP默认80端口,HTTP是默认端口443
- HTTP 内容是明文,未加密;
- HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS 运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密
对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。
SSL 和 TLS
SSL:安全套接层(Secure Socket Layer)
TLS:传输层安全性协议(Transport Layer Security)
TLS位于传输层和应用层之间,内部有TLS握手协议,TLS记录协议
HTTPS= HTTP + TLS
传输内容加密
client:明文 + 公钥 = 密文
server:密文 + 私钥 = 明文
服务端身份认证
伪随机数生成器:秘钥生成随机性,更难被猜测
对称密码:对称密码使用的秘钥就是由伪随机数生成,相较于非对称密码,效率更高
消息认证码:保证消息信息的完整性、以及验证消息信息的来源
公钥密码:证书技术使用的就是公钥密码
数字签名:验证证书的签名,确定由真实的某个 CA 颁发
证书:解决公钥的真实归属问题,降低中间人攻击概率
运输层协议
TCP
传输层协议 面向连接,可靠的,基于字节流 有FTP、SMTP、HTTP
建立连接(三次握手):
1、建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
2、服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
3、客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
两次握手可能出现网络延迟导致创建无效连接
关闭连接:(四次挥手)
1、当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
2、主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
3、主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
4、主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
滑动窗口协议
传输层流控 接收方告知发送方自己的窗口大小,控制发送方的发送速度
通过API通知TCP协议栈缩小TCP接收窗口
TCP的拥塞控制采用了四种算法,即 慢开始 、 拥塞避免 、快重传 和 快恢复
UDP
非面向连接的,不可靠的,传输快
OPSF
路由转发协议
ARP协议
IP地址转换MAC地址
Linux
文件:一切皆文件
文件类型
- 普通文件
- 目录文件
- 链接文件
- 设备文件
- FIFO(命名管道):进程间通信
基本命令
目录切换命令
cd usr: 切换到该目录下usr目录
cd ..(或cd../): 切换到上一层目录
cd /: 切换到系统根目录
cd ~: 切换到用户主目录
cd -: 切换到上一个操作所在目录
目录操作命令
mkdir 目录名称: 增加目录
ls或者ll(ll是ls -l的别名,ll命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息
find 目录 参数: 寻找目录(查)
mv 目录名称 新目录名称: 修改目录的名称(改)
mv 目录名称 目录的新位置
cp -r 目录名称 目录拷贝的目标位置: 拷贝目录(改),-r代表递归拷贝
rm [-rf] 目录: 删除目录(删)
文件操作命令
touch 文件名称: 文件的创建(增)
cat/more/less/tail 文件名称 文件的查看(查)
cat: 查看显示文件内容
- more: 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看
- less: 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看
- tail-10 : 查看文件的后10行,Ctrl+C结束 用tail -f main.log监控文件变化
vim 文件: 修改文件的内容(改)
rm -rf 文件: 删除文件(删)
压缩文件操作命令
tar -zcvf 打包压缩后的文件名 要打包压缩的文件
(z:调用gzip压缩命令进行压缩 c:打包文件 v:显示运行过程 f:指定文件名)
tar [-xvf] 压缩文件 -C /user 解压压缩包 -C 代表指定压缩的位置
Linux 权限命令
文件类型
d:代表目录
-:代表文件
l:代表软连接
文件权限
r:代表权限是可读,r也可以用数字4表示
w:代表权限是可写,w也可以用数字2表示
x:代表权限是可执行,x也可以用数字1表示
所有者:ls ‐ahl命令 查看 chown 用户名 文件名 修改文件的所有者
所在组:s ‐ahl命令 看到文件的所有组 chgrp 组名 文件名 修改文件所在的组
命令
修改文件/目录的权限的命令:chmod
chmod u=rwx,g=rw,o=r aaa.txt 等价于 chmod 764 aa.txt
用户(组)管理
useradd 选项 用户名:添加用户账号
userdel 选项 用户名:删除用户帐号
usermod 选项 用户名:修改帐号
passwd 用户名:更改或创建用户的密码
passwd -S 用户名 :显示用户账号密码信息
passwd -d 用户名: 清除用户密码
用户组管理
groupadd 选项 用户组 :增加一个新的用户组
groupdel 用户组:要删除一个已有的用户组
groupmod 选项 用户组 : 修改用户组的属性
其他常用命令
- pwd: 显示当前所在位置
- grep 要搜索的字符串 要搜索的文件 --color: 搜索命令,–color代表高亮显示
- ps -ef/ps -aux:这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:ps aux|grep redis (查看包括redis字符串的进程),也可使用 pgrep redis -a。
注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。 - kill -9 进程的pid: 杀死进程(-9 表示强制终止。)先用ps查找进程,然后用kill杀掉
- ifconfig:查看当前系统网卡信息
- ping:查看与某台机器连接情况
- netstat -an:查看当前系统端口使用情况
- shutdown:shutdown -h now 现在立即关机
- reboot:重开机
面试问题
Java基础
- 抽象类和接口的区别:
- 抽象类有成员实现,接口则没有;
- 抽象类抽象成员可以被子类部分实现,接口则要求全部实现;
- 一个类只能继承一个抽象类,但可以实现多个接口;
- 抽象类是对类的抽象,接口是对类的行为定义;
- super的用法 具体到构造函数 分情况:看父类是否有无参构造函数 this关键字
- 泛型的理解
- String类的常用方法:equals compareTo indexOf charAt valueOf concat toCharArray replace replaceAll split substring
- 普通类和抽象类的区别:抽象类不能被实例化;含有抽象方法的类必须声明为抽象类;抽象方法不能用private,static,final修饰
- <<和>>,>>>?<<表示左移,低位补0;>>表示右移,正数高位补0,负数高位补1;>>>表示无符号右移,高位补0
- 常见的异常类:Throwable是根类,子类有ERROR和EXCEPTION,Exception包含RuntimeException和一般异常 RuntimeException:IllegalArgumentException IndexOutOfBoundsException NullPointerException
IO
- IO流分类:字节流(一次读入或读出是8位二进制):Stream,字符流(一次读入或读出是16位二进制):Writer,Reader 四大类{InputStream,OutputStream,Reader,Writer}
InputStream: FileInputStream、PipedInputStream、ByteArrayInputStream
BufferedInputstream、SequenceInputStream、DataInputStream、ObjectInputStream
OutputStream:FileOutputStream、PipedOutputStream、ByteArrayOutputStream
BufferedOutputStream、DataOutputStream、ObjectOutputStream、PrintStream
Reader: FileReader、PipedReader、CharArrayReader
BufferedReader、InputStreamReader
Writer: FileWriter、PipedWriter、CharArrayWriter
BufferedWriter、InputStreamWriter、PrintWriter - 常用的流:
文件操作:FileInputStream(字节输入流)、FileOutputStream(字节输出流)、FileReader(字符输入流)、FileWriter(字符输出流)
管道操作:PipedInputStream(字节输入流)、PipedOutStream(字节输出流)、PipedReader(字符输入流)、PipedWriter(字符输出流)。
字节/字符数组:ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter是在内存中开辟了一个字节或字符数组。
Buffered缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
带缓冲区的处理流、缓冲区的作用的主要目的是:避免每次和硬盘打交道、提高数据访问的效率。
字节转化成字符流:InputStreamReader、OutputStreamWriter
数据流:DataInputStream、DataOutputStream 可以直接输出float类型或long类型 - BIO、NIO、AIO 有什么区别?
- BIO:同步阻塞,从发起请求起,线程一直阻塞,直到操作完成;
- NIO:同步非阻塞,发起IO请求,立即返回,IO准备好之后,调用回调函数完成IO操作,线程开始阻塞,直到操作完成
- AIO:异步非阻塞,发起IO请求,立即返回,IO准备好之后,做IO操作,直到操作完成或失败,调用回调函数通知线程IO操作结果
- JAVA内部类
- 成员内部类:编译之后会生成两个class文件:OuterClass.class和OuterClass$InnerClass.class
- 方法内部类:定义在方法中,编译后生成OuterClass.class和OuterClass$1InnerClass.class,只能在定义该内部类所在方法内实例化,不能使用该内部类所在方法的非final局部变量
- 匿名内部类
- NIO中的Files类常用方法?
集合
- Collection 和 Collections 有什么区别?Collection是JDK中集合层次结构中的最根本的接口;Collections是一个包装类。它包含有各种有关集合操作的静态多态方法,不能实例化,像一个Collection集合框架中的工具类
- HashMap 和 Hashtable 有什么区别?
- 线程安全性不同
- key、value是否允许null。HashMap的key和value都是可以是null,key只允许一个null;Hashtable的key和value都不可为null。
- 迭代器不同。HashMap的Iterator是fail-fast迭代器;Hashtable还使用了enumerator迭代器。
- 默认初始大小和扩容方式不同。HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍;Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1。
- 父类不同。HashMap继承自AbstractMap;Hashtable继承自Dictionary。
- HashMap和TreeMap:HashMap基于散列桶(数组和链表)实现;TreeMap基于红黑树实现。HashMap不支持排序;TreeMap默认是按照Key值升序排序的,可指定排序的比较器,主要用于存入元素时对元素进行自动排序。
- 数组和List之间的转换
String[] strs = new String[] {"aaa", "bbb", "ccc"}; List<String> list = Arrays.asList(strs); List<String> list = Arrays.asList("aaa", "bbb", "ccc"); String[] array = list.toArray(new String[list.size()]);
- ArrayList 和 Vector 的区别是什么?线程安全性,默认初始化容量都是10,Vector 扩容默认会翻倍,可指定扩容的大小;ArrayList只增加 50%
- Queue 中 add() 和 offer() 区别?add() 和 offer()都是用来向队列添加一个元素。在容量已满的情况下,add() 方法会抛出IllegalStateException异常,offer() 方法只会返回 false 。
- 在 Queue 中 element() 和 peek()有什么区别?element() 和 peek()都是用来返回队列的头元素,不删除。在队列元素为空的情况下,element() 方法会抛出NoSuchElementException异常,peek() 方法只会返回 null。
- 迭代器Iterator:Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String str = iterator.next(); System.out.println(str); if(///){ iterator.remove();//删除 } }
- Iterator 和 ListIterator 有什么区别? ListIterator继承iterator,方法更多(add,set),只能使用于List及子类
- 保证集合不能被修改?list = Collections.unmodifiableList(list);
并发
-
并行指同一个时刻,并发指同一个时间段
-
进程和线程?
- 进程是系统资源分配和调度的独立单位,线程是进程的实体,CPU调度和分派的基本单位;
- 每个进程都有独立的内存地址空间;线程不分配内存,只能共享进程的内存资源;
- 进程间切换开销比线程间切换大;
-
守护线程?
- 程序运行时在后台提供通用服务的线程:调用方式(在start线程之前调用线程的setDaemon(true))
- 守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
-
创建线程的方式?
class customThread extend Thread{ @override public void run(){ } } class customRunnable implements Runnable{ public void run(){} } class customCallable implements Callable<String>{ public String call() throws Exception{ } }
-
wait和sleep的区别?
- wait释放锁,sleep不释放锁;
- wait是Object类成员本地方法;sleep是Thread静态本地方法;
- sleep在任何地方都可以使用;wait只能在同步方法和同步代码块使用‘
-
Runnable 和 Callable 有什么区别?
- Runnable接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可获得异常信息;
-
notify()和notifyall()
等待池:线程调用对象的wait方法,线程释放对象的锁之后进入该对象的等待池,不去竞争该对象的锁
锁池:只有获取对象的锁,线程才能执行对象synchronized代码,对象的锁每次只能有一个线程获得,其余线程只能在锁池中等待notify()随机唤醒该对象等待池中的一个线程,进入锁池;notifyAll()唤醒对象等待池中的所有线程进入锁池
-
创建线程池?
//定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量 static ExecutorService fixedExecutor = Executors.newFixedThreadPool(3); fixedExecutor.execute(new Runnable() { public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " index:" + index); } }); //可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制 static ExecutorService cachedExecutor = Executors.newCachedThreadPool(); //定长线程池,可执行周期性的任务 static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(3);
-
线程池状态:RUNNING SHUTDOWN STOP TIDYING TERMINATED
-
线程池中 submit() 和 execute()方法有什么区别?
- execute()没有返回值,submit()有返回值
- execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
-
多线程安全性问题
- 原子性:不可中断(线程切换)–>> 原子类 synchronized Lock
- 可见性:修改共享变量(缓存导致)–>> synchronized volatile Lock
- 有序性:代码运行顺序(编译优化重排序)–>> Happens-Before规则
-
锁的级别:无锁->偏向锁->轻量级锁->重量级锁
- 无锁:所有线程都能访问并修改,但只有一个能修改成功,其余重试;
- 偏向锁: 偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争偏向锁才会被释放。
- 轻量级锁:指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
- 重量级锁: 指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
-
死锁?多个线程相互持有所需要的资源,无法执行。
- 不可抢占、互斥、循环等待 、请求和保持
- 加锁顺序,加锁时限,死锁检测
-
ThreadLocal
- 线程本地存储,每一个线程都创建一个THreadLocalMap对象
- 使用场景:为每一个JDBC分配Connection、Session管理
- ThreadLocalMap属于Thread,不属于ThreadLocal(代理工具类),不容易产生内存泄露
- 线程池中使用ThreadLocal,在 finally 代码块中手动清理 ThreadLocal 中的 value,调用 ThreadLocal 的 remove()方法,以免内存泄露;
-
synchronized原理
- 同步代码块:monitorenter和monitorexit指令获取线程的执行权;
- 同步方法通过加ACC_SYNCHRONIZED表示线程的执行权控制;
-
synchronized和volatile区别?
- synchronized可作用于变量,方法,对象;volatile只能作用于变量;
- synchronized保证原子性,可见性和有序性;volatile只能保证可见性和有序性;
- synchroized线程阻塞,volatile线程不阻塞。
-
synchronized和lock区别?
- 关键字 接口
- 是否自动释放锁 synchronized 在线程代码执行完或出现异常时自动释放锁;Lock 不会自动释放锁,需要再 finally {} 代码块显式地中释放锁
- 是否一直等待 synchronized拿不到锁一直等待 Lock 可以设置尝试获取锁或者获取锁失败一定时间超时
- synchronized 可重入 不可中断 非公平;Lock可重入,可中断,可公平可不公平(即先等待先获取到锁)
-
ReadWriteLock 如何使用?
使用读写锁实例 -
原子类的原理 CAS
CAS 包含 3 个参数,CAS(V, E, N)。V 表示需要更新的变量,E 表示变量当前期望值,N 表示更新为的值。只有当变量 V 的值等于 E 时,变量 V 的值才会被更新为 N。如果变量 V 的值不等于 E ,说明变量 V 的值已经被更新过,当前线程什么也不做,返回更新失败。for(int i=0;i<n;i++){ new Thread(){ @override public void run(){ for(int j=0; j< 10;j++){ try{ Thread.sleep(100); }catch(InterruptedException){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } }.start(); }
-
ForkJoinPool 解决CPU负载不均衡 较大的任务,被一个线程去执行,而其他线程处于空闲状态
- 调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask;
- 调用 ForkJoinTask 的 join() 的方法,将多个小任务的结果进行汇总。
网络
- http 响应码 301 和 302 代表的是什么?有什么区别?重定向
301 表示被请求 url 永久转移到新的 url;302 表示被请求 url 临时转移到新的 url。 - session 和 cookie 区别?
- session 是在服务器端记录信息;cookie 是在浏览器端记录信息;
- session数据大小取决于程序设计;cookies大小不超过4kb,一个站点最多20个cookies;
- cookies是一段文本,session可以处理为Key-Value;
- session安全性更高。
- session工作原理
- 服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息。
- 如果浏览器禁用 cookie,默认情况下 session 无法生效。可以通过url重载携带 sessionid 参数、把 sessionid 设置为 http 协议 header 设为其他自定义字段中,请求中始终携带。
- 预防sql注入
- 给连接数据库的用户提供满足需要的最低权限
- 校验参数的数据格式是否合法(正则或特殊字符判断)
- 预编译 SQL(Java 中使用 PreparedStatement),参数化查询方式
- 发布前进行注入检测
- 保错信息不要将SQL信息输出到页面
Spring
- AOP
面向切面 预编译和运行期动态代理实现程序功能的统一维护 将影响多个类的公共行为封装到一个可重用的模块
实现方法:动态代理(拦截方法,进行装饰,以取代原有对象行为的执行) 静态织入(特定的语法创建)
使用xml配置和注解的使用AOP - IOC
将创建和查找依赖对象的控制权交给IOC容器,注入和组合对象
便于测试 维护 代码重用 可维护性 扩展性 - 主要模块:Spring Core、Spring AOP、Spring Web、SpringMVC、Spring DAO…
- Bean注入
- xml注入
- bean+property,set方法注入
package constxiong.interview.inject; public class Bowl { public void putRice() { System.out.println("盛饭..."); } }
package constxiong.interview.inject; public class Person { private Bowl bowl; public void eat() { bowl.putRice(); System.out.println("开始吃饭..."); } public void setBowl(Bowl bowl) { this.bowl = bowl; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowl" class="constxiong.interview.inject.Bowl" /> <bean id="person" class="constxiong.interview.inject.Person"> <property name="bowl" ref="bowl"></property> </bean> </beans>
package constxiong.interview.inject; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class InjectTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring_inject.xml"); Person person = (Person)context.getBean("person"); person.eat(); } }
- bean+ constructor-arg 节点使用 构造方法注入
package constxiong.interview.inject; public class Person { private Bowl bowl; public Person(Bowl bowl) { this.bowl = bowl; } public void eat() { bowl.putRice(); System.out.println("开始吃饭..."); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="bowl" class="constxiong.interview.inject.Bowl" /> <bean id="person" class="constxiong.interview.inject.Person"> <constructor-arg name="bowl" ref="bowl"></constructor-arg> </bean> </beans>
- bean+property,set方法注入
- 注解注入
- bean的声明和注册
@Component //注册所有bean
@Controller //注册控制层的bean
@Service //注册服务层的bean
@Repository //注册dao层的bean. - bean 的注入
@Autowired 作用于 构造方法、字段、方法,常用于成员变量字段之上。
@Autowired + @Qualifier 注入,指定 bean 的名称
@Resource JDK 自带注解注入,可以指定 bean 的名称和类型等
- bean的声明和注册
- xml注入
- spring 管理的 bean 的线程安全跟 bean 的创建作用域和 bean 所在的使用环境是否存在竞态条件有关,spring 并不能保证 bean 的线程安全
- Bean的作用域
- singleton:单例模式
- prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时,都产生一个新的 bean 实例
- request:每次 HTTP 请求将会产生不同的 bean 实例
- session:同一个 Session 共享一个 bean 实例。
- global-session:同 session 作用域不同的是,所有的Session共享一个Bean实例。
- DispatchServlet-> Handermapping->HanderAdaper 处理Hander->返回ModelAndView给ViewReslover->VIew给DispatchServlet响应
- spring mvc 有哪些组件?
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器适配器(HandlerAdapter)
拦截器(HandlerInterceptor)
语言环境处理器(LocaleResolver)
主题解析器(ThemeResolver)
视图解析器(ViewResolver)
文件上传处理器(MultipartResolver)
异常处理器(HandlerExceptionResolver)
数据转换(DataBinder)
消息转换器(HttpMessageConverter)
请求转视图翻译器(RequestToViewNameTranslator)
页面跳转参数管理器(FlashMapManager)
处理程序执行链(HandlerExecutionChain) - @RequestMapping 是一个注解,用来标识 http 请求地址与 Controller 类的方法之间的映射
- @Autowired 是一个注释,它可以对类成员变量、方法及构造函数进行标注,让 spring 完成 bean 自动装配的工作。
算法
常问排序算法
快速排序
public class quickSort {
public static void quickSort(int []array,int lo,int hi){
if(lo >= hi){
return;
}
int index = partition(array,lo,hi);
quickSort(array,lo,index-1);
quickSort(array,index+1,hi);
}
public static int partition(int []array,int lo,int hi){
int tmp = array[lo];;
while(lo < hi){
while(array[hi] >=tmp && lo<hi){
hi--;
}
array[lo] = array[hi];
while(array[lo] <=tmp && lo<hi){
lo++;
}
array[hi] = array[lo];
}
array[lo] = tmp;
return lo;
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
quickSort(array,0,array.length-1);
System.out.print(Arrays.toString(array));
}
}
冒泡排序
public class bubbbleSort {
public static void bubbleSort(int []array){
int n = array.length;
int tmp = 0;
for(int i=0;i<n-1;i++){
for(int j = 0;j< n-i-1;j++){
if(array[j+1]<array[j]){
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
bubbleSort(array);
System.out.print(Arrays.toString(array));
}
}
选择排序
public class selectSort {
public static void selectSort(int array[]){
int n = array.length;
int tmp = 0;
for(int i = 0 ;i< n;i++){
int index = i;
for(int j =i;j<n;j++){
if(array[j]<array[index]){
index = j;
}
}
tmp = array[index];
array[index] = array[i];
array[i] = tmp;
}
}
public static void main(String args[]){
int array[] = {2,9,-1,10,4};
selectSort(array);
System.out.print(Arrays.toString(array));
}
}
二分查找
public class binarySearch {
public static int binarySearch(int []array,int target){
if(array.length == 0){
return 0;
}
int start = 0;
int end = array.length-1;
while(start<=end){
int mid = start + (end-start)/2;
if(array[mid]==target){
return mid;
}else if(array[mid]>target){
end = mid-1;
}else {
start = mid+1;
}
}
return 0;
}
public static void main(String []args){
int array[] = {1,2,3,4,9,10,11};
System.out.print(binarySearch(array,9));
}
}
笔试编程题
动态规划
最长递增子序列
arr[] = {3,1,4,1,5,9,2,6,5}的最长递增子序列长度为4。即为:1,4,5,9
public static int MaxChildArrayOrder(int array[]){
int n = array.length;
int dp[] = new int[array.length];
for(int i=0;i<n;i++){
dp[i] = 1;
}
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
if(array[j]<array[i] && dp[j]+1>dp[j]){
dp[j] = dp[j]+1;
}
}
}
Arrays.sort(dp);
return dp[n-1];
}
数组最大连续子序列和
arr[] = {6,-1,3,-4,-6,9,2,-2,5}的最大连续子序列和为14。即为:9,2,-2,5
public static int MaxContinueArraySum(int []array){
int sum = array[0];
int max = array[0];
for(int i=1;i<array.length;i++){
max = Math.max(max,max+array[i]);
if(max>sum){
sum = max;
}
}
return sum;
}
最长公共子串
01背包问题
物品的个数为5,重量分别是weight[2,2,6,5,4],价值分别是value[6,3,5,4,6],给定一个容量为10的背包,如何装能装入最多财富
//输入数组前加一个0
public class Bag{
public static int getMaxValue(int[]weight,int value[],int m,int n){
int dp[][] = new int [m+1][n+1];//m个物品,n为背包容量
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(weight[i]>j){
dp[i][j] = dp[i-1][j];
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
}
}
}
return dp[m][n];
}
}
//一维数组形式
public static int getMaxValue(int[]weight,int value[],int m,int n){
int dp[]= new int [n+1];
for(int i=0;i<m;i++){
for(int j=n;j>weight[i];j--){
if(dp[j-weight[i]]+value[i]>dp[j]){
dp[j] = (dp[j-weight[i]]+value[i];
}
}
}
return dp[n];
}
完全背包问题
和01背包不同的是:每种物品可以重复取
public static int completeBag(int[] weight, int[] value, int m, int n){
int dp[] = new int[n+1];
for(int i=0;i<m;i++){
for(int j=weight[i];j<=n;j++){
if(dp[j-weight[i]]+value[i]>dp[j]){
dp[j]=dp[j-weight[i]]+value[i];
}
}
}
return dp[n];
}
多重背包问题
有n件物品和容量为m的背包 给出i件物品的重量以及价值 还有数量 求解让装入背包的物品重量不超过背包容量 且价值最大
特点 它与完全背包有类似点 特点是每个物品都有了一定的数量
public static int mutiBag(int[] weight, int[] value,int[]number, int m, int n){
int dp[] = new int[n+1];
for(int i=0;i<m;i++){
for(int j=n;j>=0;j--){
for(int k =0; k<=number[i];k++){
if(j-k*weight[i]<0)break;
dp[j]=Math.max(dp[j],dp[j-k*weight[i]]+k*value[i]);
}
}
}
return dp[n];
}
常见笔试题
数组-对撞指针-最大蓄水
给出一个非负整数数组,每一个整数表示一个竖立在坐标轴x位置的一堵高度为该整数的墙,选择两堵墙,和x轴构成的容器可以容纳最多的水
public int maxArea(int[] height) {
int i = 0, j = height.length - 1;
int max = 0;
while(i < j){
max = Math.max(max, Math.min(height[i], height[j])*(j-i));
if(height[i] < height[j]){
i++;
}else{
j--;
}
}
return max;
}
数组-滑动窗口-最小连续子数组
给定一个整形数组和一个数字s,找到数组中最短的一个连续子数组,使得连续子数组的数字和sum>=s,返回这个最短的连续子数组的长度值
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int i = 0, j = -1, sum = 0, minLength = Integer.MAX_VALUE;
while(i < nums.length){
if(j + 1 < nums.length && sum < s){
sum+=nums[++j];
}else{
sum-=nums[i++];
}
if(sum >= s){
minLength = Math.min(j-i+1, minLength);
}
}
if(minLength == Integer.MAX_VALUE){
return 0;
}
return minLength;
}
}
面试算法题
在一个连续的比特流中查找特定比特块出现的次数及首次出现的次数,比如在010011100001101110010中查找011出现的次数及首次出现的位置。
输入:int[] array, int target,整数的取值范围在[0,7]
输出:出现的次数times和出现的位置,没有找到次数返回0,位置返回-1
import java.util.ArrayList;
public class substringSearch {
static class bitRuslt{
private int times;
private int firstFound;
public int getTimes() {
return times;
}
public void setTimes(int times) {
this.times = times;
}
public int getFirstFound() {
return firstFound;
}
public void setFirstFound(int firstFound) {
this.firstFound = firstFound;
}
}
public static String intToString(int target){
if(target == 0){
return "000";
}else if(target == 1){
return "001";
}else if(target == 2){
return "010";
}else if(target == 3){
return "011";
}else if(target == 4){
return "100";
}else if(target == 5){
return "101";
}else if(target == 6){
return "110";
}else{
return "111";
}
}
public static bitRuslt find(int []array, int target){
bitRuslt result = new bitRuslt();
if(array.length == 0 || target > 7 || target < 0){
result.setTimes(0);
result.setFirstFound(-1);
return result;
}
StringBuffer arrString = new StringBuffer();
String targetStr = intToString(target);
for(int i=0;i<array.length;i++){
arrString.append(intToString(array[i]));
}
ArrayList<Integer> locations = new ArrayList<>();
int times = 0;
int firstFound = -1;
for(int i=0;i<arrString.length()-1;i++){
for(int j=0;j<3;j++){
if(targetStr.charAt(j) == arrString.charAt(i+j)){
if(j == 2){
times++;
locations.add(i);
}
continue;
}else {
break;
}
}
}
if(locations.size() == 0){
result.setFirstFound(-1);
}else {
result.setFirstFound(locations.get(0));
}
result.setTimes(times);
return result;
}
public static void main(String[]args){
int []arr = {2,3,4,1,5,6,2};
bitRuslt res = find(arr,3);
System.out.print(res.getTimes()+" "+res.getFirstFound());
}
}
显示中的时间是24进制的,时间表示也可以使用各种进制表示,例如2进制等等;先输入一个不知道是何种进制的时间如00002:00130,试求该时间表示的所有可能进制
输入:字符串, 例如00002:00130 每个字符的可能取值为[0,9] 或者[A ~ Z](分别代表10-35)
输出:所有可能存在的进制数,没有匹配的进制则返回0,有无穷多则返回-1;
输入样例:00002:00130
输出样例:4 5 6
本人菜鸡只做了45%
import java.util.ArrayList;
import java.util.Scanner;
public class shunfen2 {
public static int getMax(String str){
int max =0;
int num;
for(int i=0;i<str.length();i++){
if(str.charAt(i) >= 'A' && str.charAt(i) <= 'Z'){
num = (int)(str.charAt(i) - 'A' +10) ;
if(num > max){
max = num;
}
}else {
num = Integer.parseInt(String.valueOf(str.charAt(i)));
if(num > max){
max = num;
}
}
}
return max;
}
public static int conform(String str,int b){
int sum =0;
for(int i=0;i<str.length();i++){
if(str.charAt(i) >= 'A' && str.charAt(i) <= 'Z'){
sum += (int)(str.charAt(i) - 'A' +10) * Math.pow(b,str.length()-i-1);
}else {
sum += Integer.parseInt(String.valueOf(str.charAt(i))) * Math.pow(b,str.length()-i-1);
}
}
return sum;
}
public static void Solution(String []arr){
ArrayList<Integer> res = new ArrayList<>();
int Max = Math.max(getMax(arr[0]),getMax(arr[1]));
for(int i =Max+1;;i++){
if(conform(arr[0],i)>24 || conform(arr[0],i)<0){
break;
}
if(conform(arr[1],i)>60 || conform(arr[1],i)<0){
break;
}
res.add(i);
}
if(res.size() == 0){
System.out.print(-1);
}else {
for(int j =0;j<res.size();j++){
System.out.print(res.get(j)+" ");
}
}
}
public static void main(String []args){
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
String arr[] = input.split(":");
Solution(arr);
}
}
对一个HashMap数据结构按value值排序
public class HashMapSort {
public static void main(String []args){
HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("1",1);
hashMap.put("2",5);
hashMap.put("3",3);
hashMap.put("4",10);
hashMap.put("5",7);
System.out.println(hashMap);
List<HashMap.Entry<String,Integer>> list = new ArrayList<>(hashMap.entrySet());
Collections.sort(list, new Comparator<HashMap.Entry<String,Integer>>() {
@Override
public int compare(HashMap.Entry<String,Integer> o1, HashMap.Entry<String,Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
System.out.println(list);
}
}
读入和写入一个text文本文件
import java.io.*;
public class readAndWriteFile {
public static void read(){
String filePath = "input.txt";
try (FileReader reader = new FileReader(filePath);BufferedReader br = new BufferedReader(reader)){
String line;
while((line = br.readLine()) != null){
System.out.println(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void write(){
try {
File writeName = new File("output.txt");
writeName.createNewFile();
try(FileWriter writer = new FileWriter(writeName);BufferedWriter out = new BufferedWriter(writer)){
out.write("zhangchangwei\r\n");
out.write("jiayou\r\n");
out.flush();//缓存区内容压入文件
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String args[]){
read();
write();
}
}
持续更新中