目录
基础篇
一、Get 和 Post 的区别
二、Java 多态的具体体现
三、StringBuffer StringBuilder String 区别
四、==和 equals 区别
五、重写 equals 需要重写 hashCode 吗
六、java 的基本数据类型
七、List, Set, Collection, Collections
八、ArrayList 和 LinedList 区别
九、HashMap 和 Hashtable 区别
十、Forward(请求转发) 与 Redirect (重定向)
十一、switch 默认接受的几种数据类型
十二、冒泡排序
十三、 二分查找法
十四、快速排序
十五、阶乘(递归)
十六、java Exception 体系结构
十七、怎么定义异常规范的?
十八、throw 和 throws 的区别
十九、static 关键字理解
二十、自动装箱 自动拆箱
二十一、 final, finally, finalize 三者区别
二十二、设计模式
一.单例设计模式
二.代理设计模式
三.工厂设计模式
四.适配器模式
二十三、抽象类与接口的区别
二十四、修饰符的作用
二十五、this 和 super 关键字的作用和区别
二十六、Servlet 生命周期及具体实现的三个方法
二十七、反射的作用和原理
二十八、泛型的概述
数据库篇
一、数据库连接池
二、Oracle 分页
三、0racle 中 id、rowid、 rownum 的区别
四、主键和唯一索引的区别?
五、Preparedstatement 和 statement 的区别
六、数据库三范式
七、视图概述
八、存储过程概述
九、索引概述
十、sql 优化
十一、Mysql 哪些情况下索引会失效
十二、where 和 having 的区别
十三、慢查询日志开启方式
十四、悲观锁,乐观锁
十五、SQL 书写顺序及执行顺序
框架篇
二、Spring MVC 运行原理
四、SpringMvc 常用注解
五、springmvc 主要组件
六、SpringMvc 的控制器是不是单例模式
七、过滤器
八、拦截器
九、springmvc 拦截器和过滤器的区别
十、谈谈对 spring 的理解
十一、单例和多例的区别
十二、Autowired 和 Resource 区别
十三、Hibernate 中 get 和 load 的区别
十四、Hibernate 的运行原理
十五、Hibernate 五大核心(类/接口)简述
十六、Hibernate 的三种状态以及状态的转换
十七、Hibernate 的缓存机制
十八、什么是 0RM 框架
十九、mybatis 中的#和$的区别
二十、mybatis 执行流程
二十一、mybatis 中 dao 层是否可可以重载
二十二、Mybatis 的一级、二级缓存:
二十三、JDBC、Hibernate、 Mybatis 区别
二十四、事物的概述
二十六、并发情况下事物产生的问题
二十七、事物的隔离级别
二十八、事物的传播特性
概念二
一、Maven
Redis
RocketMQ
二十. 事务消息流程以及原理
二十一. 事务消息回查实现(了解) :
二十二. 集群模式(了解)
二十三. NameServer:
二十四. 高可靠并发读写服务:
Nginx
Dubbo
SpringBoot
多线程
JVM
Quartz
常用方法
正则表达式
基础篇
一、Get 和 Post 的区别
1. get 是从服务器上获取数据,post 是向服务器传送数据。
2. get 传送的数据量较小,不能大于 2KB,post 传送的数据量较大,一般被默认为不受限制。
3.get 安全性非常低,post 安全性较高。但是执行效率却比 Post 方法好。
4.在进行文件上传时只能使用 post 而不能是 get。
二、Java 多态的具体体现
1.面向对象编程有四个特征:抽象,封装,继承,多态。
2.多态有四种体现形式:接口和接口的继承,类和类的继承,重载,重写
3.其中重载和重写为核心。
重载(Overload) :重载发生在同一个类中,在该类中如果存在多个同名方法,但是方
法的参数类型,个数,顺序不一样,那么说明该方法被重载了。
重写(Override):重写发生在子类继承父类的关系中,父类中的方法被子类继承,方
法名,返回值类型,参数完全一样,但是方法体不一样,那么说明父类中的该方法被子
类重写了。
三、StringBuffer StringBuilder String 区别
1. StringBuilder 执行效率高于 StringBuffer 高于 String.
2. String 是一个常量,是不可变的,所以对于每一次+=赋值 都会创建一个新的对象,
StringBuffer 和 StringBuilder 都是可变的,当进行字符串拼接时采用 append 方法,在原来的
基础上进行追加,所以性能比 String 要高,又因为 StringBuffer 是线程安全的而 StringBuilder
是线程非安全的,所以 StringBuilder 的效率高于 StringBuffer。
3.对于大数据量的字符串的拼接,采用 StringBuffer, StringBuilder。
4.原理:
①String 类不可变,内部维护的 char[]数组长度不可变,为 final 修饰,String 类也
是 final 修饰,不存在扩容。字符串拼接,截取,都会生成一个新的对象。频繁操作字
符串效率低下,因为每次都会生成新的对象。
②StringBuilder 类内部维护可变长度 char[],初始化数组容量为 16,存在扩容,
其 append 拼接字符串方法内部调用 System 的 native 内德吴 方法,进行数组的拷贝,不会重新 生成新的 StringBuilder 对象。非线程安全的字符串操作类,其每次调用 toString 方法而重 新生成的 String 对象,不会共享 StringBuilder 对象内部的 char[],而是会进行一次 char[]的 copy 操作。
四、==和 equals 区别
1.==:在比较基本数据类型的时候,比较的是数据的值:比较引用数据类型时,比较的是
地址值。
2.equals 方法在重写之前,比较的是俩个对象的地址值;在重写之后.比较的是属性值
五、重写 equals 需要重写 hashCode 吗
需要。好比咱们有两个相同值的 User 对象,假如只重写 equals 而不重写 hashcode,那么
User 类的 hashcode 方法就是 Object 默认的 hashcode 方法,由于默认的 hashcode 方法是根据对象的内存地址经哈希算法得来的,那么两者的 hashcode 不一定相等。
六、java 的基本数据类型
数据类型 位 大小
byte (字节) l (8 位)
shot (短整型) 2(16 位)
int (整型) 4 (32 位)
long(长整型) 8(64 位)
float(浮点型) 4 (32 位)
double (双精度) 8 (64 位)
char (字符型) 2 (16 位)
boolean (布尔型) |
附加:
String 是基本数据类型吗? (String 不是基本数据类型)
String 的长度是多少,有限制? (长度受内存大小的影响)
七、List, Set, Collection, Collections
1. List 和 Set 都是接口,他们都继承于接口 Collection, List 是一个有序的可重复的集台,
而 Set 的无序的不可重复的集合。Collection 是集合的顶层接口,Collections 是一个封装了众多关于集合操作的静态方法的工具类,因为构造方法是私有的,所以不能实例化。
2, List 接口实现类有 ArrayList, LinkedList, Vector. ArrayList 和 Vector 是基于数组实现的, 所以查询的时候速度快,而在进行增加和删除的时候速度较慢 LinkedList 是基于链式存储结构,所以在进行查询的时候速度较慢但在进行增加和删除的时候速度较快。又因为 Vector是线程安全的,所以他和 ArrayList 相比而言,查询效率要低。
八、ArrayList 和 LinedList 区别
1. ArrayList 是 List 的一个实现类,他的查询修改效率比较快,因为顺序存储直接通过下标
计算位置定位到需要获取的值,寻址次数较少.他的增加删除比较慢,每当增加或删除元素都需要移动后面的所有元素所以效率比较低. LinkedList 是 List 的一个实现类,他的增删效率
比较高,因为他是节点实现,链式存储 每个节点都记录了上一个节点和下一个节点的地址,
针对于增加和删除 只需要修改前后节点就可以,所以效率比较高.
2. ArrayList 实现原理
ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。随着向
ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,
它的一个扩容是在原来基础上扩大 1.5 倍[int newCapacity = oldCapacity +(ol dCapacity >> 1)] 确定之后就是把老数组 copy 到新数组中.如果可预知数据量的多少,可在构造 ArrayList 时指定其容量.
3. LinkedList 实现原理
LinkedList 数据存储是基于双向链表实现的.
LinkedList 类中有一个内部私有的类Node,这个类就代表了双端列表的节点Node。这个类有三个属性,分别是前驱节点,本节点的值,后继节点。每个节点有两个 reference 指向前驱
节点和后继节点。
对于 LinkedList 来说提供了很多操作头尾节点的方法,好比 linkFirst linkLast 方法,拿 linkFirst 举例,方法中首先用变量 f 来临时保存原有的 first 节点,然后调用的 node 的构造函数新建一个值为 e 的新节点,这个节点插入之后将作为 first 节点,所以新节点的前驱节点为 null,值为 e,后继节点是 f,也就是未插入前的 first 节点。
然后进行了判断,如果 f == null,那就说明插入之前,链表是空的,那么新插入的节点不仅
是 first 节点还是 last 节点,所以我们要更新 last 节点的状态,也就是 last 现在要指向新插入的 newNode。如果 f != null 那么就说明 last 节点不变,但是要更新 f 的前驱节点为 newNode, 最后 size 加一就完成了操作。
九、HashMap 和 Hashtable 区别
1. HashMap 和 Hashtable 都是用于存储键和值的对应关系,都是 Map 的实现类,都是使
用哈希表的方式存储
2. Hashtable 是线程安全,HashMap 是线程不安全的
3. Hashtable 不能存储 null 键和 null 值,HashMap 可以存储 null 键和 null 值。
HashMap 实现原理
①当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0. 75,这是时
间和空间成本上一种折中:
增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内
存空间,但会增加查询数据的时间开销,而查询是最频繁的操作(HashMap 的 get()与 put()
方法都要用到查询) ;减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存
空间。
HashMap 通过键的 hashCode 来快速的存取元素。当不同的对象发生碰撞时,HashMap通过单链表来解决,将新元素加入链表表头,通过 next 指向原有的元素。单链表在 Java 中
的实现就是对象的引用(复合)。
Hash 冲突会造成 HashMap 的查询效率低下?
HashMap 里面没有出现 hash 冲突时,没有形成单链表时,hashmap 查找元素很快,get
()方法能够直接定位到元素,但是出现单链表后,单个 bucket 里存储的不是一个 Entry, 而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry, 直到找到想搜索的 Entry 为止一-如果 恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元系。
ConcurrentHashMap (并发哈希映射)
ConcurrentHashMap 并没有直接实现 map 接口,而是实现 ConCurrentMap 接口,
ConCurrentMap 继承于 map 接口。
1) currentHashMap 使用分段锁的概念,它只会锁操作的那一段数据, 而不是整个
Map 都上锁。
2) ConcurrentHashMap 有很好的扩展性,在多线程环境下性能方面比做了同步的
HashMap 要好,做了同步的 HashMap 和 Hashtable 在并发效率上区别不大,但是在单
线程下 HashMap 的效率高于 ConcurrentHashMap
ConcurrentHashMap 实现原理
一个 ConcurrentHashMap 由多个 segment 组成,每一个 segment 都包含了一个 HashEntry 数组的 hashtable,每一 个 segment 包含了对自己的 hashtable 的操作,比如 get, put, replace 等操作,这些操作发生的时候,对自己的 hashtable 进行锁定。由于每一个 segment 写操作 只锁定自己的 hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个 hashtable 锁定的情况。
Segment 继 承 了 ReentranLock ( 重入锁定 ). 所 以 它 就 是 一 种 可 重 入 锁 (RentrantLock). 在 ConcurrentHashMap, 一个 Segment 就是一个子哈希表,Segment 里维护了一个 HashEntry 数组, 并发环境下,对于不同 Segment 的数据进行操作是不用考虑锁竞争的,(就按默认的Concurrentl eve ( 同时发生的 )为 16 来讲,理论上就允许 16 个线程并发执行)
在 JDK1.8 又将可重入锁修改成了 cas+synchronize (cast synchronize 强制转换同步)???来实现,对于操作同段 Map 的时候。进行竞争同一个锁的概率非常小, 分段锁反而会造成更新等操作的长时间等待.
使用 cas+synchronize 可以将锁的粒度缩小。首先通过 hash 找到对应链表过后,查看是否是第一个object,如果是直接用 cas 原则插入。无需加锁, 如果不是链表第一个 object,则直接用链表第一 个 object 加锁。
十、Forward(请求转发) 与 Redirect (重定向)
1. Forward 是一个请求的延续,可以共享 request 的数据
2. Redirect 开启一个新的请求,不可以共享 request 的数据
3. Forward 转发地址栏不发生变化,Redirect 转发地址栏发生变化
十一、switch 默认接受的几种数据类型
jdk 版本 1.5之前 : short, int, byte, char
jdk 版本 1.5 : , Enum 枚举
jdk 版本 1.7 以上 : String
十二、冒泡排序
算法思想
基本思想是对所有相邻记录的关键字值进行比效,如果是逆顺(a[j]>a[j+1]),则将其交换,
最终达到有序化
public static void main(String[] args) { int[] arr= ( 25, 13, 335, 6, 23 ]; System. out. println (Arrays. toString (bubbleSort (arr))); } private static int[] bubbleSort(int[] nums){ int len = nums.length; if (len = 0 ll len== 1) { return nums ; } for(int i=0;i<1en;i++){ for (int j=0, subLen= len-1-i; j< subLen; j++){ if (nums[j + 1] < nums[j] ) ( int tmp = nums[j + 1]; nums[j + 1] = nums[j]; nums[j] = tmp; } } } return nums; }
十三、 二分查找法
前提条件:
1.存储在数组中(例如一维数组)
2.数组元素为有序
算法思想:
1. 搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程
结束;
2. 如果某---特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较
3. 如果在某一步骤数组为空,则代表找不到.
4. 代码示例
public static int commonBi narySearch(int[] arr, int key) { int low = 0; int high = arr.length 一 1; int middle = 0; if(key < arr[low] 十 key > arr[high] || 1ow > high) { return - 1; } while(1ow <= high) { middle-=(low + high) / 2; if (arr[midd1e] > key) { //比关键字大则关键字在左区域 high = middle - 1; }else if(arr[midd1e] < key) { //比关键字小则关键字在右区域 low = middle + 1; }else { return middle; } } return -1; //最后仍然没有找到,则返回-1 }
十四、快速排序
/** * @param num 排序的数组 * eparam left 数组开始下标值 * @param right 数组结束下标值 */ private static void QuickSort(int[] num,int left,int right){ //如果 left 等于 right,即数组只有一个元素,直接返回 if (left >= right) { return; } //设置最左边的元素为基准值 int key = num[left]; //数组中比 key 小的放在左边,比 key 大的放在右边,key 值下标为 i int i = left; int j = right; while(i<j) {//j 向左移,直到遇到比 key 小的值(从后向前比较) while (num[j]>=key & i<j){ j--; } //交换位置 num[i] = num[j]; //i 向右移,直到遇到比 key 大的值(从前向后比较) while (num[i]<=key && i<j) { i++; } //交换位置 num[j] = num[i]; /** *此时第一次循环比较结束,基准值的位置已经确定了。 *左边的值都比基准值小,右边的值都比基准值大,但是两边的顺序还 *有可能是不一样的,进行下面的递归调用 */ num[j]=key; //递归,再对左半部分排序 QuickSort (num, left, i-1); //递归,再对右半部分排序 QuickSort (num, i+1, right); }
十五、阶乘(递归)
前提条件
1、有反复执行的过程(调用自身)
2、有跳出反复执行过程的条件(递归出口)
算法思想
1.程序调用自身的编程程技巧(自己调用自己)
代码示例
public static int multiply(int num) { if(num<0){ System.out.print1n("请输入大于 0 的数! "); return -1; } else if (num== 0 || num==1){ return 1 ; } else { return multiply(num 一 1) * num; public static void main(String[] args) { System.out.println(multiply(10)); }
十六、java Exception 体系结构
1.体系图
(1) Throwable
①Error 错误
②Exception 异常
1) RuntimeException 运行时异常
I0Exception、FileNotFoundException、SQLException
2.在 Java 的异常体系里有一个所有异常和错误的超类 Throwable,这个超类有两个直接子
类,一个表示错误的子类 Error,另一个表示异常的子类 Exception。其中这个异常类 Exception又分为运行时异常 RuntimeException 和非运行时异常 I0Exception,
在运行时异常里都是 RuntimeException 类及其子类异常,例如空指针异常 NullPointerException,下标越界异常 IndexOutBoundsException, 未 找到 类异 常 ClassNotPoundException 异 常, 未知 类 型异 常 UnknowTypeException,这些异常都是不检查异常,程序可以选择捕获处理,也可以不处理。 往往这些异常是程序逻辑错误引起的,所以应该尽可能避免这类异常的发生;还有那个错误 子 类 Error, Error 是 程 序 无 法 处 理 的 错 误 , 它 是 由 JVM 产 生 和 抛 出 的 ,
比 如 说 OutOfMemoryError(内存不足错误)、ThreadDeath (线程死亡)等等。
十七、怎么定义异常规范的?
尽量避免大段代码进行 try-catch, catch 时分清稳定代码和非稳定代码,对非稳定代码
的 catch 尽可能的进行区分异常类型.也可以进行自定义异常,对异常进行统一封装,让整个项
目的异常处理更规范统一,同时使日志记录更加清晰,便于排查问题。
十八、throw 和 throws 的区别
1. throw 是对异常对象的抛出, throws 是对异常类型的声明,一旦用了 throw 关键字,就
一定有一个异常对象出现;
2. throws 是对可能出现的异常类型的声明,即使声明了一些异常类型,在这个方法中,也可
以不出现任何异常
3. throw 后面只能跟一个异常对象, throws 可以跟很多个异常类型
十九、static 关键字理解
1. static 可以修饰变量,修饰方法,修饰代码块, static 修饰后是属于类的,所以只加载一
次, 他们的加载时机是随着类的加载而加载进入方法区, 内存中只分配一块区域供所有类使
用,可以节省空间。
二十、自动装箱 自动拆箱
1.自动装箱和拆箱(在 jdk1. 5 之后)
自动装箱:可以直接使用基本类型的数据,给引用类型变量赋值
自动拆箱:可以直接使用包装类对象,给基本类型变量赋值
二十一、 final, finally, finalize 三者区别
1.final 是一个修饰符:
当 final 修饰一个变量的时候,变量变成一个常量,它不能被二次赋值
当 final 修饰方法时,该方法不能被重写
当 final 修饰类时,该类不能被继承
2.fnally:
Finally 只能与 try/catch 语句结合使用,finally 语句块中的语句一定会执行,并且会在
return, continue,break 关键字之前执行
3.finalize:
Finalize 是一个方法,属于 java.lang.0bject 类,finalize()方法是 GC (garbage collector 垃圾回收)运行机制的一部分,finalize()方法是在 GC 清理它所属的对象时被调用的。
二十二、设计模式
一.单例设计模式
单例就是该类只能返回一一个实例。
单例所具备的特点:
1.私有化的构造函数
2.私有的静态的全局变量
3.公有的静态的方法
单例分为懒汉式、饿汉式和双重判定锁
饿汉式:
public class Singleton1 { private Singleton1() {}; private static Singleton1 single = new Singleton1(); public static Singleton1 getInstance() { return single; } }
懒汉式:
public class Singleton2private Singleton2() {} private static Singleton2 single=null; public static Singleton2 getInstance() { if (single == null) { single = new Singleton2() ; } return single; } } 线程安全:(双重判定锁) public class Singleton3 { private Singleton3() {} private static Singleton3 single publice static singleton3 getInstance(){ if(null == single) { synchronized(single ){ if(null == single) { single = new Singleton3(); } } } return single; } }
二.代理设计模式
代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对
象
代理设计模式所具备的特点
1.抽象角色
2.真实角色
3.代理角色
代码示例
public interface TeacherService{ void teach(); }
接口的真实实现:真实角色
public class TeacherServiceImp1 imp1 ements TeacherService{ @Override public void teach(){ System.out.print1n(”教学方法”);} } 接口的代理实现: private TeacherServiceImpl t = new TeacherServiceImp1(); @Override public void teach(){ System.out.println("开启事物"); //执行逻辑代码 t.teach() ; System.out.print1n("提交事物"); } 测试 @Test public void test(){ TeacherService t = new StaticProxy() ; t.teach() ; }
三.工厂设计模式
工厂设计模式中,我们在创建对象时,不会对客户端暴露创建对象的逻辑
//接口类 public interfaceShape f void draw(); //实现类 1 public class Rectangle implements Shape{ @Override public void draw(){ System. out. print1n(”Inside Rctangte:draw0) method.”); } } //实现类 2 public class Square eimplements Shape l @Override public void draw(){ System. out. println(" Inside Square::draw(0 method."); } } //创建工厂类 public class ShapeFactory{ //使用 getShape 方法获取形状类型的对象 public Shape getShape(String shapeType){ if (shapeType == null){ return null;} if (shapeType equalsIgnoreCase("CIRCLE")){ return new Circle() ; }else if (shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); }else if (shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } } //测试 public static void main(String[] args) ( ShapeFactory shapeFactory = new ShapeFactory(); //获取 Rectangle 的对象,并调用它的 draw 方法 Shape shape2 = shapeFactory.getShape("RECTANGLE"); shape2. draw() ; //获取 Square 的对象,并调用它的 drawep 方法 Shape shape3 = shapeFactory.getShape("SQUARE"); shape3. draw () ; }
四.适配器模式
适配器模式解决接口与接口实现类之间继承矛盾问题(在编程实现里面:当一个接口实
现另一个接口的时候,有义务把接口中的方法都实现)
特征:
使用抽象类分离了接口与接口实现
抽象类分摊接口中的方法
使得接口可以随意的选择接口中的的方法米实现
代码示例
创建一个学生守则接口;
public interface 学生守则接口{ public void 考试后家长签字(); public void 每周免费看一次电影(); } 创建-个零食商贩(小学生干爹) public abstract class 零食小商贩 implements 学生守则接口{ @Override public void 考试后家长签字(){ System.out.println("家长签字”); } }创建一个小学类 public class 小学生 extends 零食小商贩{ @Override public void 每周去看一次电影(){ System.out.println("去看电影"); } } 定义目标类,通过小学生类调用所需要的方法 public classTestMain { public static void main(String[] args){ 学生守则接口 target = new 小学生(); target.考试后家长签字(); target.每周免费看一次电影(); } }
二十三、抽象类与接口的区别
1.一个类只能进行单继承,但可以实现多个接口。
2.有抽象方法的类,一定是抽象类,但是抽象类里面不一定有抽象方法。
3.接口里面所有的方法的默认修饰符为 public abstract, 接口里的成员变量默认修饰符
为:static final
二十四、修饰符的作用
修饰符分为访问修饰符(决定访问权限)与非访问修饰符(修饰属性)两种。它可以修饰类、包的成员(变量和方法),他们的适用范围各不相同。
- 修饰符的种类:
- 访问修饰符:public(同一类、同一包中的类、子类、不同包中的类都可以访问;可修饰所有)、protected(同一类、同一包中的类、子类包含不同包中的子类都可以访问;可修饰变量与方法) 、default(同一类、同一包中的类可以访问;可修饰所有) 、private(同一类可以访问;可修饰变量与方法)
- 非访问修饰符:abstract(无法进行实例;可修饰方法与类) 、final (最终的;修饰所有)、throw
- 修饰符的适用范围
- 类的修饰符:public、default、abstract、final。同一类、同一包中的类、子类、不同包中的类都可以访问;default:同一类、同一包中的类可以访问;abstract:抽象类;final:没有子类;throw:不做讲解
- 变量的修饰符:四种访问修饰符、static、final。static修饰时会使变量变成静态变量,初始化时被赋值。final修饰时会使变量变成常量,需进行赋值。
- 方法的修饰符:与变量相同。statuc修饰时方法为静态方法,可通过类名直接调用;abstrct修饰的方法没有方法体即执行语句,且必须在抽象方法中。例 (public abstract void sleep();) ; final表示此方法不能被子类重写。
- 属性的修饰符:四种访问修饰符、static。
总结:
抽象类不一定有抽象方法,但有抽象方法的类一定是抽象类。
final方法一定没有子类,但有final方法的类不一定没有父类。
当有多种修饰符修饰同一物体时,效果为叠加效果,如private static foreach(),它为私有的、静态方法,其它类调用时可以通过该方法所属类.foreach()调用。
protected、private、static不能修饰类。类的修饰符只有四种,其中default可不用写,也就是说只能右以下4种形式出现:class、public class、abstract class、final class。
二十五、this 和 super 关键字的作用和区别
1.this 关键字表示本类当前对象的引用
(1)哪个对象调用 this 所在的方法,this 就表示哪个对象
2. super 关键字表示本类当前对象的父类引用
(1)哪个对象在调用 super 所在的方法,super 就表示哪个对象中的父类中的成员
3. this 访问成员时,既可以访问当前类也可以访问父类, super 只能访问父类
二十六、Servlet 生命周期及具体实现的三个方法
- 实例化对象 init() 方法
当请求第一次访问的时候进行实例化, 就是这个服务器被第一次访问的时候, 在 servlet 初始化时调用 init() 方法,而且只被调用一次
2.调用工作方法 service() 方法
每次请求都会调用该方法,该方法进行获取用户请求方式,通过用户请求方式调用相对应
的 doGet() 或者是 doPost() 方法
3.销毁 destory()方法
当容器检测到一个 Servlet 实例应该从服务中被移除的时候,容器就会调用实例的
destroy()方法
二十七、反射的作用和原理
1.反射机制其实就是指程序在运行的时候能够获取自身的信息。如果知道一个类的名称
或者它的一个实例对象,就能把这个类的所有方法和变量的信息(方法名,变量名,方法,
修饰符,类型,方法参数等等所有信息)找出来。如果明确知道这个类里的某个方法名+参数
个数类型,还能通过传递参数来运行那个类里的那个方法
2.当然,在平时的编程中,反射基本用不到,但是在编写框架的时候,反射用的就多了,
比如咱们要使用某一个类进行操作,但是这个类是用户通过配置文件配置进来的,你需要先
读配置文件,然后拿到这个类的全类名:比如 test. Person,然后在利用反射 API 来完成相应的
操作。
二十八、泛型的概述
从 JDK1.5 开始提供泛型的概念,泛型实质上就是使程序员定义安全的类型。在没有出
现泛型之前,java 也提供了对 Object 的引用任意化操作,这种任意化操作就是对 0b. jet 引
用进行向下转型”及“向上转型”操作,但某些强制类型转换的错误也许不会被编译器捕捉,
而在运行后出现异常,可见强制类型转换存在安全隐患。
咱们也可以自定义泛型类,在声明该类对象时可以根据不同的需求指定<T>真正的类型,
而是使用 ( 就是 )在声明泛型类对象时设置的数据类型。
泛型擦除
Java 的泛型是伪泛型。为什么说 Java 的泛型是伪泛型呢,他只是在咱们编译的时候进
行检测,那么咱们就可以借助反射动态的去获取操作方法,实现泛型擦除
数据库篇
一、数据库连接池
数据库连接池的优点:
在我们不使用数据库连接池的时候,每次访问数据库都需要创建连接,使用完成之后需
要释放关闭连接,而这样是很耗费资源的。使用数据库连接池的时候,在 tomcat 启动的时候就创建了指定数量的连接,之后当我们程序使用的时候就直接从连接池里面取,而不需要
创建,同理,当我们使用完的时候也不需要关闭连接,而是将连接返回到连接池中,供其他
请求继续使用。大大提供了数据库连接的利用率,减小了内存吞吐的开销。
数据库连接池运行原理
建立连接池对象(服务启动)。按照事先指定的参数创建初始数量的连接(即:空闲连接数)。
对于一个访问请求,直接从连接池中得到一个连接。如果连接池对象中没有空闲的连接,且
连接数没有达到最大(即:最大活跃连接数),创建一个新的连接;如果达到最大,则设定一定的
超时时间,来获取连接。运用连接访问服务。访问服务完成,释放连接到空闲队列中
二、Oracle 分页
select 水 from (select * from (select s.水, rownum rn from students ) where rn<=5) where
rn>0
三、0racle 中 id、rowid、 rownum 的区别
1. rowid 物理位置的唯一标识。 而 id 是逻辑上的唯一-标识, 所以 rowid 查找速度要
快于 id,是目前最快的定位-条记录的方式
2. rowid 和 rownum 都是”伪数列^,所谓“伪数列”也就是默认隐藏的一个数列。rownum
用于标记结果集中结果顺序的一个字段,它的特点是按顺序标记,而且是连续的,换句话说
就是只有有 rownum=1 的记录,才可能有 rownum=2 的记录。rownum 关键字只能和<或者<=
直接关联,如果是>或者=则需要给他起个别名
四、主键和唯一索引的区别?
创建主键的同时会生成对应的唯一索引,主键在保证数据唯一性的同时不允许为空,而
唯一可以有一个为空数据项,一个表中只能有一个主键,但是一个主键可以有多个字段,一
个表中可以有多个唯一索引。
五、Preparedstatement 和 statement 的区别
PreparedStatement 是预编译的,而 statement 不是,如果执行相同 sql 但执行次数大于
1,那么每次执行 sql 语句 statement 都要重新编译一次,而 PreparedStatetement 不
用,PreparedStatement 的运行效率大于 statement;PreparedStatemented 可以大大提高程序的 安全性,PreparedStatement 是用“?”传参,可以防止 sq1 注入,具有安全性,而因为 statement 用的是“+”字符串拼接,安全性较低。
六、数据库三范式
1.第一范式:数据库表中的所有字段值都是不可分解的原子值。
2.第二范式:需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相
关 (主要针对联合主键而言)
3.第三范式:需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关
七、视图概述
1.视图可以视为“虚拟表”或“存储的查询”,创建视图所依据的表称为“基表”
2.视图的优点:
①提供了另外一种级别的表安全性:隐藏了一些关键的字段
②简化的用户的 SQL 命令
③隔离基表结构的改变
八、存储过程概述
1.存储过程(Stored Procedure) ,可以包含逻辑判断的 sql 语句集合。是经过预编译,存在于数据库中。通过调用指定存储过程的名字(可有参,可无参)来执行。
2.优点:
①简化了复杂的业务逻辑,根据需要可重复使用
②屏蔽了底层细节,不暴露表信息即可完成操作
③降低了网络的通信量, 多条语句可以封装成一个存储过程来执行
④提高执行效率, 因为他是预编译以及存储在数据库中
3.缺点:
①可移植性差,相同的存储过程并不能跨多个数据库进行操作
②大量使用存储过程后,首先会使服务器压力增大,而且维护难度逐渐增加
4.存储过程的语法:
①创建
1) create procedure sp_ name ()
2) begin
3).......
4) end
②调用
1) call sp_name ()
5.注意:
①存储过程之间可相互调用
②存储过程一般修改后,立即生效。
九、索引概述
1.索引的概念
索引就是为了提高数据的检索速度。数据库的索引类似于书籍的索引。在数据库中,使用索引可以迅速地找到表中的数据,而不必扫描整个数据表.
2.索引的优点
①创建唯一性索引,保证数据库表中每一行数据的唯一性
②大大加快数据的检索速度,这也是创建索引的最主要的原因
③减少磁盘 I0 (向字典一样可以直接定位)
3.索引的缺点
①创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
②索引需要占用额外的物理空间
③当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据
的维护速度
4.索引的分类
普通索引和唯一性索引
1)普通索引
a. CREATE INDEX mycolumn_index ON mytable (myclumn)
2)唯一性索引: 保证在索引列中的全部数据是唯一一的
a. CREATE unique INDEX mycolumn_index 0N mytable(myclumn)
单个索引和复合索引
1)单个索引: 对单个字段建立索引
2)复合索引: 又叫组合索引,在索引建立语句中同时包含多个字段名,最多 16 个字段
a. CREATE INDEX name_ index 0N user Info (firstname, lastname)
顺序索引,散列索引,位图索引
十、sql 优化
1.不建议使用%前缀模糊查询
①LIKE “%name”或者 LIKE“%name%”,这种查询会导致索引失效而进行全表扫描。但是可
以使用 LIKE“ name%"。
②如何解决这个问题?
1)使用全文索引(FULLTEXT INDEX), MySQL 中的 MyISAM 存储引擎支持,InnoDB 从
MySQL5.6 版本开始对其支持
2.对于联合索引来说,要遵守最左前缀法则
联合索引含有字段 id、name、 school, 可以直接用 id 字段,也可以 id、name 这样的顺
序,但是 name 或者 school 都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面
3.避免在 where 语句中对字段进行 null 值判断
对于 null 的判断会导致引擎放弃使用索引而进行全表扫描
4 如果排序字段没有用到索引,就尽量少排序
5.避免在 where 语句中对字段进行表达式操作
6.尽量避免在 where 语句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描
7.in 和 not in 也要慎用,否则会导致全表扫描
如: select id from t where num in(1, 2,3) .
对于连续的数值,能用 between 就不要用 in 了
1) select id from t where num between 1 and 3
8.任何地方都不要使用 select *
9.查询总条数使用 count (1)或 count(*) , count(字段)会过滤 null 值,所以会进行判断然后把值取出进行累加,而 count(1)忽略了取值直接放置一个 1 然后进行累加,count(*)和 count(1)效率差不多,因为 mysq1 对于 count(*) 做了一些优化
10.使用合理的分页方式提高分页效率
select id, name from product limit 80000, 20
1) 会检索 80000 + 20 条数据,然后选择最后 20 条
优化的方法如下:可以取前一页的最大行数的 id,然后根据这个最大的 id 来限制下一页的
起点
1) select id, name from product where id> 79999 limit 20
11.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及
update 的效率。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列
上创建的索引是否有必要
十一、Mysql 哪些情况下索引会失效
1. where 语句中使用了 IS NULL 或者 IS NOT NULL.会造成索引失效
2.在 where 中使用 or 时,有一个列没有索引,那么其它列的索引将不起作用
3. like 查询是以%开头,索引失效
4.组合索引中不符合”最左前缀”,索引失效
5.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
6.where 语句中使用<>和!=
7.where 语句中对字段表达式操作
8. where 语句中使用 Not In
十二、where 和 having 的区别
1. where 是在分组前对条件进行过滤
2. having 是在分组后对条件进行过滤
3. sql 中有 where 和 having 和 group by 共存的时候,顺序是 where group by having
十三、慢查询日志开启方式
1.在配置文件 my.cnf 或 my.ini 中在[mysqld]-行下面加入两个配置参数
log-slow-queries/data/mysqldata/slow -query.log
long_query_time=2
①注:
1) log-slow-queries 参数为慢查询日志存放的位置
2) long_query_time=2 中的 2 表示查询超过两秒才记录
十四、悲观锁,乐观锁
1 悲观锁(Pessimistic Lock),每次去查询数据的时候都认为别人会修改,所以每次在查询
数据的时候都会,上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据
库里边就用到了这种锁机制,比如通过 select .... for update 进行数据锁定。
2.乐观锁(OptimisticLock),每次去查询数据的时候都认为别人不会修改,所以不会上锁,
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号,时间
戳等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
十五、SQL 书写顺序及执行顺序
1.书写顺序
select-> from 表名-> join-> on 条件-> where 条件-> groupby 分组-> _having 条件-> order by 排序-> limit 分页
2.执行顺序
from-> join-> on~-> where -> group by -> having -> select-> order by -> limit
框架篇
- Struts2 的运行原理
1、tomcat 启动的时候会加载 web.xml,核心控制器并解析 struts.xml
2、客户端会发送一个请求到 action、核心控制器根据后缀名进行拦截
3、核心控制器根据 struts.xml 的配置文件信息找到 action 对应的方法
4、执行相关的业务逻辑最后返回一个 String
5、通过<action/> 里配置<resul t/> name 的属性值与返回的 String 进行匹配,跳转到指定的 jsp页面
二、Spring MVC 运行原理
1.客户端发送 http 请求,web 服务器接收到这个请求,如果能匹配到 DispatcherServlet 的映射路径 web.xml 文件,web 容器交给 DispatcherServlet 进行处理
2. DispatcherServlet 根据请求信息去找 HandlerMapping(处理器映射器), HandlerMapping 通过对比映射,成功向 DispatcherServlet 返回一个 Handler 对象
3. DispatcherServlet 继续将请求转发给 HandlerAdapter (处理器适配器),处理器适配器根据包装好的内容找到对应的资源对象,然后调用 controller 对应的方法
4. controller 完成业务逻辑处理后,返回一个 ModelAndView 给前端控制器
5.前端控制器将 ModelAndView 传递到 ViewResolver (视图解析器)进行解析 ModelAndView,找到相对应的视图
6.前端控制器将 ModelAndView 中的数据模型进行页面的视图渲染
三、SpringMVC 和 Struts2 区别
1. springmvc 单例非线程安全,struts2 线程安全对每个请求都产生一个实例
2. spring mvc 的 入 口 是 servlet 而 struts2 是 filter 。 spring mvc 的 前 端 总 控 制 器 为
DispatcherServlet,struts2 的前端总控制器为 Filter
3.参数传递:struts 是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个个
方法共享的。spriningmvc 用方法来接受参数
4. spring mvc 是基于方法的设计,而 sturts 是基于类
四、SpringMvc 常用注解
1. @RequstMapping: 用于处理请求地址映射,可以作用于类和方法上。
2. @RequestParam: 用于获取传入参数的值
3. @PathVairiable: 用于定义路径参数值
4. @ResponseBody: 可以将整个返回结果以 json 格式返回
5. @ModelAttribute:把参数保存到 model 中,可以注解方法或参数
6.@RestController:相当于@ResponseBody 和 Controller 的结合
五、springmvc 主要组件
1.核心控制器:Dispatcherservlet
2.处理器映射器:HandlerMapping
3.处理器适配器:HandlerAdapter
4.后台控制器:Controller
5.封装模型信息和视图信息:ModelAndView
6.视图解析器:ViewResolver
六、SpringMvc 的控制器是不是单例模式
1 springmve 默认是单例模式,所以在多线程访问的时候有线程安全问题,
不要用同步,会影响性能
2.咱们在控制器里面不写成员变量,就可以避免问题发生
七、过滤器
1. Filter :依赖于 servlet 容器,是 java 中预先定义好的接口,可以过滤不同的内容,具体怎
么过滤,需要咱们定义一个实现类.然后实现接口中的过滤方法,在方法中书写过滤条件。
filter 是对客户端访问资源的过滤,符合条件放行,不符合条件不放行
2.filter 的生命周期
①实例化-->初始化(init)-->进行过滤(doFiler)-->销毁(destroy)-->释放资源
- 在项目中我们通常通过 filter 进行编码转换,进行安全验证,进行重复提交的判断。
八、拦截器
依赖于 web 框架,在 SringMVC 中就是依赖于 SringMVC 框架。在实现上基于 Java 的反射机制,属于面向切面编程(aop)的一种运用,由于栏截器是基于 web 框架的调用,因此
可以使用 Spring 的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个 controller
生命周期之内可以多次调用
九、springmvc 拦截器和过滤器的区别
1.拦截器( Interceptor)是基于 Java 的反射机制,而过滤器(filter)是基于函数回调。过滤器过滤所有请求,而拦截器只拦截 controller 方法,从灵活性上来说拦截器功能更强大些,Filter 能做的事情它都能做
2.拦截器可以获取 IOC 容器中的各个 Bean,而过滤器不行,而且拦截器可以在请求前,请
求后执行,比较灵活。Filter 主要是针对 URL 地址做一个编码的事情,过滤掉没用的参数、
安全校验等
十、谈谈对 spring 的理解
Spring 是一个设计层框架,有两个核心,一个核心是 I0C (控制反转),它是基于工厂设
计模式。所谓控制反转就是将自己手工完成对象创建(new)的这种任务交给 spring 容器去完
成。和控制反转搭配使用的还有一个 DI 也就是依赖注入。我们可以进行造函数注入,属性
注入等,最常用的还是属性注入。可以注入各种类型 Map, List, properties。 注入可以通过
ByType 和 ByName 分别按照类型和名字进行自动注入。Spring 中的 Bean 支持单例和原型两种方式,默认是单例的。可以通过. scope=" singleton”,scope=" prototype”来配置。所谓单例: 即至始至终在 jvm 里都只有一个该类的实例。所谓原型:也叫多例,就每次都会创建一个新的对象实例。另一个核心是 AOP(面向切面编程/面向方面编程),AOP 是 OOP(面向对象编程)的延续,主要应用于日志记录,性能统计,安全控制,事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别有一个真实实现和一个代理实现,而动态代理分为基于接口的 jdk 的动态代理和基于类的 cglib 的动态代理,咱们正常都是面向接口开发,所以 Aop 使用的是基于接口的 jdk 的动态代理。 好比我们使用的事物管理,就是通过这样一个动态代理,对所有需要事务管理的 Bean 进行
加载,并根据配置在 invoke 方法中,对当前调用的方法名进行判定,并在 method.invoke 方法前后为其加上合适的事务管理代码,这样就实现了 Spring 式的事务管理.
十一、单例和多例的区别
1.所谓单例就是所有的请求都用一个对象(实例)来处理,比如我们常用的 service 和 dao
层的对象通常都是单例的,而多例则指每个请求用一个新的对象(实例)来处理
①单例模式和多例模式说明:1)单例模式和多 例模式属于对象模式。
2)单例模式的对象在整个系统中只有一份,多例模式可以有多个实例。
3)它们都不对外提供构造方法,即构造方法都为私有。
2.为什么用单例、多例:
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费内存又浪费CPU
之所以用多例,是防止并发问题,即一个请求改变了对象状态,此时对象又处理另一个
请求,而之前请求对对象状态的改变导致对象对另一个请求做了错误的处理
十二、Autowired 和 Resource 区别
1. @Autowired
①Autowired是spring 中的注解,默认按照 byType 进行注入,如果当前type类型对象大于1时按照byName。如果Autowired 声明 byName注入,需要和Qualifier 注解一起使用
2. @Resource
①Resource 是 java 中提供的注解,默认按照 byName 进行注入,如果注入失败则按照
byType 注入
十三、Hibernate 中 get 和 load 的区别
1. get 立即加载,当调用 get,方法时,会立即发送 sql 语句
2. load 延迟加载,当调用 load 方法时,不会立即发送 sq1 语句,而是在使用当前对象时才
会发送 sql 语句
3. get 和 load 对于返回值为空时报错信息不同, get 报错空指针异常, load 报错类未找到异常
十四、Hibernate 的运行原理
首先通过 configuration 去加载 hibernate.cfg.xml 这个配置文件,根据配置文件的信息去
创建 sessionFactory, sessionFactory 是线程安全的,是一个 session 工厂,用来创建 session,
session 是线程不安全的,相当于 jdbc 的 connection,最后通过 session 去进行数据库的各种操作,在进行操作的时候通过 transaction 进行事务的控制,然后关闭 session 以及 sessionFactory
十五、Hibernate 五大核心(类/接口)简述
1. Configuration
到 src 下找到一个名字为 hibernate.cfg.xml 的配置文件,并将文件内容封装到当前对象当中
2. SessionFactory
用来创建 Session 对象的,它里面封装了数据库的配置信息以及所有的映射文件
3. Session它提供了一系列与持久化相关的操作,如增加,修改,删除,查询等等. Session 的话是一个线程不安全的对象
4. Transaction
负责对于数据库事物的一个管理、如开启事物,提交事务,事物回滚等
5. Query
负责对于数据库的查询操作, Query 对象封装了 HQL,查询语句
十六、Hibernate 的三种状态以及状态的转换
1. Transient (临时):对象中没有 id 并且对象与 session 无关(如添加操作) ;
2. Persistent (持久化):对象里存在了 id 并且与 session 有关(如查询,修改操作)
3. Detached(托管/游离):session 关闭后,对象与 session 无关(删除操作)
十七、Hibernate 的缓存机制
首先 hibernate 分为了一级缓存和二级缓存,默认是开启一级缓存,一级缓存的范围是
session 级别的,从 session 的开启到关闭.使用一级缓存的话可以自动更新数据,不需要调用update 方法
hibernate 二级缓存的话范围比较大,是 sessionFactory 范围,他的性能损耗比较大,所以
正常情况下咱们也不去使用他,而是借助第三方缓存技术,比如 redis
十八、什么是 0RM 框架
0RM(对象关系映射)它的作用是在关系型数据库和业务实体对象之间作一个映射,这样
我们在具体的操作业务对象的时候,就不需要再去和复杂的 SQL 语句打交道,只要像平时操
作对象一样操作它就可以了。比如咱们的 hibernate 和 mybatis 就是一个 orm 框架
十九、mybatis 中的#和$的区别
1. #将传入的数据根据类型进行相应的转换,如果类型不匹配则报错。如果传入的是字
符串类型则会自动加上双引号。
2. $将传入的数据直接拼接在 sql 中。
3.#方式能够很大程度.比防止 sql1 注入,$方式无法防止 Sql 注入,所以一般能用#的就
别用$。
二十、mybatis 执行流程
1.项目启动加载 mybatis-config.xml, 读取 mybatis 运行环境,并且加载 mybatis 的 sq1 映射文件(配置操作数据库的 sql 语句和映射关系)
2.通过配置文件创建 SqlSessionFactory 工厂,用来创建 SqlSession
3.通过 SqlSession 来操作数据库的 crud
4.SqlSession 执 行 是 通 过 mybatis 底 层 定 义 的 Executor 执 行 器 来 执 行 sql 语句 (MappedStatement)
5.然后执行器执行 mybatis 底层封装的MappedStatement 对象.当前对象封装了 sql 映射的信息和输入、输出结果信息
(xxMapper. xm1 文件中一个 sql 对应一个 MappedStatment 对象)
二十一、mybatis 中 dao 层是否可可以重载
不可以,dao 层中接口的方法名与与 xml中的标签 id 值是一致的,所以一旦出现方法重
载之后就会出现相同的方法名,xml 中的 id 也出现了重名的情况,一旦运行系统就会报错 .
Mapper 接口的全限名,就是映射文件中的 namespace 的值,接口的方法名就是映射
文件中 MappedStatement 的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯
一定位一个 MappedStatement,因为是使用全限名+方法名的保存和寻找策略。
Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Mapper 接口生成代理对象 Proxy,代理对象会拦截接口方法转而执行 MapperStatement 所代表的 sql, 然后将 sql 执行结果返回
二十二、Mybatis 的一级、二级缓存:
它只开启一级缓存,一级缓存只是相对于同一个 Sq1Session 而言。所以在参数和 SQL
完全一样的情况下,我们使用同一个 SqlSession 对象调用个 Mapper 方法,往往只执行一次SQL,因为使用 SelSession 第一次查询后 ,MyBatis 会将其放在缓存中,以后再查询的时候, 如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession 都会取出当前缓存的数据, 而不会再次发送 SQL 到数据库。SqlSessionFactory 层面上的二级缓存默认是不开启的,范围较大,一般借助第三方缓存技术,比如 redis
二十三、JDBC、Hibernate、 Mybatis 区别
Hibernate 属于全自动,Mybatis 属于半自动,Jdbc 属于手动,从开发效率上讲 hibernate
较高, Mybatis 居中,jdbc 较低,从执行效率上讲 hibernate 较低,Mybatis 居中,jdbc 较高,因为 jdbc 是纯手工编写 sql 语句,开发效率极低,Mybatis 对 sql 的控制能力相对灵活,可以根据业务需要进行优化,而 hibernate 因为高度封装所开发效率相对较高,但正因为这个原因,所以程序员在对 sql 语句的控制和优化方面相对比较弱。
二十四、事物的概述
1.一个事物是由一个单独单元内一个或多个sql语句组成:这个单元中的每个SQL语向都是相互
相依赖的,单元作为一个整体是不可分割的。如果单元中的一个语句不能够成功,完成,整
个单元就会回滚,所有影响到的数据将返回到事物开始以前的状态。因此,只有事务中的所
有语句都成功执行,才能说明这个事务被成功执行。
二十五、事物的四大特性
1.原子性(atomicity) :将事务中所做的操作捆绑成一个原子单元,即对事务所进行的事务修改
等操作,要么都执行要么都不执行
2.一致性(Consistency); 事务在完成时,必须所有的数据都保持
3.隔离性(Isolation):事务与事务直接是隔离开的,事务正确提交之前,它可能的结果不应该
显示给其他事务
4.持久性(Durability):事务正确提交之后,结果将永远保存在数据库之中
二十六、并发情况下事物产生的问题
1.脏读:一个事物读到另一个 事物没有提交的数据
2.不可重复读:一个事物范围两个相同的查询却返回了不同的数据
3.幻读:一个事物读第一次取到的数据比后来读取到的数据条自少或多
二十七、事物的隔离级别
1.读操作未提交(ISOLATION READ _UNCOMITTED):无法避免脏读,幻读,不可重复读
2.读操作已提交(ISOLATION_ _READ_ COMMITTED):可以避免脏读,但是无法避免幻读,不可重
复读
3.可重复读(ISOLATION_ REPEATABLE _READ) : MySQL 的默认隔离级别
说明事物保证能够再次读取相同的数据,能够避免脏读,不可重复读,不可以避免幻读
4、串行化(ISOLATION_ _SERIALIZABLE)
提供了非常严格的事物隔离,能够保证任何并发情况下的问题发生,但是效率最低,因
此没人使用
二十八、事物的传播特性
1. PROPCGATION REQUIRD:如果当前没有事务,就新建一个事务,如果有就加入到这个事务
中。(常用)
2. PROPAGATION_ SUPPORTS:支持当前事务,如果当前没有事务,就以非事务执行
3. PROPAGATION MANDATORY:使用当前事务,如果当前没有事务,就抛出异常
4. PROPAGATION_ REQUIRES NEW:新建事务,如果当前存在事务,就把当前事务挂起
5. PROPAGATION _NOT_ _SUPPORTED:以非事名 方式执行, 如果当前存在事务,就把当前事
务挂起
6. PROPAGATION NEVER: 总是以非事各方式执行,加果当前存在事务,就抛出异常
7. PROPAGATION _NESTED:如果当前存在事务,就嵌套在当前事务内执行。
概念二
一、Maven
maven 主要用于 jar 包管理,模块管理,创建 maven 项目可以实现多模块的分类,首先
maven 有一一个仓库的概念,仓库中存放着我们所需要的 jar 包,当我们每次在 pom.xml 中
添加或者修改 maven 坐标时,它就会去咱们配置的服务器去下载 jar 包,maven 中也可以自
己去创建私服,私服的作用是当我们增加 jar 包时,maven 会先去我们私服中去寻找 jar 包,
私服中没有的话再去指定的服务器下载,我们也可以向私服中上传自己的 jar 包,并生成
maven 坐标,以便于我们的使用,好处的话其实就是更好的对模块进行划分,和对 jar 包的
管理,减轻项目的大小,提高项目的可维护性
Redis
- Redis 为什么快
相关问题:1、 Redis 为什么性能高?
2、 redis 是单线程的,为什么还快,单线程
不应该慢才对吗?
答: Redis 是一个非常优秀的、高性能的 NoSQL 数据库,它之所以效率高,我认为主
要有那么几点:
1、首先,它是纯内存数据库,在内存中存取数据相当快;
2、其次,它存储的是 key-value 结构,这种哈希方式查找速度非常快,每秒数
据百万级没什么问题;
3、Redis 虽然是单线程,但它采用了 Linux 系统 epoll 技术,实现网络 IO 多路复用技
术,保证在多连接时系统的高吞吐量,提高了性能。Epoll 是目前最高效的能降低在高并发
少活跃情况下 CPU 的占用率的方法,epoll 最大的好处在于它不会随着监听文件句柄数目的
增长而降低效率,它无须遍历整个被侦听的描述符集,只要遍历那些被内核 IO 事件异步唤
醒而加入 Ready 队列的描述符集合就行了;
4、正因为 Redis 是单线程的,它避免了不必要的上下文切换和竞争条件,也不存在多
进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,
没有因为可能出现死锁而导致的性能消耗;
- Redis 有几种数据类型
String、List、Set、zSet(SortedSet)、Hash
- redis 数据淘汰策略
内存不足时,redis 会根据配置的缓存策略淘汰部分数据,以保证新数据写入成功。当
没有淘汰策略时或者没有找到合适的淘汰的 key 时,redis 直接返回 out of memory 错误。
noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误
信息。 大多数写命令都会导致占用更多的内存。
allkeys-lru: 所有 key 通用,优先删除最近最少使用的 key。
allkeys-random: 所有 key 通用; 随机删除一部分 key。
volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less
recently used ,LRU) 的 key。
volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL)
短的 key。
- Redis 持久化机制
第一种是 RDB 快照模式,也是默认方式,它会把内存中所有数据都保存到磁盘上。它会
在三种情况保存,一是根据配置触发,例如默认情况下 15 分钟之内更新一条记录,或者 5
分钟之内更新了 10 条记录,或者 1 分钟之内更新了 1 万都会触发保存。二是客户端手动执
行 save 命令。三是服务端正常退出时也全部保存。RDB 的缺点很明显,容易丢数据,另外
由于每次保存都全部备份内存的数据,因此写入的数据大,不适合小内存机器。
第二种是 AOF 追加模式,它只记录最后执行了更新命令和数据。优点是丢失的数据少,
最大丢失一秒的数据,写入压力小,它追加最后一条。AOF 模式不是默认的,需要手动打开,
AOF 模式缺点是文件会越来越大,数据恢复也会越来越慢,例如使用 incr 命令递增 100 万
次,就会写 100 万次磁盘。
Redis4.0 之后 AOF 和 RDB 可以结合使用。
- Redis 集群
为了提高 redis 高可用,我们可以选用主从复制、哨兵模式,集群模式;不管是主从复
制也好还是哨兵模式也好,redis 每台服务器存储的数据都一样的,但是单台 redis 存储的
数据量是有限的,我们这个时候可以使用 redis 的集群来进行存储,为了保证集群中的每台
redis 数据不会丢失,我们需要给每台 redis 节点配置从节点。
在 redis 集群模式中至少需要 3 主 + 3 从,才能建立集群。Redis-cluster 采用无中心
结构(去中心化思想),每个节点保存了数据和整个集群的状态,每个节点都和其他所有节
点直接连接
- Redis 节点是咋分配的?你了解 redis 的哈希槽吗?它是如何分配的?
是这样的,Redis 中有一个哈希槽的概念,redis 一共有 0-16383 个哈希槽,我给您举
个例子吧:假如我有三个主节点分别是 A,B,C 那么采用哈希槽(hash slot)的方式来分配
16384 个槽的话,他们三个节点分别承担的槽的区间是将这 16384 个槽平均分成三份,每个
节点存放三分之一的哈希槽。那么这个时候要是再新增一个主节点的话: 新增一个节点 D,
集群会从各个节点的前面各拿去一部分槽到 D 节点上
- Redis 缓存和 MySQL 数据一致性方案
(知道)不管是先写 MySQL 数据库,再删除 Redis 缓存;还是先删除缓存,再写库,
都有可能出现数据不一致的情况。因为写和读是并发的,没法保证顺序,就会出现缓存和数
据库的数据不一致的问题。举一个例子:
1.如果删除了缓存 Redis,还没有来得及写库 MySQL,另一个线程就来读取,发现缓存
为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。
2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数
据不一致情况。
第一种方案:采用延时双删策略+缓存超时(会背)
在写库前后都进行 redis.del(key)操作,并且设定合理的超时时间
先删除缓存;
再写数据库;
根据业务休眠 n 毫秒;
再次删除缓存;
给缓存设置过期时间(保证最终一致性)。
第二种方案:异步更新缓存(基于订阅 binlog 的同步机制)(会背)
订阅 MySQL 的 binlog,读取 binlog 后分析,利用消息队列,推送更新 redis 缓存数据。
这样一旦 MySQL 中产生了新的写入、更新、删除等操作,就可以把 binlog 相关的消息推送
至 Redis,Redis 再根据 binlog 中的记录,对 Redis 进行更新。
其实这种机制,很类似 MySQL 的主从备份机制,因为 MySQL 的主备也是通过 binlog 来
实现的数据一致性。
这里可以结合使用 canal(阿里的一款开源框架),对 MySQL 的 binlog 进行订阅。
- 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。
解决方案:从缓存取不到的数据,在数据库中也没有取到,这时也可以将 key-value
对写为 key-null,缓存有效时间可以设置短点,如 30 秒(设置太长会导致正常情况也没法
使用)。这样可以防止攻击用户反复用同一个 id 暴力攻击缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存的数据时间到期),这时由
于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬
间增大,造成过大压力
解决方案:
当然,目前我能想到的解决方案大概是这么几种
比如我们可以设置热点数据永远不过期啊。
我们可以使用加锁的机制来解决缓存失效啊
- 什么是 TPS?什么是 QPS?
QPS: 应用系统每秒钟最大能接受的用户访问量,每秒钟处理完请求的次数。
(注意这里是处理完,具体是指发出请求到服务器处理完成功返回结果的整个过程)
TPS: 每秒钟最大能处理的请求数,也就是每秒钟处理完的事务次数,一个事务可能会
对应多个请求。
- Redis 相比 memcached 有哪些优势?
1、 redis 和 memcached 都是缓存数据库,而 memcached 所有的值都是简单的字符串,
Redis 支持更为丰富的数据类型
2、
Redis 的速度比 memcached 快很多
3、 memcached 是纯粹的基于内存存储的,重启之后数据就丢失了,Redis 是可以持久
化数据的
4、
Redis 支持主从复制模式;
RocketMQ
- 为什么使用 RocketMQ
RocketMQ 相对于其他 MQ 来说,天然支持集群,可用性高,吞吐量相对较高,抗并发,
支持事物消息可以处理分布式事物,RocketMQ 本身提供的持久化机制,防止消息丢失。在
一些需要异步处理的场景中是一个不错的选择。
- RocketMQ 基础概念(知道)
Broker:RocketMQ 的核心组件之一。用来从生产者处接收消息,存储消息以及将消息
推送给消费者。同时 RocketMQ 的 broker 也用来存储消息相关的数据,比如消费者组、消费
处理的偏移量、主题以及消息队列等Name Server: 可以看做是一个信息路由器。生产者和消费者从 NameServer 中查找对
应的主题以及相应的 broker
GroupName:无论是 Producer 端还是 Consumer 端,都必须指定一个 GroupName,这个组
名称需要由应用来保证唯一性。同一个 ProducerGroup 下的所有 Producer 发送用一类消息,
且发送逻辑一至。Consumer 同理。
Topic:代表消息发送和订阅的主题,是一个逻辑上的概念,Topic 并不实际存储消息。
每个 Topic 都会维护一些 MessageQueue(默认 4 个),这个 MessageQueue 则是物理上的概念,
直接存储消息。
Tag: Tag 可以看做是一个子主题(sub-topic),可以进一步细化主题下的相关子业务。
提高程序的灵活性和可扩展性
- RocketMQ 的两种订阅模式:(了解)
一种是 Push 模式(MQPushConsumer)
一种是 Pull 模式(MQPullConsumer)
但在具体实现时,Push 和 Pull 模式都是采用消费端主动拉取的方式,即 consumer 轮
询从 broker 拉取消息。
区别是:
Push 方式里,consumer 把轮询过程封装了,并注册 MessageListener 监听器,取到消
息后,唤醒 MessageListener 的 consumeMessage()来消费,对用户而言,感觉消息是被推
送过来的。
Pull 方式里,取消息的过程需要用户自己写(首先通过打算消费的 Topic 拿到
MessageQueue 的集合,遍历 MessageQueue 集合,然后针对每个 MessageQueue 批量取消息,
一次取完后,记录该队列下一次要取的开始 offset,直到取完了,再换另一个
MessageQueue)。
- RocketMQ 消费模式:
集群消费:当使用集群消费模式时,消息队列 RocketMQ 认为任意一条消息只需要被集
群(Group)内的任意一个消费者处理即可。(默认集群消费模式)
广播消费:当使用广播消费模式时,消息队列 RocketMQ 会将每条消息推送给集群
(Group)内所有注册过的客户端,保证消息至少被每台机器消费一次。(不支持消息重试,
不支持顺序消息)
- RocketMQ 消息类型:
普通消息、延时消息、顺序消息、事物消息
消息重复如何解决(解决消息消费幂等性问题)?
由于消费者可能会收到两条内容相同并且 Message ID 也相同的消息,这样我们就可以利用一张日志表(redis)来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志
表中,那么就不再处理这条消息。
- ACK 确认-消息重试
RocketMQ 提供了 ack 机制,可以保证消息能够被正常消费。broker 为了保证消息肯定
消费成功,只有使用方明确返回消费成功(也就是返回 CONSUME_SUCCESS),RocketMQ 才会
认为消息消费成功。当然,我们是对消息进行手动 ACK 返回的操作。确保中途断电、抛出异
常等都不会认为成功——即都会发起重试。
- 死信队列
RocketMQ 默认重试次数为 16,当达到最大重试次数之后消息就进入死信队列了。我们
也可以根据业务的需要,可以自定义消费的最大重试次数。当死信消息投递到死信队列
( DLQ )中后,死信消息的业务需要进行人工干预。
其实,重试三、五次就可以认为当前业务存在异常,继续重试下去也没有意义了,那么
我们就是将当前的这条消息进行提交,返回 broker 状态 CONSUME_SUCCES,让消息不再重发,
同时将该消息存入我们业务自定义的死信消息表(redis),将业务参数入库,相关的运营
通过查询死信表来进行对应的业务补偿操作。
- 分布式事务怎么解决
分布式事务我们是利用的 rocketMq 的事务消息确保我们分布式项目中事务最终一致性
的。因为 rocketmq 的设计中 broker 和 producer 端的双向通信能力,使得 broker 天生作为一
个事务协调者存在,可以确保本地事务执行与消息发送的原子性问题,而 rocket 本身提供的
存储机制,则为事务消息提供持久化能力。Rocketmq 的高可用机制以及可靠消息设计,则
为事务消息在系统发生异常时,依然能够保证事务的最终一致性达成。
二十. 事务消息流程以及原理
1.首先 Consumer 是通过消息的索引来读取/订阅消息的,普通消息都带有消息索引
2.事务发起方首先发送 prepare 消息到 MQ,普通消息会去构建消息索引,而如果发现是
事务消息,则跳过了创建索引的逻辑传入内置的 HalfTopic,所以消费者是不可见的
3.Producer 在发送 prepare 消息成功后执行本地事务
4.根据本地事务执行结果返回 commit 或者是 rollback
5.如果是 commit 消息,MQ 将该事物消息从 HalfTopic 中提出并生成消息索引存入该消
息真正的业务 Topic,消息对消费者可见。如果消息是 rollback,MQ 不会生成消息索引,消息
对消费者仍然不可见
6.如果执行本地事务过程中,执行端挂掉,或者超时,MQ 将会不停的询问其同组的所
有 producer 来获取状态“回查”
7.Producer 收到回查消息,检查回查消息对应的本地事务的状态,根据本地事务状态,
重新返回 Commit 或者 Rollback 状态这些流程就确保了 Producer 端的消息发送与本地事务执行的原子性
二十一. 事务消息回查实现(了解) :
首先,事务消息的成功投递是需要经历三个 Topic 的,分别是: HalfTopic、 OpTopic、事
务消息真正的 Topic.
超时处理是 RocketMQ 服务端对 Producer 端进行回查: broker 维护一个死循环,每一分
钟执行一次,RocketMQ 通过使用 Half Topic 以及 Op Topic 两个内部队列来存储事务消息推
进状态。其中,HalfTopic对应队列中存放着prepare消息,OpTopic对应的队列则存放了prepare
消息对应的 commit/rollback 消息,消息体中是 prepare 消息对应的 offset;服务端通过比对两
个队列的差值来找到尚未提交事物结果的超时事务,调用 Producer 端,用来回查事务处理
结果。
Producer 端接收 broker 回查的逻辑:Producer 端一个线程池维护执行 TransactionListener
的 executel ocalTransaction 实现,也就是本地事务方法的任务。将查询到的本地事务结果反
馈给 broker 端,broker 来决定对事务消息如何处理。
二十二. 集群模式(了解)
RocketMQ 消息生产者和消费者都是一个组的概念, 天然支持集群,这样可以应对大量
的消息生产和消费,也是 RocketMQ 高吞吐量的一个标志性特征。单个 Master:一旦 Borker
重启或宕机期间,将会导致这个服务不可用.
多个 Master:当单个 Broker 宕机期间,这台机器上未被消费的消息在机器恢复之前不可
订阅
多 Master 多 Slave 模式-异步复制:采用的是异步复制方式,主备有短暂的消息延迟,一
旦主宕机可能会有消息丢失
多 Master 多 Slave 模式-同步双写(推荐):采用的是同步双写模式,主备都写成功,才会
向应用返回成功,更保险消息不会丢失
二十三. NameServer:
维持心跳和提供 Topic-Broker 的关系数据,多个 Namesrv 之间相互没有通信,单台
Namesrv 宕机不影响其他 Namesrv 与集群;即使整个 Namesrv 集群宕机,已经正常工作的
Producer,Consumer,Broker 仍然能正常工作,但新起的 Producer, Consumer,Broker 就无
法工作,nameserver 不会有频繁的读写,所以性能开销非常小,稳定性很高
Broker 与 Namesrv 的心跳机制:
单个 Broker 跟所有 Namesrv 保持心跳请求,心跳间隔为 30 秒,心跳请求中包括当前
Broker 所有的 Topic 信息
二十四. 高可靠并发读写服务:
所有发往 broker 的消息,有同步刷盘和异步刷盘机制,同步刷盘时,消息写入物理文件才会返回成功,因此非常可靠;异步刷盘时,只有机器宕机,才会产生消息丢失,broker
挂掉可能会发生,但是机器宕机崩溃是很少发生的,除非突然断电。
负载均衡:
Broker 上存 Topic 信息,Topic 由多个队列组成,队列会平均分散在多个 Broker 上,而
Producer 的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落
在每个 Broker 上
高可用:
集群部署时一般都为主备,Broker 名相同的一组 Master/Slave Broker,其中包含一个
Master Broker(Broker Id 为 0)和 0~N 个 Slave Broker(Broker Id 不为 0),备机实时从主机
同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。
Producer
Producer 启动时,也需要指定 Namesrv 的地址,从 Namesrv 集群中选一台 Master 建立
长连接,生产者每 30 秒从 Namesrv 获取 Topic 跟 Broker 的映射关系,更新到本地内存中。
再跟 Topic 涉及的所有 Broker 建立长连接
生产者发送时,会自动轮询当前所有可发送的 broker,一条消息发送成功,下次换另外
一个 broker 发送,以达到消息平均落到所有的 broker 上。假如某个 Broker 宕机,意味生产
者最长需要 30 秒才能感知到。在这期间会向宕机的 Broker 发送消息。当一条消息发送到某
个 Broker 失败后,会往该 broker 自动再重发 2 次,假如还是发送失败,则抛出发送失败异
常。业务捕获异常,重新发送即可。客户端里会自动轮询另外一个 Broker 重新发送,这个
对于用户是透明的
消息发送方式分为,同步发送,异步发送,单向发送
Consumer
消费者启动时需要指定 Namesrv 地址,与其中一个 Namesrv 建立长连接。消费者每隔
30 秒从 nameserver 获取所有 topic 的最新队列情况
Consumer 跟 Broker 是长连接,会每隔 30 秒发心跳信息到 Broker。Broker 端每 10 秒检
查一次当前存活的 Consumer,若发现某个 Consumer 2 分钟内没有心跳,就断开与该
Consumer 的连接,并且向该消费组的其他实例发送通知,触发该消费者集群的负载均衡。
消费者得到 master 宕机通知后,转向 slave 消费(重定向,对于 2 次开发者透明),但是 slave
不能保证 master 的消息 100%都同步过来了,因此会有少量的消息丢失。但是消息最终不会
丢的,一旦 master 恢复,未同步过去的消息会被消费掉。
消费分为集群消费和广播消费
Topic+Queue :
如果各 Master Broker 有 Slave Broker,Slave Broker 中的结构和其对应的 Master Broker完全相同。
Topic 是逻辑概念,对于 RocketMQ,一个 Topic 可以分布在各个 Broker 上,把一个 Topic
分布在一个 Broker 上的子集定义为一个 Topic 分片,其实就是在某一 broker 上一个 topic 的
部分数据
Queue 存在的意义:每个 Topic 分片等分的 Queue 的数量可以不同,由用户在创建 Topic
时指定, 是消费负载均衡过程中资源分配的基本单元.
什么是 Quartz?
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java
代码开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入
日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。
Quartzh 提供了极为广泛的特性如持久化任务,集群和分布式任务等。
特点 :
1) 完全由 Java 写成,方便集成(Spring)
2) 伸缩性
3) 负载均衡
4) 高可用性
应用场景 :
订单超过半个小时未支付就要取消该订单,每天定时生成报表
Quartz 的核心组件
(1) Scheduler(任务调度器)
[ˈskɛdʒʊlər]
Scheduler 为任务的调度器,它会将任务 Job 及触发器 Trigger 整合起来,负
责基于 Trigger 设定的时间来执行 Job
(2) Trigger(触发器)
触发任务任务执行的时间或规则。在任务调度 Quartz 中,Trigger 主要的触发
器 有 :SimpleTrigger( 简 单 触 发 器 ) , CalendarIntervelTrigger( 日 历 触 发 器 ) ,
DailyTimeIntervalTrigger(日期触发器),CronTrigger(表达式触发器)
常用 : SimpleTrigger(简单触发器)和 CronTrigger(表达式触发器)
(3) Job(任务)
是一个接口,其中只有一个 execute 方法。需要实现接口中的 execute 方法。
来编写任务执行的业务逻辑
(4) JobDetail(任务细节)
Quartz 执行 Job 时,需要新建 Job 实例,但不能直接操作 Job 类,所以通过
JobDetail 获得 Job 的名称,描述信息。
Scheduler 调度器需要借助 JobDetail 对象来添加 Job 实例。
注 : JobDetail 定义的是任务数据,而真正的执行逻辑是是在 Job 中。这是因为任务
是有可能并发执行,如果 Scheduler 直接使用 Job ,就会存在对同一个 Job 实例并发访问的问题。而 采用 JobDetail & Job 方式, Scheduler 每次执行,都会根据 JobDetail 创建一
个新的 Job 实例,这样就可以规避并发访的问题, 当调用完成后,关联的 Job 对象示例会
被释放,释放的实例会被垃圾回收机制回收
Cron 表达式
Cron 表达式是一个字符串,字符串以 5 或 6 个空格隔开,分为 6 或 7 个域,每一个域
代表一个含义,Cron 有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year
Seconds Minutes Hours DayofMonth Month DayofWeek
corn 从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份Cron 表达式的一些示例
Nginx
什么是 nginx,它的优势?
Nginx 是-一个高性能的 HTTP 和反向代理服务器、负载均衡服务器,Nginx 可以比其他
Web 服务器更快地响应请求,而且 Nginx 单机支持 10 万以上的并发连接,我想这也是各大互联
网公司使用 Nginx 的一个重要原因吧负载均衡
当我们使用 nginx+3 个 tomcat 进行负载均衡,在我们不进行负载均衡之前,那所有的请
求都由一台 tomcat 进行处理,这样会使我们的 tomcat 所承受的压力增大,而我们进行负载均
衡之后,同样数量的请求经过 nginx 将其分发到多台 tomcat 进行处理,从而降低每台 tomcat
所承受的压力,而且当其中一台机器宕机时,其他机器还可以继续提供服务,保证服务不间
断。
但是在我们使用 nginx 默认轮询策略的时候会出现 session 共享的问题,我们可以借助
redis 来解决,也比较简单,导入一个 jar 包就可以了.或者将 nginx 轮询策略修改为 ip_hash
Nginx 常用轮询策略
轮询:每个请求按时间顺序逐--分配到不同的后端服务器,如果后端服务器 down 掉,能
自动剔除
ip_hash:每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器
权重:按 weight 权重分配,数字越高比例越高
正向代理和反向代理的区别
其实「正向代理 J 代理的对象是客户端,「反向代理」 代理的对象是服务端
Nginx 限流
一般情况下, 对于并发访问量比较大的网站,即使加了缓存,如果有大量恶意的请求,
也会对系统造成影响,而限流就是保护措施之一。Nginx 限流主要分为两种方式:一种是限制
访问频率,另外--种是限制并发连接数。
nginx 是基于漏桶算法实现的,漏桶算法的核心思想是:请求从上方进入桶中,然后再从
桶的下方流出被系统处理,来不及处理的请求存在桶中进行缓冲,系统以固定速率处理他们,
当桶满后多余的请求就会被丢弃,这个算法的核心是:缓存请求、匀速处理、多余的请求直接
丢弃,漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理。
Dubbo
Dubbo 是什么?
Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发
现等高效服务治理方案,可以和 Spring 框架无缝集成。
RPC 指的是远程调用协议,也就是说两个服务器交互数据。DUBBO 架构的优势:
Consumer 与 Provider 解偶,双方都可以横向增减节点数;
注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉后,将自动切换到
另一台;
去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间内也不会影响服务
的调用;
服务提供者无状态,任意一台宕掉后,不影响使用。
Dubbo 的核心功能?
1.远程通讯:提供对多种基于长连接的 NIO 框架抽象封装,包括多种线程模型,序列化,
以及“请求-响应"模式的信息交换方式。
2.集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软|负载均
衡,失败容错,地址路由,动态配置等集群支持。
3.自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透
明,使服务提供方可以平滑增加或减少机器。
Dubbo 有些哪些注册中心?
Multicast、redis、 Simple 等
推荐 Zookeeper 注册中心,因为它基于分布式协调系统 Zookeeper 实现,采用.Zookeeper
的 watch 机制实现数据变更;
Dubbo 支持哪些协议?
Dubbo 支持 dubbo、rmi、 hessian、 webservice、 thrift、 redis 等多种协议,Dubbo
默认采用 dubbo 协议,dubbo 协议采用单-长连接和 NIO 异步通讯,适合于小数据量大并发
的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况
dubbo 集群有 6 种模式: ( 通过 cluster 属性配置)
failover 模式,集群容错默认的模式,如果调用失败,重新调用其他节点上的服务
failfast 模式,一次调用失败后直接抛异常
failsafe 模式,失败安全模式,调用失败后,记录日志以便后续审计 failback 模式,失败
后记录日志,并且定时重发
forking 模式,并行发往多个服务器,有一个返回就悴止所有
broadcast 模式,逐个调用各个服务节点,有一台报错则报错,通常用于更新缓存之类
的东西。dubbo 负载均衡 4 种模式: (通过 loadbalance 配置)
轮训模式,如果有个服务运行迟钝,容易造成卡死
随机模式,可以分配权重
最少活跃调用,处理请求越慢的节点分配的请求越少
一致性 hash,相同参数的请求,调用同一个服务节点
注册中心集群挂掉,Dubbo 还能通信么?
可以的,启动 dubbo 时,消费者会从 zokeper 拉取注册的生产者的地址接口等数据,缓
存在本地。每次调用时,按照本地存储的地址进行调用。
Zookeeper 的 Watcher 事件监听器(心跳机制)
Watcher(事件监听器),是 ZooKeeper 中一个很重要的特性,Watcher 机制主要包括客户
端线程、客户端 WatchManager 和 Zookeeper 服务器三部分。具体的流程:客户端向 Zookeeper
服务器注册 Watcher 事件监听的同时,会将 Watcher 对象存储在客户端 WatchManager 中。
当 Zookeeper 服 务 器 触 发 Watcher 事 件 后 , 会 向 客 户 端 发 送 通 知 , 客 户 端 线 程 从
WatchManager 中取出对应的 Watcher 对象执行回调逻辑(执行 process 方法)。
SpringBoot
什么是 Spring Boot?
SpringBoot 是 Spring. 开源组织下的子项目,是 Spring 组件-站式解决方案,主要是简化
了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
优点非常多:
独立运行: Spring Boot 而且内嵌了各种 servlet 容器,Tomcat、 Jetty 等, 现在不再需要
打成 war 包部署到容器中,SpringBoot 只要打成一个可执行的 jar 包就能独立运行,所有的
依赖包都在-一个 jar 包内。
简化配置: spring-boot-starter-web 启动器自动依赖其他组件,简少了 maven 的配置;
自动配置: Spring Boot 能根据当前类路径下的类、jar 包来自动配置 bean:.如添加一个
spninootostatrweb 启动器就能拥有 web 的功能,无需其他配置。
无代码生成和 XIML 配置: Spring Boot 配置过程中无代码生成,也无需 XML 配置文件就
能完成所有配置工作,这一切都是借助于条件注解完成的,这也是 Spring4.x 的核心功能之,--。
应用监控: Spring Boot 提供一系列端 点可以监控服务及应用,做健康检测。
Spring Boot 与 Spring 的区别
Spring Boot 可以建立独立的 Spring 应用程序;内嵌了如 Tomcat, Jetty 和 Undertow 这样的容器,也就是说可以直接跑起来,用不着再做部署工作了。无需再像 Spring 那样搞一堆 繁
琐的 xml 文件的配置:可以自动配置 Spring 实现自动装配功能;可以让开发人员只关心程序核
心编码。
spring Boot 的核心注解是哪个?由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组
合包含了:
@SpringBootConfiguration:组合了@Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如
关闭数据源自动配置功能:@sringopolpiation(exctude={ DatsorceutoConiuratin.,a }》
SpringBoot 的自动配是怎么做的?
主配置类启动,通过@SringBootApplication 中的@EnableAutoConfguration 加载所需的所
有自动配置类,然后自动配置类生效并给容器添加各种组件。那么@EnableAutoConfguration
其实是通过它里面的@AutoConfigurationPackage 注解,将主配置类的所在包皮下面所有子包
里面的所有组件扫描加载到 Spring 容器中;
还通过@EnableAutoConfguration 里面的 AutoConfigurationImportSelector 选择器中的
SringFactoriesLoader.loadFactoryNames()方法,获取类路径下的 META-INF/spring.factories 中的
资源并经过一些列判断之后作为自动配置类生效到容器中,自动配置类生效后帮我们进行自
动配置工作,就会给容器中添加各种组件:这些组件的属性是从对应的 Properties 类中获取
的,这些 Properties 类里面的属性又是通过@ConfigurationProperties 和配置文件绑定的:所以
我们能配置的属性也都是来源于这个功能的 Properties 类。SpringBoot 在自动配置很多组件
的时候,先判断容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置
的,如果没有,才自动配置;如果有些组件可以有多个就将用户配置和默认配置的组合起来。
Spring Boot 中的 Starters 是什么?
Starters 可以理解为启动器,它包含了-系列可以集成到应用里面的依赖包,你可以一站
式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。如:你想使用 Spring JPA 访问
数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。Starters 包含了许多项
目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?
配置变更
JDK 版本升级
第三方类库升级
响应式 Spring 编程支持 HTTP/2 支持
配置属性绑定ES
术语:
索引( index) :相当于 mysq!的数据库
类型(type) :相当于 mysql 的表
文档(document) :相当于 mysql 的一行
节点(node) :-般指安装 es 的服务器
分片(primary shard) : - 一个索引可以有多个分片组成
副本(replicashard):-一个分片可以有多备份备份就是副本段(segment):一个分片是由多个段组
成的
以下概念会多次提到上面的这些术语,大家必须掌握
ElasticSearch 介绍
ElasticSearch 是一款基于 Apache Lucene 构建的开源搜索引擎,ElsticSearch 采用 Java 编
写的,可以提供简单易用的 RESTFul API 开发者可以通过它简单明了的 RESTFulAPI 轻松地实
现搜索功能,而不必面对 Lucene 的复杂性:
ElasticSearch 目标就是屏蔽复杂性,从而让全文搜索变得很简单。同时 ElasticSearch 可
以轻松的实现大规模的横向扩展,可支持 PB 级(大约等于 1000 个 TB)的结构化或非结构化的
海量数据处理,简单的说就是当我们 ElasticSearch 存储容量不足的时候,可以通过不断的横向
加节点(机器)来解决存储容量的问题。
elasticsearch 与 solr 的区别
solr 和 elasticsearch 都是基于 Lucene 实现的,但是 solr 建立索引时候,搜索效率下降,实
时搜索效率不高,es 实时搜索效率相对比较高,而且 es 支持分布式,节点对外表现对等,
加入节点自动均衡。
elasticsearch 近实时
Elastisearch 和磁盘之间还有一层文件系统缓存(ileSystem Cache),正是由于这层缓存的存
在才使得 es 能够拥有更快搜索响应能力。
我们都知道一个索引是由若干个分片组成,而且每个分片内部由很多个 segment (段)组
成,随着每个 segment (段)的不断增长,我们索引一条数据后可能要经过分钟级别的延迟才
能被搜索到,为什么有种这么大的延迟,这里面的瓶颈点主要在磁盘。
因为持久化一个段需要文件同步操作用来确保每个段能够被写入磁盘来真正的避免数
据丢失,但是文件同步操作比较耗时,所以它不能在每次修改或添加一条数据后就执行一次,
如果那样添加和搜索的延迟都会非常大。
所以 ES 使用了一-种更轻量级的处理方式来提高速度,es 每次新增的数据会被收集到缓
冲区,接着被重写成一个段,然后直接写入文件系统缓存中,这个操作是非常快的。之后经过一定的间隔或外部触发后才会被持久化到磁盘上,这个操作非常耗时,但只要文件被写入
缓存后,这个文件就可以被查询到了,而不用等到真正持久化到磁盘之后在查询,从而确保
在短时间内就可以搜到。
分片和副本是 elasticsearch 非常核心的概念
1、一个索引包含多个分片,每个分片都是一一个最小工作单元, 承载部分数据, .每个
分片都是一个 lucene 实例,有完整的建立索引和处理请求的能力
2、增减节点时,分片会自动在节点中负载均衡
3、每个文档肯定只存在于某一一个主分片上,以及其对应的副分片上,不可能存.在于
多个主分片
4、副分片我们称之为主分片的副本,主要负责容错,以及承担读请求负载
5、主分片的数量在创建索引的时候就固定了,而副本的数量可以随时修改(为什么不能
更改请参考路由第二句话)
6、主分片不能和自己的副本放在同-一个节点上(否则节点宕机,分片和副本都会丢失,
起不到容错的作用),但是可以和其他主分片的副本放在同一个节点 上
elasticsearch 怎么和数据库进行数据同步
我们当时采用的是 logstash 进行数据库和 elasticsearch 进行同步,他的一个工作原理很简
单,就是定时执行咱们配置文件中所定义的 sql,首先他需要两个插件一个是读取 mysql 数据的
插件,另一个是同步 ES 的插件。
elasticsearch 分页
当时我在使用 elasticsearch 也遇到过这个坑,因为 es 默认采用的分页方式是 from-size
的方式,这种方式其实就是简单意义上的分页。
它的原理很简单,比如我们要查询第 10-20 条数据,他会把前面 20 条数据全部查出来,
然后截断前 10 条,只返回 10-20 的数据。这样其实白白浪费了前 10 条的查询。而且越往后
的分页,执行的效率越低,这是一点,另外 es 默认最大只能查询 1 万条数据,所以用这种
分页我们只能查询前 1W 的数据,如果查询超过这个最大值,es 就会报错,当然也能调最大
值,这样的话只是暂时的解决方案,因为我们的数据是在不断的增加的。
所以后来我采用了另外一种 scroll (游标) 方式的分页,这种分页也叫深分页,深分页是
不受配置的最大返回记录数限制的,使用游标可以记录当前读取的文档信息位置,因为这个
游标相当于维护了一份当前索引段的快照信息,这个快照信,息是我们执行这个游标查询时
的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。但是它相对
于 from 利 size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证: 下
一次快速 继续读取。
倒排索引相关问题:同等数据量下,
ES 比 MysqI 检索快的原因是什么? Mysql 也建立了索引,而且 mysql
索引的时间复杂度和 es 的倒排索引的时间复杂度差不多啊?
答:首先我们先讲讲什么是倒排索引什么是正排索引:
正排索引: .
正排索引就是你得从头到尾的把数据查询出来,查看哪些数据里面包含我们要查询的关
键字,效率比较低。
倒排索引:
倒排就正好相反,他维护了一一个字典表, key 就是我们要搜索的关键词,value 是包
含这些关键司的数据的 id,我们只要对比字典表就能知道哪几条数据有我们要查询的关键
字,然后拿到这些数据的 id,一下就找到我们要找的数据了。
(如果只问倒排索引就回答到这就可以了)
问到 Mysql 与 es 对比:
mysql 的索引只是存储字段的内容(如果字段内容过长,就只存前几位的内容为索引),
其次 mysql 也没有分词。而 es 存储的是分词以后的索引,记录每个词都在哪些文档中出现
过 。 如 果 是 搜 索 关 键 字 来 精 确 匹 配 这 种 基 本 没 啥 影 响 。 但 是 如 果 是 mysql 的
like"%word%"mysql 全表查会特别慢,es 只需要查 word"这个词包含的文档 id 速度明显不是
一个级别。
文档数据路由原理
一个索引由多个分片构成,当添加、修改或者刪除- -个文档时,es 就需要决定这个文
档存储在哪个分片上,这个过程就称为数据路由
路由原理是:
1、每次增删改查时,都有- -个 routing (路由)值,默认是文档的_id 的值,也可以手动
指定一个值
2、
ES 会对这个 routing 值使用哈希函数进行计算,计算出的值再和主分片个数取余数(取
模),余数肯定在分片之间(也就是 0~分片数-1 之间),所以分片的数量一旦确定就不能更改
了。
ElasticSearch 解决并发修改问题
ElasticSearch 采用了乐观锁来保证数据的一致性,也就是说,当用户对文档数据进行操作
时,并不需要对该文档作加锁和解锁的操作,只需要指定要操作的版本即可。当版本号一致
时,ES 会允许该操作顺利执行,而当版本号存在冲突时,ES 会提示冲突并抛出异常
(VersionConflictEngineException 异常)。
MongoDB
MongoDB 的优势:高性能、高可用性、易扩展性、丰富的查询语言、面向文档的存储。以 JSON 格式的文
档保存数据。任何属性都可以建立索引。复制以及高可扩展性。自动分片。丰富的查询功能。
快速的即时更新。来自 MongoDB 的专业支持。数据之间无关系,每条文档/记录的字段可
以不一致,想怎么存就怎么存;没有关系型的联查,数据结构简单。
Redis 和 MongoDB 区别
redis 我们是用来存储一些热数据,数据量不是特别大,但是操作很频繁,MongoDB 是
用来存储大数据量的。就 Redis 和 MongoDB 来说,大家一般称之为 Redis 缓存、MongoDB 数
据库。这也是有道有理有根据的,Redis 主要把数据存储在内存中,其“缓存”的性质远大于
其“数据存储“的性质,其中数据的增删改查也只是像变量操作一样简单;MongoDB 却是-个“存
储数据”的系统,增删改查可以添加很多条件,就像 SQL 数据库一样灵活。
支持的数据结构:Redis 支持的数据结构丰富,包括 hash、set、 list 等。MongoDB 数据
结构比较单- ,但是支持丰富的数据表达,索引。最类似关系型数据库,支持的查询语言非常
丰富。
非关系型数据库的优势:
1.非关系型数据库是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需
要经过 SQL 层的解析,所以性能非常高;
2.可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
简单的来说,非关系型数据库没有固定的字段,我们每次添加字段,不需要先向数据库添加
字段,直接通过键值对形式存放,key 会自动创建。
关系型数据库的优势:
1.复杂查询可以用 SQL 语句方便的在一一个表以及多个表之间做非常复杂的数据查询。
2.事务支持使得对于安全性能很高的数据访问要求得以实现。
为什么 MongoDB 适合大数据的存储
针对于这个问题可以归纳于为什么 mongodb 的查询效率高,其实不只是非关系型数据库,
关系型数据库同样可以存放大量数据,只不多数据达到某个量时查询效率下降,那么针对
MongoDB 数据是暂存到内存中,然后就是 Mongodb 使用的是 javascript 语法,还有就是对于
MongoDB 是没有像咱们 mysq|这种表关系存在的,那也就相当于每个数据都有一个单独的
存储空间,通过一个索引指向一下,所以搜索性能相对来说会高一些。
MongoDB 大数据量排序问题
因为 mongodb 的 sort 操作是在内存中操作的,必然会占据内存,同时 mongo 内的一
个机制限制排序时最大内存为 32M,当排序的数据量超过 32M,就会报错,第一种解决方案是去修改他的一个最大内存但是的话,因为效据都足增址的所以不可取.第二种解决方案的话
就是添加索引,MongoDB 创建索引是及时生效的不需要重启服务,所以也比较方便,这样的话,
不但不会报错而且速度也很快
微服务
微服务化的核心就是将传统的一-站式应用,根据业务拆分成一个一个的服务,彻底地去
耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事,从技术角度看就是-种小而
独立的处理过程,类似进程概念,能够自行单独启动或销毁拥有自已独立的数据库.
微服务的优点缺点?
优点:
1.通过分解巨大单体式应用,开发效率高,各服务之间代码解耦和
2.每个微服务独立的开发部署,单一职责功能,每个服务只关注一个业务功能
3.改善故障隔离,-一个服务宕机不会影响其他服务
缺点:
1.各服务间通信成本增加
2.多服务运维难度加大
3.保证数据-致性难度增加,系统集成测试、性能监控难度增加。
Spring Boot 和 Spring Cloud 的关系
SpringBoot 是 Spring 推出用于解决传统框架配置文件冗余,装配组件繁杂的解决方案,专
注于快速,方便的开发单个个体的微服务;
SpringCloud是关注全局的微服务协调整理治理框架,整合并管理各个微服务,为各个微服
务之间提供:配置管理,服务发现,断路器,路由,链路追踪等集成服务。SpringBoot 不依赖于
SpringCloud,SpringCloud 依赖于 SpringBoot,属于依赖关系;
Spring Cloud 和 Dubbo 有哪些区别?
Dubbo 和 Spring Cloud 并不是完全的竞争关系,两者所解决的问题域不一样:Dubbo 的定
位始终是一款 RPC 框架,而 Spring Cloud 的目的是微服务架构下的一站式解决方案。
1.服务调用方式 dubbo 是 RPC springcloud Rest Api
2.注册中心,dubbo 是 zookeeper springcloud 是 eureka,也可以是 zookeeper
3.服务网关,dubbo 本身没有实现,只能通过其他第三方技术整合,springcloud 有 Zuul
路由网关,作为路由服务器,进行消费者的请求分发,pringcloud 支持断路器,与 git 完美集
成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服
务架构要素。REST 和 RPC 对比
1.RPC 主要的缺陷是服务提供方和调用方式之间的依赖太强,需要对每一个微服务进行
接口的定义,并通过持续继承发布,严格版本控制才不会出现冲突。
2.REST 是轻量级的接口,服务的提供和调用不存在代码之间的耦合,只需要一个约定进
行规范。
Eureka 和 ZooKeeper 的区别
首先先说 CAP 是什么:所谓的 CAP, C 强一致性,A 可用性,p 分区容错性。
ZooKeeper 遵守 CP , ZooKeeper 集群采用的 leader 选举机制,有 Leader 和 Fllower 角色,
当 Leader 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问
题在于,选举期间整个 ZoKeeper 集群都是不可用的,这就导致在选举期间注册服务瘫瘓。也
就是 ZooKeeper 是最终强一致性。但可用性稍差一些。
Eureka 遵守 AP,Eureka 各个节点是平等关系几个节点挂掉不会影响正常节点的工作,剩
余的节点依然可以提供注册和查询服务。只要有一台 Eureka 还在,就能保证注册服务可用(保
证可用性),只不过查到的信息可能不是最新的也就是不保证强一致性。
除此之外,Eureka还有一种自我保护机制,默认是如果在15分钟内超过85%的节点都没有
正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时Eureka不再从注册列
表中移除因为长时间没收到心跳而应该过期的服务:而且 Eureka 仍然能够接受新服务的注册
和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用);等到当网络稳定时,
当前实例新的注册信息会被同步到其它节点中。
注:leader 选举是怎么选举的
Eureka 自我保护机制是什么?
当 Eureka 节点在短时间内丢失了过多实例连接时,会进入自我保护模式(比如网络故
障或频繁启动关闭客户端),节点会进入自我保护模式,保护注册信息,即使过期也不删除
注册数据,故障恢复时自动退出自我保护模式。
什么是 Rbbon?什么是 Feigin?区别是什么?
Ribbon 是个负载均衡客户端,可以很好的控制 http 和 tcp 的些行为。
Feign 默认集成了 Ribbon,具有负载均衡的能力,整合了 Hystx 具有熔断的能力。
Feign 基于动态代理模式,根据注解和通过 Ribon 选择的机器,拼接请求 URL 地址,发
起请求。
Ribbon 和 Feign 调用方式不同
Ribbon 需要自己构建 http 请求,模拟 http 请求然后使用 RestTemplate 发送给其他服务,
开发步骤繁琐。Feign 则是在 Ribbon 的基础上进行了一次改进,基于动态代理模式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建 http 请求。
什么是 Hystrix?
防雪崩利器,具备服务降级,服务熔断, 依赖隔离, 监控(Hstrix Dashboard)等功能
什么是服务熔断?熔断器的作用是什么?
首先我们说一下什么是扇出与雪崩效应:多个微服务之间调用的时候,假设微服
务 A 调用了微服务 B 和微服务 C,微服务 B 和微服务 C 又调用了其他的服务,这
就是所谓的扇出,如果扇出的链路上某个微服务的调用响应时间过长,或者是不
可用,那么该微服务调用者就会阻塞线程,占用越来越多的系统资源,进而崩溃。
同理,影响调用者的调用者,进而一步步崩溃,这也就是所谓的雪崩效应。
那么服务熔断机制就是应对雪崩效应的一种微服务链路保护机制,扇出链路
的某个微服务不可用,或者是响应时间过长,会进行服务的降级,进而会熔断该
节点微服务的调用,快速返回错误的响应信息,当检测到该节点微服务调用响应
正常的时候,恢复链路。在 SpringCloud 框架里熔断机制是通过 Hystrix 实现的。
Hystrix 会搞很多个小的线程池比如订单服务请求库存服务是一个线程池,
请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程
仅仅用于请求那个服务,当某个线程池达到阈值时,就会启动服务熔断,服务降
级。如果其他请求继续访问,就直接返回 fallback 的默认值。
什么是服务降级?
服务熔断的时候,在返回的之前做一个熔断处理,比如将请求信息存储到数
据库以便后期数据恢复,我认为这种熔断后的处理指的就是服务降级。优先核心
服务,非核心服务不可用或弱可用。
服务降级案例:双十一:《哎哟喂,被挤爆了.…》或 app 秒杀:《网络开小差
了,请稍后再试…》
在 fallbackMethod (回退函数)中具体实现降级逻辑。
Zuul 网关服务的作用
为了保证对外服务的安全性,所有对外的微服务接口往往都会有一定的权限
校验机制。为了防止客户端在发起请求时被篡改等安全方面的考虑,需要签名校
验,对校验的修改,扩展,优化,就需要解决各个前置校验的冗余问题。
API 网关是整个微服务架构系统的入口,所有的外部客户端访问都需要经过
他来进行调度和过滤。它处理要实现请求路由,负载均衡,校验过滤等功能之外,
还需要更多的能力,比如与服务治理框架结合,请求转发时的熔断机制,服务的聚
合等一系列高级功能。Zuul 网关的限流
在一个微服务集群架构中,我们仅仅在入口处的 nginx 中使用限流是远远不够的,因为
nginx 是我项目的统一入口,那么它的限流一定是粗粒度的,因此为了保护微服务集群的安
全,我们还在微服务的网关上加了限流操作。
Zuul 网关的限流采用的是令牌桶算法,此算法的核心思想是:
令牌以固定速率产生,并缓存到令牌桶中;
当令牌桶放满时,那么多余的令牌将会被丢弃;
请求要消耗等比例的令牌才能被处理;
当令牌不够的时侯,请求将被缓存。
令牌桶与漏桶算法的区别
从作用上来说,漏桶和令牌桶算法最明显的区别就是是否允许突发流量( burst )的处理,
漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理;而令牌桶算法
能够在限制数据的平均传输速率的同时允许某种程度的突发传输。
Spring Cloud 核心组件,分别扮演的角色:
Eureka :各个服务启动时, Eureka Client 都会将服务注册到 Eureka Server ,并且 Eureka
Client 还可以反过来从 Eureka Server 拉取注册表,从而知道其他服务在哪里
Ribbon :服务间发起请求的时候,基于 Ribbon 做负载均衡,从一个服务的多台机器中选
择一台
Feign :基于 Feign 的动态代理机制,根据注解指定的服务和 Ribbon 选择的机器,拼接请
求 URL 地址,发起请求
Hystrix :发起请求是通过 Hystrix 的线程池来走的,不同的服务走不同的线程油现了不同
服务调用的隔离,避免了服务雪崩的问题
Zuul :如果前端、移动端要调用后端系统,统一从 Zuul 网关进入,由 Zuul 网关转发请求
给对应的服务
Config :服务配置文件统一管理,实时更新,它支持配置服务放在配置服务的内存中(即本
地),也支持放在远程 Git 仓库中
Nacos
Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
相比于 Eureka + Spring Cloud Config,Nacos 默认集成了注册中心和配置中心,使用起来更加
简洁方便。
Nacos 和其他几大服务注册中心相比,支持雪崩保护、自动注销实例,提供监听支持,
支持多数据中心、跨注册中心,可以集成 cloud、 dubbo 等,功能更加强大。
Nacos 集群默认支持 CAP 原则中的 AP 原则,但是也可切换为 CP 原则。如果不需要存
储服务级别的信息并且服务实例是通过 nacos 客户端注册的,并能够保持心跳,那么就可以选择 AP 模式,AP 模式是为了服务的可用性而降低了一致性,因此 AP 模式下只支持注册临
时实例。如果需要在服务级别编辑或者存储信息,那么我们可以使用 CP 模式。
Nacos 的强大之处在于,不仅可以作为服务的注册中心,而且还可以作为服务的配置中
心来使用,服务的配置中心分为 config server 和 config client,由于 nacos 本身就是一个配置
中心服务,因此我们只需在微服务中搭建 config client 即可,非常方便。
Nacos 自带多环境配置,我们可以使用 dataid 配置、group 配置、nameSpace 配置,我
们可以根据不同的开发场景选择不同的多环境配置。
Nacos 还有一个亮点是默认自带持久化功能,Nacos 默认有自带嵌入式数据库 derby,但
是如果做集群模式的话,就不能使用自带的数据库,不然每个节点一个数据库,那么数据就不
统一了,因此我们需要使用外部的数据库来统一,目前仅支持 mysql5.6 以上版本
MySQL 主从同步
原理以及流程
1.主: binlog 线程——记录下所有改变了数据库数据的语句,放进 master 上的 binlog
中;
2.从: io 线程——在使用 start slave 之后,负责从 master 上拉取 binlog 内容,放进自己
的 relaylog 中;
3.从: sql 执行线程——执行 relay log 中的语句;
MysQL 主从同步读写分离
1.加载多数据源
2.将数据源注入到 MyBatis 中
3.利用 Aop 切面区分读写请求,存入 ThreadLocal 线程局部变量
4.继承 Spring 数据源切换类,重写数据源切换方法,进行切换具体数据源。
多线程
什么是线程?
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运
作单位。咱们可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要
100 毫秒,那么用十个线程完成改任务只需 10 毫秒。
并行和并发
1、并行(parallel):多个任务(进程、线程)同时运行。在某个确定的时刻,有多个
任务在执行
条件:有多个 cpu,多核编程
2、并发(concurrent):多个任务(进程、线程)同时发起。不能同时执行的(只有一个 cpu),要求同时执行。就只能在某个时间片内,将多个任务都有过执行。一个 cpu 在不
同的任务之间,来回切换,只不过每个任务耗费的时间比较短,cpu 的切换速度比较快,所
以可以让用户感觉就像多个任务在同时执行。
线程和进程有什么区别?
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进
程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个
线程都拥有单独的栈内存用来存储本地数据。
如何在 Java 中实现线程?
继承 Thread 类实现
Runnable 接口
实现 Calable 口
线程池
实现接口和继承 Thread 类比较
接口更适合多个相同的程序代码的线程去共享同一个资源。
接口可以避免 java 中的单继承的局限性。
接口代码可以被多个线程共享,代码和线程独立。
线程池只能放入实现 Runable 或 Calable 接口的线程,不能直接放入继承 Thread 的
类。
Thread 类中的 start ()和 run ()方法有什么区别?
start ()方法被用来启动新创建的线程,而且 start ()内部调用了 run ()方法,这和直接调用
run ()方法的效果不一样。当你调用 run ()方法的时候,只会是在原来的线程中调用,没有新的
线程启动, start ()方法才会启动新线程。
Java 中 Runnable 和 Callable 有什么不同?
实现 Callable 接口的线程能返回执行结果;而实现 Runnable 接口的线程不能返回结
果;
Callable 接口的 calI ()方法允许抛出异常;而 Runnable 接口的 run ()方法的不允许抛异
常;
实现 Calable 接口的线程可以调用 Future . cancel 取消执行,而实现 Runnable 的线程
不能
注意点: Calable 接口支持返回执行结果,此时需要调用 FutureTask . get ()方法实现,此方
法会阻塞主线程直到获取到结果;当不调用此方法时,主线程不会阻塞!Java 多线程中调用 wait ()和 sleep ()方法有什么不同?
sleep ()来自 Thread 类, wait ()来自 Object 类,调用 sleep ()方法的过程中,线程不会释放
对象锁。而调用 wait 方法线程会释放对象锁
sleep ()睡眠后不出让系统资源, wait 让其他线程可以占用 CPU
sleep ( milliseconds )需要指定一个睡眠时间,时间一到会自动唤醒,而 wait ()需要配合
notify ()或者 notifyAll ()使用
CountDownLatch 工作原理
CountDownLatch 是 在 java1.5 被 引 入 的 , 存 在 于 java . util . concurrent 包 下 。
CountDownLatch 作用:能够使一个线程等待其他线程完成各自的工作后再执行。
CountDownLatch 底层是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个
线程完成了自己的任务后,计数器的值就会减 1。当计数器值到达 0 时,它表示所有的线程已
经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
什么是 FutureTask ?
在 Java 并发程序中 FutureTask 表示一个可以取消的异步运算。它有启动和取消运算、
查询运算是否完成和取回运算结果等方法。
只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个
FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也
是调用了 Runnable 接口所以它可以提交给 Executor 来执行。
Java 中的 volatile 变量是什么?你了解吗?
volatile 是一个特殊的修饰符,只有成员变量才能使用它。
volatile 修饰的变量不会缓存在工作内存(寄存器、高速缓存)中,保证每次读写变量都从
主内存中读,跳过 CPU Catch 这一步,当一个线程修改了这个变量的值,新值对与其他线程是
立即得知的, voatile 保证了内存的可见性,并禁止指令重排,所以效率上相对较低,因此只有在
必要使用的时候才会使用。
比如:状态标志的时时改变
加锁与使用 volatile 区别
(1) volatile 是轻量级同步机制。在访问 volatile 变量时不会执行加锁操作,因也就不会使执行
线程阻塞,是一种比 synchronized 关键字更轻量级的同步机制。
(2) volatile 无法保证内存原子性,可以确保可见性。加锁机制既可以确保可见性又可以确保原
子性。
(3) volatile 不能修饰写入操作依赖当前值的变量。
(4)当要访问的变量已在 synchronized 代码块中,或者为常量时,没必要使用 volatile :(5) volatile 屏蔽掉了 JVM 中必要的代码优化,所以在效率上比较低,因此一定在必要时才使
用此关键字。
volatile 禁止指令重排
指令重排序是 JVM 为了优化指令、提高程序运行效率,在不影响单线程程序执行结果的
前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。
在 JDK1.5 之后,可以使用 volatile 变量禁止指令重排序。针对 volatile 修饰的变量,在读
写操作指令前后会插入内存屏障,指令重排序时不能把后面的指令重排序到内存屏障
为什么要进行重排序呢?
假如执行 int a = 1 这句代码需要 100ms 的时间,但执行 int b = 2 这句代码需要 1ms 的
时 间,并且先执行哪句代码并不会对 a,b 最终的值造成影响。那当然是先执行 int b = 2 这句
代 码了。 所以,虚拟机在进行代码编译优化的时候,对于那些改变顺序之后不会对最终变
量的值造成 影响的代码,是有可能将他们进行重排序的。 然而,这种提高效率的重排序单
线程情况下没有问题,但是多线程情况下很有可能造成线程 不安全;我们可以使用 volatile
关键字保证代码指令的有序执行,进而保证线程的安全。 需要注意的是,虚拟机只是保证
volatile 修饰的变量之前的代码一定比它先执行,但并没有保证这 个变量之前的代码不可以
重排序。之后的也一样。
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代
码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一
样的,就是线程安全的。反之就是非安全的。
多线程的安全怎么保证
线程的安全性问题体现在:
1. 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
2. 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
3. 有序性:程序执行的顺序按照代码的先后顺序执行
导致原因:
1. 缓存导致的可见性问题
2. 线程切换带来的原子性问题
3. 编译优化带来的有序性问题
解决办法:
1. Atomic 开头的原子类、 synchronized 、L0CK 可以解决原子性问题
2. synchronized 、 volatile 、L0CK,可以解决可见性问题
3. Happens — Before 规则可以解决有序性问题线程的生命周期?
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状
态。在线程的生命周期中,它要经过新建( New )、就绪( Runnable )、运行( Running )、阻塞
( Blocked )和死亡( Dead ),5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独
自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
造成阻塞( sleep , wait , callable 中的 future . get )
注:
新建:new 关键字创建了一个线程之后,该线程就处于新建状态 JVM 为线程分配内存,
初始化成员变量值
就绪:当线程对象调用了 start()方法之后,该线程处于就绪状态 JVM 为线程创建方法
栈和程序计数器,等待线程调度器调度
运行:就绪状态的线程获得 CPU 资源,开始运行 run()方法,该线程进入运行状态
阻塞:
当发生如下情况时,线程将会进入阻塞状态
线程调用 sleep()方法主动放弃所占用的处理器资源 线程调用了一个阻塞式 IO 方
法,在该方法返回之前,该线程被阻塞
线程试图获得一个同步锁,但该同步锁正被其他线程所持有。
线程在等待某个通知(notify)
死亡:
线程会以如下 3 种方式结束,结束后就处于死亡状态:
run()或 call()方法执行完成,线程正常结束。
线程抛出一个未捕获的 Exception 或 Error。
调用该线程 stop()方法来结束该线程,该方法容易导致死锁,过弃方法,不推荐 使
用。
如何在两个线程间共享数据?
通 过 在 线 程 之 间 共 享 资 源 就 可 以 了 , 然 后 通 过 wait / notify / notifyAl ( 依 赖
Synchronize )、 await / signal / signalAl (依赖 Lock )进行唤起和等待,比方说阻塞队列
BlockingQueue 就是为线程之间共享数据而设计的。
Java 中 notify 和 notifyAll 有什么区别?
notify ()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之
地。而 notifyAlIO 唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
什么是 ThreadLocal ? ThreadLocal 你了解吗?
ThreadLocal 提供线程局部变量,即为使用相同变量的每一个线程维护一个该变量的副
本。在 ThreadLocal 类 中 定 义 了 一 个 ThreadLocalMap , 每 一 个 Thread 都 有 一 个
ThreadLocalMap 类型的变量 threadLocals , threadLocals 内部有一个 Entry , Entry 的 key
是 ThreadLocal 对象实例, value 就是共享变量副本;
ThreadLocal 的 get 方 法 就 是 根 据 ThreadLocal 对 象 实 例 获 取 共 享 变 量 副 本
ThreadLocal 的 set 方法就是根据 ThreadLocal 对象实例保存共享变量副本
主要使用在数据库连接等地方
为什么要使用线程池?
(1)降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
(3)提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会
降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
常用的几种线程池:
CachedThreadPool :可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
若无可回收,则新建线程。
FixedThreadPool :工作线程池。每当提交一个任务就创建一个工作线程,如果工作线程数
量达到线程池初始的最大数,则将提交的任务存入到阻塞队列中。
SingleThreadExecutor :单线程化线程池,即只创建唯一的工作者线程来执行任务,它只会
用唯一的工作线程来执行任务,保证所有任务按照指定顺序( FIFO , LIFO ,优先级)执行。
ScheduleThreadPool :定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及
周期性任务执行。
Spring 的线程池: TreadPoolTaskExecutor
线程池的拒绝策略
无 论 是 使 用 jdk 的 线 程 池 ThreadPoolExecutor 还 是 spring 的 线 程 池
ThreadPoolTaskExecutor 都会使用到一个阻塞队列来进行存储线程任务。当线程不够用时,则
将后续的任务暂存到阻塞队列中,等待有空闲线程来进行使用。当这个阻塞队列满了的时候,
会通过拒绝策略进行对后续的任务进行处理。
四种策略
AbortPolicy () 抛 出 java . util . concurrent . RejectedExecutionException 异 常 ( 默 认 )
CallerRunsPolicy ()重试添加当前的任务,他会自动重复调用 execute ()方法
DiscardOldestPolicy ()抛弃最旧的任务
DiscardPolicy ()抛弃当前最新的任务
如何避免死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现
象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,
规定所有的进程申请资源必须以一定的顺序做操作来避免死锁。
Java 中 synchronized 和 ReentrantLock 有什么不同?
ReenTrantLock 和 synchronized 都是可重入锁;
Synchronized 是 java 内置关键字, Lock 是个 java 类;
Synchronized 无法判断是否获取锁的状态, Lock 可以判断是否获取到锁;
Synchronized 会自动释放锁(线程执行完同步代码会释放锁;线程执行过程中发生异常
会释放锁), Lock 需在 finally 中手工释放锁( unlock ()方法释放锁),否则容易造成线程死锁;
ReenTrantLock 可以指定是公平锁还是非公平锁。而 synchronized 只能是非公平锁。所
谓的公平锁就是先等待的线程先获得锁。
ReenTrantLock 提供了一个 Condition (条件)类,用来实现分组唤醒需要唤醒的线程们,而
不是像 synchronized 要么随机唤醒一个线程要么唤醒全部线程。
ReenTrantLock 提供了一种能够中断等待锁的线程的机制,通过 lock . lockInterruptibly()
来实现这个机制。
注:Thread 类提供 interrupt()方法,用于中止正在被阻塞的任务。当打断一个正在被阻
塞 的任务时,通常还需要清理资源,这正如处理抛出异常,由于有这种需求存在,Java 在
设计 阻塞时中断方法时,即在调用 interrupt()设置线程为中断状态后,如果线程遇到阻塞就
会抛 出 InterruptedException 异常,并且将会重置线程的中断状态。I/O 和 synchronized,
lock.lock()块上的等待是不可中断的,lock.lockInterruptibly()允许被中断。
多线程安全问题解决-线程同步
要解决以上线程问题,只要在某个线程修改共享资源的时候,其他线程不能修改该资源,
等待修改完毕之后,才能去抢夺 CPU 资源,完成对应的操作,保证了数据的同步性,解决
了线程不安全的现象。
为了保证每个线程都能正常执行共享资源操作,Java 引入了 7 种线程同步机制。
1) 同步代码块(synchronized)
2) 同步方法(synchronized)
3) 同步锁(ReenreantLock)
4) 特殊域变量(volatile)
5) 局部变量(ThreadLocal)
6) 阻塞队列(LinkedBlockingQueue)
7) 原子变量(Atomic*)分布式锁
分布式环境下多个不同线程需要对共享资源进行同步,那么用 Java 的锁机制就无法实
现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。
那么目前有很多的技术能够实现分布式锁,我喜欢用 redis 来解决分布式锁,在使用
redis 时,需要用到几个核心的方法:
setIfAbsent ( key , value ):这个方法对应的 redis 命令是 setnx ,该方法会判断缓存中是否
存在该 key ,如果有返回 false ,如果没有,设置 value 值,并返回 true ,
getAndSet ( key , value ):这个方法对应的 redis 命令 get set ,该方法每次只允许一个线
程操作,它会获取 key 的上一个 value 值,并将当前 value 值放入缓存
在具体的实现中,首先,需要设置一个唯一的标识作为 key ,还需要将当前时间和有效时
间相加作为 value ,当多个线程调用该方法时,会先通过 setIfAbsent ( key , value )方法判断缓
存中是否存在该 key ,如果没有,说明目前还没有线程拿到锁,此时,可以将 key 和 value 存
入缓存,并返回 true ,表示该线程拿到锁,当线程执行完相关逻辑之后,调用解锁方法,将该线
程的缓存删除。
那么,这个时候,有可能出于某种原因,会使线程长时间还没有解锁,那为了防止锁超时、也
就是防止死锁的产生,新进的线程第一次没有拿到锁,但可以获取上一个锁的有效时间和当前
时间比较,如果当前时间大于上一个锁的有效时间,那么就说明上一个锁运行超时了,此时,可
以通过 getAndSet ( key , value )方法获取上一个锁的时间,并且将当前时间和过期时间相加作
为 value 放入缓存。
为了防止多个线程同时获取到锁,就对比当前线程获取的时间和上一次锁的到期时间相
同,说明该线程是最先执行的,那只有该线程拿到锁,返回一个 true ,其他线程返回 false 。
那么,通过这样一个设计就基本上可以实现分布式锁的问题,在实际项目开发当中,就比
如在秒杀、拍卖等业务中就可以利用这种锁的应用,这一个设计其实也是一个非公平锁。
有三个线程 T1,T2,T3,怎么确保它们按顺序执行?
可以用线程类的 join ()方法。(join 抢夺 cpu 执行权,A 执行到 B 的.join()方法时,A 等
待 B 结束后执行)
volatile 变量和 Atomic 变量有什么不同?
首先, volatile 变量和 Atomic 变量看起来很像,但功能却不一样。 Volatile 变量可以确
保先行关系,即写操作会发生在后续的读操作之前,但它并不能保证原子性。例如用 volatile
修饰 count 变量那么 count ++操作就不是原子性的。而 AtomicInteger 类提供的 atomic 方
法可以让这种操作具有原子性如 qetAndIncrement ()方法会原子性的进行增量操作把当前值
加一,其它数据类型和引用变量也可以进行相似操作。
你有哪些多线程开发良好的实践?
给线程命名最小化同步范围
优先使用 volatile
尽可能使用更高层次的并发工具而非 wait 和 notify ()
来实现线程通信优先使用并发容器而非同步容器.
考虑使用线程池
CountDownLatch 和 CyclicBarrier 区别:
CountDownLatch 的计数器只能使用一次。而 CyclicBarrier 的计数器可以使用 reset() 方
法重置。
所以 CyclicBarrier 能处理更为复杂的业务场景,比如如果计算发 生错误,可以重置计
数 器 , 并 让 线 程 们 重 新 执 行 一 次 CyclicBarrier 还 提 供 getNumberWaiting( 可 以 获 得
CyclicBarrier 阻塞的线程数 量)、isBroken(用来知道阻塞的线程是否被中断)等方法。
CountDownLatch 会阻塞主线程,CyclicBarrier 不会阻塞主线程,只会阻塞子线程
原子类:
Java 的 java.util.concurrent.atomic 包里面提供了很多可以进行原子操作的 类,分为以下
四类:
原子更新基本类型:AtomicInteger、AtomicBoolean、 AtomicLong
原子更新数组:AtomicIntegerArray、AtomicLongArray
原子更新引用:AtomicReference、 AtomicStampedReference 等
原子更新属性:AtomicIntegerFieldUpdater、 AtomicLongFieldUpdater
这些原子类的目的就是为了解决基本类型操作的非原子性导致在多线程并发情况下引
发的问 题。
你了解 JAVA 中的原子操作类吗?
当然,java.util.concurrent.atomic 包里面提供了很多可以进行原子操作的类,原子类 操
作基于 CAS 原理,CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量 时,
只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而 是
被告知这次竞争中失败,并可以再次尝试。CAS 有 3 个操作数,内存值 V,旧的预期值 A, 要
修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不
做。原子类操作相对于加锁 也有自己的优势,比如:实现原子操作可以使用锁,锁机制, 满
足基本的需求是没有问题的了,但是有的时候我们的需求并非这么简单,我们需要更有 效,
更加灵活的机制,如 synchronized 是基于阻塞的锁机制(悲观锁),也就是说当一个 线程
拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁。 这里会有些问题:
首先,如果被阻塞的线程优先级很高很重要怎么办?其次,如果获得 锁的线程一直不释放
锁怎么办?还有一种情况,如果有大量的线程来竞争资源,那 CPU 将会 花费大量的时间和
资源来处理这些竞争,同时,还有可能出现一些例如死锁之类的情况,最 后,其实锁机制
是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过 于笨重。
Java 内存模型(JMM 模型)java 的内存模型定义了线程和主内存之间的抽象关系,它的内容主要包括: 主要由多线
程共享的主内存和各线程私有的工作内存组成(工作内 存是个抽象概念,并不真实存在,是
对缓冲区,cpu 寄存器等的抽象) 变量都存储于主内存中,但是线程的工作内存中保存着要
使用的 变量在主内存中的副本。
线程对变量的操作必须在工作内存中进行,不同的线程无法直接 访问对方的工作内存,
相互通信必须经过主内存。
原子类操作与加锁 区别/优势
实现原子操作可以使用锁,锁机制满足基本的需求是没有问题的了,但是有的时候我们
需要 更有效,更加灵活的机制,如 synchronized 是基于阻塞的 锁机制(悲观锁),也就是
说当 一个线程拥有锁的时候,访问同一资源的其它线程需要等待,直到该线程释放锁。
而原子类的 CAS 操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共
享资源 的时候不会出现冲突,既然不会出现冲突自然而然就不会阻塞其他线程的操作。因
此,线程 就不会出现阻塞停顿的状态。
那么,如果出现冲突了怎么办?无锁操作是使用 CAS 又叫做比 较交换来鉴别线程是否
出现冲突,出现冲突就重试当前操作直到没有冲突为止。当且仅当内 存值和预估值一样的
时候,才会进行修改操作 其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数
器这样的需求有点儿过于笨 重。所以有的时候使用原子类粒度更小,更灵活,一些场景下
更合适一些
JVM
运行时数据区域:
堆( Heap ),是 Java 虚拟机所管理的内存中最大的一块。 Java 堆是被所有线程共享的一
块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对
象实例都在这里分配内存。
方法区( Method Area ),方法区( Method Area )与 Java 堆一样,是各个线程共享的内存区
域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数
据。运行时常量池属于方法区的一部分,用于存放编译器生成的各种字面量和符号引用。
程序计数器( Program Counter Register ),程序计数器( Program Counter Register )是一块较
小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
JVM 栈( JVM Stacks ),与程序计数器一样, Java 虚拟机栈( Java Virtual Machine Stacks )也
是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是 Java 方法执行的内存模型:每
个方法被执行的时候都会同时创建一个栈帧( Stack Frame )用于存储局部变量表、操作栈、动
态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚
拟机栈中从入栈到出栈的过程。
本地方法栈( Native Method Stacks ),本地方法栈( Native Method Stacks )与虚拟机栈所发
挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服
务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。
方法区和堆是所有线程共享的内存区域; JVM 栈、本地方法栈和程序员计数器是运行是
线程私有的内存区域。Java 垃圾回收机制
Minor GC 与 Full GC 分别在什么时候发生?
新生代内存不够用时候发生 Minor GC 也叫 Yong GC , JVM 内存不够的时候发生 Ful
GC 也叫 Major GC
Java 堆内存--分代收集: Java 堆主要分为 2 个区域-年轻代与老年代,其中年轻代又分
Eden 区和 Survivor 区,其中 Survivor 区又分 From 和 To 2 个
Eden 区
项目中,有将近 98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生
代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC ,
Minor GC 相比 Major GC 更频繁,回收速度也更快。
通过 Minor GC 之后, Eden 会被清空, Eden 区中绝大部分对象会被回收,而那些无需回
收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。
Survivor 区
Survivor 区相当于是 Eden 区和 Old 区的一个缓冲。 Survivor 又分为 2 个区,一个是
From 区,一个是 To 区。每次执行 Minor GC ,会将 Eden 区和 From 存活的对象放到
Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)
Old 区
老年代占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会
触发" Stop — The — World ”。内存越大, STW 的时间也越长,所以内存也不仅仅是越大就越
好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年
代这里采用的是标记———整理算法。
什么情况下会进入老年代?
1、需要大量连续内存空间的大对象,不管是不是“朝生夕死”,都会直接进到老年代。这样
做主要是为了避免在 Eden 区及 2 个 Survivor 区之间发生大量的内存复制。
2、虚拟机给每个对象定义了一个对象年龄( Age )计数器。正常情况下对象会不断的在
Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC ,年龄就
增加 1 岁。当年龄增加到 15 岁时,这时候就会被转移到老年代。当然,这里的 15, JVM 也支持
进行特殊设置。
3、通过 Minor GC 之后, Eden 会被清空, Eden 区中绝大部分对象会被回收,而那些无需
回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。每
次执行 Minor GC ,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不
够,则直接进入 Old 区)
垃圾回收算法
标记清除算法( Mark - Sweep )是最基础的一种垃圾回收算法,它分为 2 部分,先把内存区
域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。复制算法( Copying )是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。这一块的内存用完了,
就将还存活着的对象复制到另外一块上面,然后再把已伸用过的内存空间一次清理掉。保证
了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。
标记整理算法( Mark — Compact )标记过程仍然与标记———清除算法一样,但后婕步骤
不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外
的内存区域。
分代收集算法( Generational Colection )严格来说是融合上述 3 种基础的算法思想,而产
生的针对不同情况所采用不同算法的一套组合拳。根据对象存活周期的不同将内存划分为几
块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收
集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算
法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、
没有额外空间对它进行分配担保,就必须使用标记—清理或者标记———整理算法来进行回
收。
如和判断一个对象是否存活?(或者 GC 对象的判定方法)
1.引用计数法
所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象
时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象
没有被引用,也就是“死对象”,将会被垃圾回收,
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象 A 引用对象 B ,对
象 B 又引用者对象 A ,那么此时 A , B 对象的引用计数器都不为零,也就造成无法完成垃圾
回收,所以主流的虚拟机都没有采用这种算法
2.可达性算法(引用链法)
该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC
Roots 没有任何引用链相连时,则说明此对象不可用。
JVM 类加载机制
一般分为三步,也可以称为 5 小步,因为三大步是加载,链接,初始化,其中链接内是
验证,准备,解析三小步
1、加载(将文件加载到内存中)
2、 验证(确保 class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟
机自身的安全)
3、准备(准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配
这些变量所使用的内存空间)
4、解析(解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。
5、初始化(开始真正执行类中定义的 Java 程序代码)JVM 提供了 3 种类加载器
启动类加载器 Bootstrap ClassLoader (负责加载 JAVA _НОМЕ\ lib rt . iar )
扩展类加载器 Extension ClassLoader (负责加载 JAVA _НОМЕ\ lib \ ext )
应用程序类加载器 ApplicationClassLoader (负贡加载用户路径( classpath 上的类库)
内存溢出,内存泄漏
内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄潺似平不佘
有大的影响,但内存泄漏堆积后的后果就是内在溢出。
内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你二快存储
int 类型数据的存储空间,但是你却存储 long 类型的数据,那么结果就具内存不够用,此时就
会报错 OOM ,即所谓的内存溢出。
java 内存泄漏的根本原因是什么呢?
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期
对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是 Java 中内
存泄漏的发生场景。
JVM 参数调优
我们通过 jstate 命令查看 jvm GC 情况,根据 GC 情况进行调优:
在 JVM 启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任
何设置 JVM 会工作的很好,但对一些配置很好的服务和具体的应用必须仔细调优才能获得
最佳性能。
1.针对 JVM 堆的设置一般可以通过— Xms — Xmx 限定其最小、最大值,为了防止垃圾
收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
2.年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率
NewRadio 来调整二者之间的大小,也可以针对回收代,比如年轻代,通过- XX : newSize - XX :
MaxNewSize 来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把— XX :
newSize — XX : MaxNewSize 设置为同样大小
3.线程堆栈的设置:每个线程默认会开启 1M 的堆栈,用于存放栈帧、调用参数、局部变
量等,对大多数应用而言这个默认值太了,一般 256K 就足用。
数据结构
JDK1.7 和 JDK1.8 的 hashMap 有什么区别
jdk1.7 采用数组+单链表进行存储,扩容是扩容为 1.5 倍,1.8 扩容为 2 倍。jdk1.7 是通过键
的 hashcode 来快速的存取元素,不同对象发生碰撞时通过 hashMap 通过单链表来解决,将
新元素存入链表表头,通过 next 指向原有元素单链表在 java 中的实现就是对象的引用。
jdk1.8 中的 HashMap 存储结构是由数组、链表、红黑树这三种数据结构形成红黑树查询删
除快、新增慢。存储结构如下:根据 key 的 hash 与 table 长度确定 table 位置,同一个位置的 key 以链表形式存储,
超过一定限制链表转为树。数组的具体存取规则是 tab [( n - 1)&hash],其中 tab 为 node 数
组, n 为数组的长度, hash 为 key 的 hash 值
1)链表数据的临界值达到 8 就进行 resize 扩展,如果数组大于 64 则转换为树
2)加果数组的 size 大于 64,则把链表进行转化为树
3)当你删除结点时,这棵树的结点个数少于 6 个,会反树化,变为链表
MySQL 中两种引擎 myisam 与 innodb 的区别
MyISAM 是非事务的存储引擎,适合用于频繁查询的应用。表锁,不会出现死锁,适合小数
据,小并发。
Innodb 是支持事务的存储引擎,适合于插入和更新操作比较多的应用,设计合理的话是
行锁(最大区别就在锁的级别上),适合大数据,大并发。5.6 版本之后支持全文索引。
数据库索引的原理?索引底层数据结构?
索引是一个排序的列表在这个列表中存储着索引的值和包含这个值的数据所在的物理
地址,在数据十分庞大的时候,索引可以大大加快查询速度,这是因为使用索引后可以不用扫
描全表来定位某行的数据,而是通过索引表找到该行数据对应的物理地址然后访问相应的数
据。
优势:可以快速检索,减少 IO 次数,加快索引速度;根据索引分组和排序可以加快分组和
排序。
劣势:占用更多存储空间,因为索引本身也是表,因此会占用存储空间。索引表的维护和创
建需要时间成本,这个成本随着数据量增大而增大;构建索引会降低数据表的修改操作(删除,
添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;
索引数据结构 btree 和 hash 的区别
1. hash 索引查找数据基本上能一次定位数据,当然有大量碰撞的话性能也会下降。而
btree 索引就得在节点上挨着查找了,很明显在数据精确查找方面 hash 索引的效率是要高
于 btree 的;
2. 那为什么不用精确查找呢,也很明显,因为 hash 算法是基于等值计算的,所以对
“like”等范围查找 hash 索引无效,不支持
3. 对于 btree 支持的联合索引的最左前缀,hash 也是无法支持的,联合索引中的字段
要呢全用要么全不用
4. Hash 不支持索引排序,索引值和计算出来的 hash 值大小并不一定一致设计模式
设计模式的 7 大设计原则:
弃闭原则(对拓展开放对修改关闭通常用抽象构建框架用实现拓展细节)
依赖倒置原则(高层模块不直接依赖低层模块二者依赖其抽象)
单一职责原则(对于类/接口/方法负责的功能单一)
接口隔离原则(不使用单一的总接口使用多个专门职责的接口接口间产生隔离)
迪米特原则/最少知道原则(一个对象对于其他对象保持最少的了解从而降低 耦合)
里氏替换原则(在继承关系中子类可以拓展方法不能修改父类原有的方法降低 需求变
更带来的风险)
聚合复用原则(使用对象聚合而不是继承达到代码复用的目的)
JAVA 23 种设计模式
《创建型模式》对象实例化的模式,创建型模式用于解耦对象的实例化过程。
单例模式*:某个类只能有一个实例,提供一个全局的访问点。
简单工厂*:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂*:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
《结构型模式》把类或对象结合在一起形成一个更大的结构。
适配器模式*;将一个类的方法接口转换成客户希望的另外一个接口。
组合模式:将对象组合成树形结构以表示"“部分—整体"“的层次结构。
装饰模式*:动态的给对象添加新的功能。
代理模式*:为其他对象提供一个代理以便控制这个对象的访问。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
《行为型模式》类和对象如何交互,及划分责任和算法。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器
耿榄式:定义一系列算法,把他们封装起来,并且使它们可以相石替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为们
观察者模式*:对象间的一对多的依赖关系
备忘录模式:在不破坏封装的前提下,保持对象的内部状态
中介者模式:用一个中介对象来封装一系列的对免亦万
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构
Quartz
什么是 Quartz
官网:http://www.quartz-scheduler.org/
Quartz 是 OpenSymphony 开源组织在 Job scheduling 领域的一个开源项目,完全由 Java
开发,可以用来执行定时任务,类似于 java.util.Timer。但是相较于 Timer, Quartz 增加了
很多功能:
持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;
quartz 是一款开源且丰富特性的任务调度库,能够集成与任何的 java 应用,下到独立
应用,大到电子商业系统。quartz 就是基于 java 实现的任务调度框架,用于执行你想要执行
的任何任务。
什么是 任务调度 ?任务调度就是我们系统中创建了 N 个任务,每个任务都有指定的时间
进行执行,而这种多任务的执行策略就是任务调度。
quartz 的作用就是让任务调度变得更加丰富,高效,安全,而且是基于 Java 实现的,
这样开发者只需要调用几个接口做下简单的配置,即可实现上述需求。
1、Quartz 的使用场景
在项目开发中,我们经常遇到需要定时处理的任务,如前天的消息统计,定时生成报表
等
拿火车票购票来说,当你下单后,后台就会插入一条待支付的 task(job),一般是 30 分钟,
超过 30min 后就会执行这个 job,去判断你是否支付,未支付就会取消此次订单;当你支付
完成之后,后台拿到支付回调后就会再插入一条待消费的 task(job),Job 触发日期为火车
票上的出发日期,超过这个时间就会执行这个 job,判断是否使用等。
在我们实际的项目中,当 Job 过多的时候,肯定不能人工去操作,这时候就需要一个任
务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?
2、使用 Quartz
在使用 Quartz 之前,我们需要先明白 Quartz 的组成部分
Quartz 有 3 个核心要素:任务(Job)、触发器(Trigger)、调度器(Scheduler)1、Job(任务):
我们需要继承一个 QuartzJobBean 类(最终还是实现的 Job 接口),然后重写 execute( )
方法来定义需要执行的任务(具体的逻辑代码)
2、JobDetail(任务实例):
用于绑定指定的 Job,为 Job 实例提供许多属性(比如 name,group 等),
每次 Scheduler 调度执行一个 Job 的时候,首先会拿到对应的 Job,然后创建该
Job 实例,再去执行 Job 中的 execute()的内容,任务执行结束后,关联的 Job
对象实例会被释放,且会被 JVM GC 清除。
为什么设计成 JobDetail + Job,不直接使用 Job?
JobDetail 定义的是任务数据,而真正的执行逻辑是在 Job 中.任务是有可能并
发执行,如果 Scheduler 直接使用 Job,就会存在对同一个 Job 实例并发访问的问题。
而 JobDetail & Job 方式,Sheduler 每次执行,都会根据 JobDetail 创建一个新的 Job
实例,这样就可以规避并发访问的问题。
3、Trigger(触发器):
描述触发 Job 执行的时间。触发规则实现类 SimpleTrigger 和 CronTrigger。可以
通过 cron 表达式定义出各种复杂的调度方案。
3、Scheduler(调度器):
代表一个 Quartz 的独立运行容器。Trigger 和 JobDetail 可以注册到 Scheduler
中。Scheduler 可以将 Trigger 绑定到某一 JobDetail 上,这样当 Trigger 被触发时,
对应的 Job 就会执行。一个 Job 可以对应多个 Trigger,但一个 Trigger 只能对应一
个 Job.
调度器就相当于一个容器,用于绑定 Trigger 与 JobDetail。两者在 Scheduler 中拥有各自的不同的组及名称。
4、体系结构
5、导入依赖
<!-- Quartz 包-->(没有使用 SpringBoot 时使用)
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!--springboot 整合和 quartz-->
<dependency>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
6、简单使用 Quartz。(没有使用 springboot 时使用)
(1) 定义执行的任务
(2) 在方法中使用定时任务7、springboot 整合 Quartz
(1)创建任务
++
继承 QuartzJobBean 类,重写 executeInternal 方法
executeInternal 方法中,写我们需要定时执行的业务逻辑
(2)配置 JobDetail 并绑定 Job
可以看到我们在创建 JobDetail 的时候,将要执行的 job 的类名传给了 JobDetail,所以
scheduler 就知道了要执行何种类型的 job。每次当 scheduler 执行 job 时,在调用其 execute(…)
方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃
圾回收;
(3)配置 Trigger 触发器8、storeDurably()
在将 JobDetails 信息持久化到数据库的时候有一个属性 storeDurably,如果设置为
true 则无论与其关联的 Trigger 是否存 在其都会一直存在,否则只要相关联的 trigger
删除掉了其会自动删除掉 。默认为 true
大概意思是在没有 Trigger 指向 Job 的时候是否还需要继续存储。不传参时为 true,
也就是在没有 Trigger 指向 Job 的时候不会被删除掉
9、JobExecutionContext 和 JobDataMap()
JobExecutionContext 是什么:
当 Schedular 调用一个 Job,就会将 JobExecutionContext 传递给 Job 的 execute()方法
Job 能够通过 JobExecutionContext 对象访问到 Quartz 运行时候的环境以及 Job 本身的明
细数据
JobDataMap 是什么:
在进行任务调度时,JobDataMap 存储在 JobExecutionContext 中,非常方便获取
JobDataMap 可以用来装载任何可序列化的数据对象,当 Job 实例对象被执行时,这些
参数对象会传递给它
JobDataMap 实现了 JDK 的 Map 接口,并且添加了一些非常方便的方法用来存取基本数
据类型。
(1)传递参数
在构建 JobDetail 的时候,使用 usingJobData(key, value)方法提供的各种重载形式
来加入一些自定义的参数。
这 些 参 数 可 以 在 execute(JobExecutionContext
jobExecutionContext)方法中取得。
注:JobDetail 和 Trigger 中都可以添加 jobDataMap,后面的对象会把前面对象相同键
值对象的值覆盖。使用 JobDataMap 可以传递 Object 类型。
(2)获取数据
10、SimpleTrigger 和 CronTrigger
(1)、SimpleTrigger
SimpleTrigger 可以实现在一个指定时间段内执行一次作业任务或一个时间
段内多次执行作业任务,我们在上面的示例中,使用的就是 SimpleTrigger
(2)、CronTriggerCronTrigger 功能非常强大,是基于日历的作业调度,而 SimpleTrigger 是
精准指定间隔,所以相比 SimpleTrigger,CroTrigger 更加常用。CroTrigger
是基于 Cron 表达式的,要使用 CronTrigger 我们就需要先学习 Cron 表达式。
11、Cron 表达式
Cron 表达式是一个字符串,字符串以 5 或 6 个空格隔开,分为 6 或 7 个域,
每一个域代表一个含义,Cron 有如下两种语法格式:
1、Seconds Minutes Hours DayofMonth Month DayofWeek Year
2、Seconds Minutes Hours DayofMonth Month DayofWeek
corn 从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的
日期 年份Cron 表达式的一些示例
比如我们想要在 2021 的 6 月 10 日的上午 10 点 50 分 05 秒 来执行任务
5 50 10 10 6 ? 2021执行结果
常用方法
Object 类型
getClass():返回当前对象的运行时类
hashCode():生成哈希码值
toString():返回当前对象的字符串表示形式。
String 类型
equals(Object obj):判断调用者和参数对象描述的字符串内容是否相同
equalsIgnoreCase(String str):忽略大小写判断两个字符串内容是否相同
contains(String str):判断调用是否包含了 str 这个字符串
startsWith(String prefix):判断调用者是否以 prefix 开头
endsWith(String suffix):判断调用者是否以 suffix 结尾
isEmpty():判断调用者是否是空串
length():获取字符串的字符个数
charAt(int index):返回调用者字符传中索引为 index 的字符(和 length()方法结合之后可以遍历
字符串)
subString(int beginIndex):获取一个字符串,内容是从当前字符串的 beginIndex 索引开始
subString(int beginIndex,int endIndex):获取一个指定范围索引的字符串
注意事项
包含头不包含尾,返回的结果中,不包含 endIndex 索引上的字符
所有的方法都无法修改字符串的对象本身,一般都是返回一个新的字符串对象。
indexOf(char ch):返回 ch 字符在当前调用字符串中,第一次出现的位置。
lastIndexOf(char ch):返回 ch 字符在当前调用字符串中,最后一次出现的位置
byte[] getBytes():将当前字符串,转换成字节数组。
char[] toCharArray():将当前的字符串,转换成字符数组
toUpperCase():将当前的字符串,转成全大写形式
toLowerCase():将当前的字符串,转换成小写形式concat(String str):将当前调用者和参数 str 进行拼接,返回拼接后的字符串(不常用,因为更多的
是使用运算符加号)
valueOf(Object obj):可以将任意数据类型的数据,转换成字符串
注意:参数不能为 null 否则报错空指针异常
replace(String oldStr,String newStr):将调用者中的老串替换成新串
trim():去掉字符串左右两边的空格,中间空格存在
StringBuild 类型
StringBuilder():创建一个生成器,初始容量为 16 个字符
StringBuilder(int capacity):创建一个生成器,初始容量为 capacity 大小
StringBuilder(String str):创建一个生成器,初始化值 str 这些字符,初始大小是 str + 16
append(任意类型):可以将任意数据类型,转成字符,添加到生成器
Insert(int index,任意数据类型):可以将任意数据类型,添加到指定的位置
deleteChar(int index):删除指定索引的字符
delete(int start,int end):删除指定范围的字符,被删除的部分包含头不包含尾
replace(int start,int end,String str):将字符串缓冲区中从索引 start 开始到 end-1,替换成 str
reverse():将原有的字符序列进行反转
Integer 类型
Integer(int i):将一个基本数据类型的 int 数,转换成 Integer 类型的对象
Integer(String s):将一个字符串类型的数字(“123”),转换成 Integer 类型的对象
xxxVaule():可以将 Integer 类型的对象,转成其他的基本数据类型 如 byteValue()、floatVaule()
parseInt(String str):将 str 以十进制的方式解读为一个 int 数
valueOf(String str):将 str 转转换成 Integer 数据类型
Math
两个常量
E:自然对数的底数,2.718
PI:圆周率
常用方法
abs(数字类型):返回该参数的绝对值
cbrt(double d ):返回 d 的开立方根
sqrt(double d):返回 d 的正平方根
ceil(double d):返回 d 的向上取整
floor(double d):返回 d 的向下取整
max(int a,int b):返回 a、b 的较大值
min(int a,int b):返回 a、b 的较小值
random():返回 0.000~0.999 的随机数
round(double d):返回 d 四舍五入后的结果System 类
System.in:标准的输入流,默认关联到键盘上。
System.out:标准的输出流,默认关联到控制台
System.err:标准的错误输出流,默认关联到控制台,用于打印错误信息,在 eclipse 中,使用
该流打印的内容是红色。
常用方法:
gc():强制垃圾回收器,回收内存中的垃圾
exit():结束虚拟机·
currentTimeMillis():返回当前时间的毫秒值,表示的是从 1970 年 1 月 1 日 0 时 0 分 0 秒开
始到现在经历的毫秒值。1s=1000ms
Date 类型
该类对象用于表示一个特定的瞬间,根据构造方法使用不同,表示不同的瞬间。
构造方法:大部分构造方法都以过时(可以使用,但是不建议使用,eclipse 中有横线)。
Date():表示当前时间
Date(long time):表示一个时间对象,表示的是从 1970 年 1 月 1 日 00:00:00 时间经过 time
毫秒之后的时刻。
成员方法:
after(Date d):比较调用者是否在参数时间 d 之后
before(Date d):比较调用者是否在参数时间 d 之前
getTime():获取当前 date 对象描述的时间毫秒值
setTime(long time):将指定的毫秒值给当前的 Date 对象进行设置
Calendar
int get(int field)传入一个表示字段顺序的值,获取这个字段的值
Void set(int year,int month,int date,int hourOfDay,int minute,int second)
Void add(int field,int offset)在 field 的基础上,增加 offset
DateFormat
String format(Date d):将参数对象 d,格式化成一个字符串
Date parse(String str):将数字字符串 str,解析成一个 Date 类型的对象
SimpleDateFormat
SimpleDateFormat():使用默认的格式创建格式对象,
SimpleDateFormat(String pattern):使用给定的模式创建格式对象Collection
add(Object obj):将 obj 元素添加到集合中
remove(Object obj):将 obj 元素从集合中删除
clear():将集合中的元素清空
isEmpty():判断集合是否为空
contains(Object obj):判断集合中是否包含 obj 元素
size():返回集合中的元素个数
Object[] toArray():将调用者集合转成 Object 类型的数组
addAll(Collection c):将参数 c 中的所有元素,都添加到调用者集合中。
removeAll(Collection c):从调用者集合中,删除那些也存在于参数中的元素
cantainsAll(Collection c):判断调用者,是否能包含参数 c 中的所有元素。
retainAll(Collection c):参数 c 中有那些元素,就在调 集合中,保留哪些元素(交集)
迭代器 Iterator
hasNext():判断集合中是否还有下一个元素
next():获取集合中的下一个元素
remove():删除迭代器对象正在迭代的那个对象
List
add(int index,Object obj):在指定索引上,添加指定的元素
remove(int index):删除指定索引上的值
set(int index,Object obj):将指定索引上的值,修改为指定的值
get(int index):根据给定的索引,获取对应位置的值
Vector
addElement(Object obj):添加元素
removeElement(Object):删除元素
elements():获取 vector 集合中的枚举对象,用于遍历集合
LinkedList
addFirst(Object obj):在头部添加元素
addLast(Object obj):在尾部添加元素
removeFirst():删除头部元素
removeLast():删除尾部元素
getFirst():获取头部元素
getLast():获取尾部元素Map 中的常用方法
put(K key,V value):增加键值对
如果集合中已经存在 key 这个键,那么使用 put 方法,就是修改其对应的值
如果集合中不存在 key 这个键,那么使用 put 方法,就是在集合中增加了一个键值对
remove(K key):根据给定的键,删除对应的键值对:
clear():清空集合
size():获取集合中的大小获取键值对的对数
V get(K key):根据跟定的键获取对应的值:
containsKey(Object obj):判断集合中是否存在某个键
containsValue(Object obj):判断集合中是否存在某个值
Collections 工具类
int binarySearch(List<E> list , E e):在一个有升序顺序的 list 集合中,通过二分查找寻找元素 e 的
索引。
fill(List<E> list, E e):将 list 集合中的所有元素填充为元素 e
int frequency(Collection<E> c, E e):返回在集合 c 中的 e 的个数
Max,min:获取集合中的最大值和最小值
replaceAll(List<E> list, E oldVal , E newVal):将集合 list 中的所有指定的老元素 oldVal 都替换成新
元素 newVal
reverse(List<E> list):将参数集合 list 进行反转
shuffle(List<E> list):将 list 集合中的元素进行随机置换
swap(List<E> list, int a, int b):将 a 索引和 b 索引的元素进行交换
SynchronizedXxxx 方法系列:将一个线程不安全的集合传入方法,返回一个线程安全的集合
异常类型继承体系中的常用方法
Throwable 中的构造方法
Theowable():创建一个没有任何参数的异常对象
Throwable(String message):创建一个带有指定消息的异常对象
常用的成员方法
getMessage():获取异常的详细信息
toString():获取异常对象的详细信息
PrintStakTrace():打印异常的调用栈轨迹(有关异常的方法调用路径)File 类型的方法
File 类型的构造方法
File(String path):把字符串路径,封装成一个 File 对象
File(String parent,String child):将父级路径和子级路径封装成一个 File 对象,其实描述的是 父
级路径和子级路径拼接后的路径
File(File parent, String child):将父级 file 对象路径和子级路径封装成一个 File 对象,其实描 述
的也是父级路径和子级路径拼接后的路径。
File 类型的创建方法
boolean createNewFile():创建当前 File 对象,所描述的路径文件
boolean mkdir():创建当前 File 对象所描述的路径的文件夹(如果父级路径不存在,那么不 会
自动创建父级路径)
boolean mkdirs():创建当前 File 对象所描述的路径的文件夹(如果父级路径不存在,那么自 动
创建父级目录)
File 的删除方法
delete():删除调用者描述的文件或者文件夹
注意事项:
delete 在删除文件的时候,只能删除空文件夹
delete 方法不走回收站
File 类型的重命名功能
renameTo(File dest)
调用者是当前的文件或者文件夹的路径对象
参数是变化之后的文件或者文件夹的路径对象
注意事项
如果在同一个文件夹下,修改路径,就是重命名
如果不在同一个文件夹下,修改路径,就是剪切
File 类型常用的判断功能
exists():判断当前调用者 File 对象是否存在
isFile():判断当前调用者 File 对象,是否是文件
isDirectory():判断当前调用者 File 对象,是否是文件夹File 类型的获取功能
getAbsolutePath():获取当前 File 对象的绝对路径
getPath():获取的就是在构造方法中封装的路径。
getName():获取最底层的简单的文件或文件夹名称(不包含所在的目录的路径)
length():获取文件的大小(字节的个数)
注意事项
该方法被文件 File 对象调用时,返回的是文件的字节个数
该文件被文件夹对象 File 调用时,返回的数据时不正确的
String[] list():获取当前文件夹下的所有文件和文件夹名称,到一个字符串的数组中
File[] listFiles():获取当前文件夹下的所有文件和文件夹的 File 对象,到一个 File 对象数组 中 ,
那有了这个 File 对象的数组以后,我每次拿到一个 File 对象。
Inputstream
read():从当前的字节输入流中,获取一个字节
read(byte[] arr):将 arr.length 个字节,读取到 arr 中
OutputStream
write(int b):将一个字节信息写出到指定的设备中
write(byte[] arr):将一个字节数组中的所有信息,写出到指定设备中
write(byte[] arr, int offset, int len):将一个字节数组 arr 中从 offset 索引开始,总共 len 个字节写
出到指定设备中。
document 对象方法
getElementById(id 值) : 通过元素 ID 值,获取到某个元素对象,找到对象返回元素对象,如果找
不到对象,返回 null
getElementsByName(name值) : 通过元素的name值,获取到所有符合要求的元素对象,找到对
象返回数组,找不到返回一个空数组
getElementsByTagName(标签名) : 通过元素的标签名,获取到所有符合要求的元素对象,找到
对象返回数组,找不到返回空数组
getElementsByClassName(class 值) : 通过元素的 class 值,获取到所有符合要求的元素对象,找
到对象返回数组,找不到返回空数组
定时器
循环定时器
启动循环定时器 - setInterval(“调用函数”,毫秒值)
停止循环定时器 - clearInterval(定时器的 ID)一次性定时器
启动一次性定时器 - setTimeout(“调用方法”, 毫秒值)
停止一次性定时器 - clearTimeout(定时器 ID)(没有意义)
JS 事件总结
onload : 页面加载完成事件
onsubmit : 表单提交事件(true:表单提交,false:阻止提交)
onclick : 单击事件
ondblclick : 双击事件
onblur : 失去焦点事件(前提:必须先获取焦点)
onchange : 值改变事件
onkeydown : 键盘按下事件
onkeyup : 键盘抬起事件
onkeypress : 键盘点击事件(只能识别字母,数字,符号键)
onmousedown : 鼠标按下事件
onmouseup : 鼠标抬起事件
onmousemove : 鼠标移入事件
onmouseout : 鼠标移出事件
element 常用属性
value : 获取值(多用于 form 元素的值)
innerHTML : 获取文本(多用于非 form 标签元素的文本,包含 html 代码)
innerText : 获取文本(多用于非 form 标签元素的文本,不包含 html 代码)
nodeName : 获取节点的名称(元素名称)
parentNode : 获取该节点的父节点
childNodes : 获取该节点所有非属性的子节点集合
说明
DOM 中分为了 元素节点, 属性节点, 文本节点
元素节点 : 标签节点
属性节点 : 标签中的属性 如 width : <table width=”200”></table>
文本节点 : 页面中的文本 如 123,空格: <span>123</span>
空格
注意
获得子节点集合,下标为 0 的不一定是看到的元素,可能是第一个元素与当前节点之
间的空白文本
firstChild : 获取该节点的第一个非属性节点
lastChild : 获取该节点的最后一个非属性节点
nextSibling : 获取当前节点的下一个兄弟节点
previousSibling : 获取当前节点的上一个兄弟节点element 常用方法
getAttribute(“属性名”) : 获取属性值
setAttribute(“属性名”, “属性值”) : 设置属性值
hasChildNodes() : 判断是否存在子节点(注意空白符)
createTextNode(“字符串”) : 创建一个新的文本节点(document 对象中的方法)
createElement(“标签名”) : 创建一个指定名称的标签节点(document 对象中的方法)
注意:
只创建出来你是无法看到的,因为你还没有添加到 DOM 文档中,需要配合 appendChild()
方法才能显示
appendChild(节点) : 追加一个子节点最为最后的子节点
insertBefore(新节点,指定节点) : 在某个子节点前插入一个新的子节点
cloneNode(boolean 类型) : 克隆节点
克隆谁就是用谁来调用该方法,克隆后需要使用添加才有效,和 createElemen 类似,
参数为 true 或者 false,true 为复制该节点,false 则不复制该节点
removeChild(需要删除的节点) : 删除子节点
String 内置对象
length : 获取字符串长度
charAt(下标) : 通过下标获取字符
big() : 输出大体字
small() : 输出小体字
bold() : 输出黑字体
italics() : 输出斜体字
sub() : 输出下标字
sup() : 输出上标字
link(链接) : 产生一个超链接
fontcolor(颜色) : 指定使用某种颜色
fontsize(字体大小) : 使用某级别字体大小
indexOf(字符) : 第一次出现的位置
lastIndexOf(字符) : 最后一次出现的位置
substring(下标,下标) : 截取字符串
toUpperCase() : 转成大写
toLowerCase() : 转成小写
split() : 分割字符串
replace(old,new) : 替换字符串
Math 内置对象
abs(数值) : 输出绝对值
random() : 输出随机数sqrt(数值) : 输出平方根
round(数值) : 输出四舍五入之后的值
ceil(数值) : 向上取整
floor(数值) : 向下取整
max(数值 1,数值 2...) : 获取最大值
min(数值 1,数值 2...) : 获取最小值
Date 内置对象
获取方式
var today = new Date();
getYear() : 输出年分数,从 1900 年到现在的年份数(java 中 1970 年 1 月 1 日至今)
getFullYear() : 输出现在年份
getMonth() : 输出月份数(0-11)
getDate() : 输出当月天数
getDay() : 输出星期数(1-7)
getHours() : 输出小时数
getMinutes() : 获取的分钟数
getSeconds() : 输出秒数
getTime() : 输出毫秒数(从 1970/1/1 开始)
toLocaleString() : 转换成本地格式的字符串
toString() : 转换成 GMT 格式字符串
setXXX 方法 设置时间
内建函数
Number() : 相当于将字符串转化成数值,若参数无法转换成数值,则返回 NaN
isNaN() : 用于测试指定的字符串是否为非数值,如果是数值则返回 false,如果不是则返回 true
parseInt() : 剖析参数字符串并返回整数,如果直接遇到不合法的字符,则返回 NaN. 如果中间
遇到不合法的字符,马上停止剖析,返回已经剖析过的字符
parseFloat() : 剖析参数字符并返回浮点数
eval() : 接收一个字符串参数,如果参数字符串是一个表达式,就会返回表达式的结果值,如果
参数表达式没有值,那么返回 undefined
window 对象
Debugger 断点
console.log 打印日志
alert() : 弹出框
confirm() : 弹出确认框(返回结果是 boolean 类型,确认是 true,取消是 false)
prompt(参数 1,参数 2) : 可输入的弹出框 (用户在页面输入的值可以获取)
参数 1 : 提示信息 参数 2 : 默认值open(“url”,打开方式) : 打开新页面
打开方式 : _self, _blank, _parent, _top
打开的页面如果想要关闭,通过返回值可以调用 close()方法,进行关闭
showModallDialog(“url”) : 模态窗口(在 IE 浏览器中测试,了解)
浏览器对象
navigator 对象(了解) : 包含关于 web 浏览器的信息
常用属性
appCodeName : 获取浏览器名称,通常都是 Mozilla
appName : 获取完整的浏览器名称,通常返回 Netscape
appVersion : 返回浏览器的平台版本信息
location 对象(了解) : 包含关于当前 url 的完整信息对象
常用属性
href : 获取和设置整个 URL
protocol : 获取或设置 URL 的协议部分
search : 获取和设置 href 属性中跟在问号后面的部分
pathname : 执行指定的路径或文件
常用方法
reload() : 刷新当前页面
replace(文件地址) : 以指定的文件取代目前的文件
history 对象(常用) : 用于存储用户端曾访问过的 url 信息
常用方法
back() : 等同于浏览器中的后退按钮
forward() : 等同于浏览器中的前进按钮
go(索引)
索引为 1 : 等同于 forward
索引为 0 : 等同于 location.reload() 刷新
索引为-1 : 等同于 back()
screen 对象(了解) : 包含关于显示器屏幕的信息
常用属性
colorDepth : 显示器色彩数目
height : 显示器桌面区域的高度
width : 显示器桌面区域的宽度
SQL 的基本操作
DDL 库操作
创建库 create database 库名;
创建带字符集的数据库 create database 库名 character set gbk;
查看库 show databases;
删除库 drop database 库名;
修改库信息 alter database 库名 character set utf8;DDL 表操作
选择需要操作的数据库 use 数据库名;
//选择数据库
创建表 : 增(create)
create table 表名
(
列名 1 数据类型(长度),
列名 2 数据类型(长度),
列名 3 数据类型(长度),
........
列名 n 数据类型(长度)
);
注意 : 最有结尾的时就不需要加 ,
查看表信息 desc 表名;
查看所有表 show tables;
删除表 drop table 表名;
修改表,多增加一个字段 alter table 表名 add 列名 数据类型(长度);
修改表,给存在的列修改名称
alter table 表名 change 原列名 新列名 数据类型(长度);
修改表,替换表名称 alter table 原表名 rename 新表名;
DML 数据操纵语言
查看表中数据 select * from 表名;
DML 数据的操作(增加)
insert into 表名(列名 1, 列名 2, 列名 3, .....) values (值 1, 值 2, 值 3 .....);不用
考虑顺序问题
insert into 表名 values (值 1, 值 2, 值 3....);必须按照列名顺序存储
DML 数据的操作(删除)
delete from 表名; //删除表中所有数据 逐条删除,效率低
truncate 表名; // 最终效果 删除表中所有数据 删除表在创建新表
delete from 表名 where 列名 = 值; //最常用的条件删除
DML 数据的操作(修改) update 表名 set 列名 1=值 1,列名 2=值 2....... where 列 = 值;
DQL 数据查询语句
查询并展示表中所有数据 select * from 表名;
查询并展示表中符合要求的记录 select * from 表名 where 条件;
查询所有记录中的某些字段 select 字段 1,字段 2,.... form 表名
别名查询 select 列名 as 别名 from 表名;
去掉重复值 select distinct 列名 from 表名;
运算查询(查询结果是表达式)
select price + 10 from product;
比较运算符 > < = >= <= <>或者!= : 大于,小于,等于,大于等于,小于等于,不等
逻辑运算符
and : 多个条件同时成立
between .... and ... 显示在某一区间的值(含头含尾)
or : 多个条件任一成立in(列表) : 显示在 in 列表中的值, 只要满足括号中一个就可以
模糊查询 like : like 语句中, % 代表的是零个或多个任意字符
判断是否为空 select * from 表名 where 列名 is null;
not 不成立,取反 select * from 表名 t where 列名 is not null;
排序 select * from 表名 order by 排序字段 ASC/DESC;
聚合函数
count() : 统计总条数
select count(1) from product;
sum() : 计算指定列的数值和
select sum(price) from product;
max() : 计算指定列的最大值
select max(price) from product;
min() : 计算指定列的最小值
select min(price) from product;
avg() : 计算指定列的平均值
select avg(price) from product;
数学函数
abs() : 绝对值,所有数变成正数
select abs(price) from product;
ceil() : 向上取整
select abs(price) from product;
floor() : 向下取整
select floor(price) from product;
round() : 四舍五入(88.5)=88 (88.51)=89
select round(price) from product;
字符串函数
concat(字段 1,字段 2) : 连接俩个字符串,字段 2 追加到字段 1 后面
select concat(category_name,pname) from product;
substr(str, start, count) : 截取字符串,从 start 开始,取 count 个
select substr(category_name,2,2) from product;
replace(字段,oldStr,newStr) : 替换字符串,将字段中的 oldStr 替换成 newStr
select replace(category_name,"电脑", "服务器") from product where pid = 1;
reverse() : 反转字符串中的每个字符
select reverse(category_name) from product where pid = 1;
分组 select 字段 1,字段 2,.... from 表名 group by 分组字段;
SQL 的约束和策略
主键约束:primary key 唯一且不能为空
非空约束:NOT NULL 约束强制列不接受 NULL 值
唯一约束:UNIQUE 约束,唯一标识数据库表中的每条记录
自动增长-策略:auto_increment(自动增长列)关键字
外键约束:
alter table 从表 add constraint 外键 foreign key (从表外键的字段名称) references 主表(主表的主键);
多表查询
显示内连接 : select * from A inner join B on 条件;
隐式内连接 : select * from A,B where 条件;
左外连接 : select * from A left join B on 条件;
右外连接 : select * from A right join B on 条件;
子查询
子查询只要是单列, 肯定在 where 后面作为条件
Select 查询字段 from 表 where 字段 = (子查询)
子查询结果只要是多列, 肯定在 from 后面作为表
Select 查询字段 from (子查询) 表别名 where 条件
Limit 函数
Select * from 表 limit m, n;
书写顺序总结
Select distinct 列 From 表名 Join On 条件 Where 条件 Group by 分组 Having 条件
Order by 排序 Limit 分页
日期函数
获取当前日期
Now() : 获取 年月日 时分秒
Curdate() : 获取 年月日
获取日期的年份
Year(date) : 获取日期的年份
获取日期的月份
Monh(date) : 数字表示月份
Monthname(date) : 英文表示月份
获取日期的周数
Week(date) : 获取当年第几周
获取当前星期几
Weekday(date) : 0-6
Dayname(date) : 英文星期几
执行日期的减法运算 date_sub()Date_sub(date, interval expr type)
Union 和 union all
Union 和 union all 都是用来将两个语句合并成一个结果集
Union 内部的 select 语句必须拥有相同数量的列, 列也必须拥有相似的数据类型, 同时,
每条 select 语句中列的顺序必须相同
作用 : 多表中去除重复值
Select*from 表 union select * from 表
Union all
会列出所有的值 (不去重)
作用 : 常用于在没有表关系的情况下,将多个表字段组合在一起
select * from 表 union all select * from 表
JDBC 操作数据库步骤
注册驱动
Class.forName("com.mysql.jdbc.Driver");
获取连接
Connection
conn
=
DriverManager.getConnection("jdbc:mysql://localhost:3306/day10_db","root","1234");
获取预编译对象,并赋值
String sql = "select * from user where uid = ? and username = ?";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1, 7);
pst.setString(2, "xiaohong");
执行 SQL,并返回结果
ResultSet rs = pst.executeQuery();
处理结果
while(rs.next()) {
Integer uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + "---" + username + "---" + password);
}
关闭资源
rs.close();
pst.close();
conn.close();Request 方法
request.getServletContext() : 获取 ServletContext 对象
request.getParameter() : 获取参数
request.getParameterValues()
获取指定参数值,参数名称相同时,获取多个参数值,返回值为数组
request.getParameterNames():获取参数中的所有 name 属性,返回值为 Enumeration
request.getParameterMap()
获取所有参数,返回值为 Map,Map 中的 key 为属性名,value 为属性值,value 数据类型是一
个 String 数组
ServletContext 方法
setAttribute(String name, Object obj) : 向 ServletContext 中设置数据
getAttribute(String name) : 从 ServletContext 中获取数据
removeAttribute(String name) : 从 ServletContext 中移除数据
Response 方法
void addHeader(String name, String value)
void setHeader(String name, String value)
这两个方法都是用来设置 HTTP 协议的响应头字段,其中参数 name 用于指定响应头字段
的名称,参数 value 用于指定响应头字段值.
不同的是 : addHeader 方法可以增加同名的响应头字段,setHeader 方法则会覆盖相同的
头字段
void setCharacterEncoding(String charset)
该方法用于设置输出内容使用的字符编码,对 HTTP 协议来说,就是设置 Content-Type 头
字段中的字符集编码部分
void setContentType(String type)
该方法用于设置 Servlet 输出内容的 MIME 类型.
例如 : 如果发送到客户端的内容是jepg格式的图像数据,就需要将响应头字段的类型设
置为”image/jpeg”.需要注意的是,
如 果 响 应 的 内 容 为 文 本 ,setContentType() 方 法 还 可 以 设 置 字 符 编 码 , 如 :
text/html;charset=utf-8
getOutputStream(): 向响应体输出二进制字节流信息
getWriter() :向响应体输出字符流信息
sendRedirect(String url) :资源跳转,立即重定向类型的方法
Object 类型的概述
1. Object 类的构造方法 : Object()
(1) 可以自己创建对象
(2) 让子类访问, 所有子类都会直接或者间接的访问到这个顶层父类的构造方法
(3) Object 类在执行构造方法时, 不去访问自己的父类, 因为没有父类了
2. getClass()方法
(1) 返回当前对象的运行时类
(2) 返回值类型 : Class 类型的对象(不详细解释), 就是加载到方法区那个字节码文件对
象
(3) 其中 Class 类型中有一个方法 getName 方法, 可以返回当前类型的全类名
3. hashCode 方法
(1) 根据对象的情况, 生成的一个数字, 就是哈希码值. 生成数字的方法就是 hashCode
方法
(2) 同一个对象多次调用 hashCode 方法, 必须 返回相同的整数
(3) Object 类型的 hashCode, 会根据不同的对象生成不同的整数(确定)
(4) 使用 equals 方法判断相同的两个对象, 必须 返回相同的整数
4. toString()方法
(1) 返回当前对象的字符串表示
(2) 简称这个内容 : 对象的内存地址
(3) 对象返回这样的一个地址值的字符串,没有什么意义. 因此对于子类而言, 需要重
写父类的这个方法
(4) 重写原则 : 返回该对象中所有成员变量的值(对象属性)
(5) alt + shift + s s
5. equals()方法
(1) 用于比较两个对象是否相等的方法, 比较的是 调用者 和 参数 这个两个对象
(2) 自己和自己相等(自反性)
(3) A 和 B 相等, 那么 B 和 A 相等(对称性)
(4) A 和 B 相等, B 和 C 相等 那么 A 和 C 相等(传递性)
(5) A 多次调用 equals 方法用于和 B 对象比较, 应该返回相同的判断结果(一致性)
(6) 在 Object 类型中, 比较的是两个引用是否指向同一个对象. 如果是 才会返回 true,
否则返回 false. 相当于比较的是两个地址值是否相等
(7) 实际生活中, 比较两个对象的内存地址, 没有什么意义. 因此在自定义的子类中,
都要重写 equals 方法
(8) 重写的原则 : 一般比较两个对象中的所有属性
(9) alt + shift + s
hScanner 概述
1. 构造方法
(1) Scanner(InputStream is) : 扫描指定的输入流
1
其中学习过的第三个 Scanner(System.in), System.in 就是一个输入流, 标准输入
流,默认关联到键盘
2. 扫描器可以解析基本数据类型和字符串
(1) 基本数据类型录入
1 nextByte() : 获取一个 byte 数据
2 nextShort() : 获取一个 short 数据
3 nextInt()
4 nextLong()
5 nextDouble()
6 nextFloat()
7 nextBoolean()
(2) 录入字符串
1 next() : 可以录入下一个完整的标记, 返回一个字符串. 通过空格分隔各个标
记
2 nextLine() : 可以录入下一个完整的标记, 返回一个字符串. 通过换行符来分
隔各个标记
String
String 类型的构造方法
1. String() : 创建一个空字符串
2. String(String original) : 创建参数字符串的一个副本(参数字符串是在常量池中, 构造方法
创建的字符串对象是在堆内存中)
3. String(byte[] bytes) : 将一个字节数组转换成字符串
(1) 将我们不认识的字节数组, 转成我们认识的字符串
(2) 使用当前平台默认的编码表
4. String(byte[] bytes, int offset, int length) : 将字节数组的一部分转成字符串
(1) offset : 下标
(2) length : 截取长度
5. String(char[] value) : 将字符数组转成字符串
6. String(char[] value, int offset, int count) : 将字符数组的一部分转成字符串
String 类型的判断方法
1. equals(Object obj) : 判断调用者和参数对象描述的字符串内容是否相等
2. equalsIgnoreCase(String str) : 忽略大小写判断两个字符串是否相同3. contains(String str) : 判断调用者是否包含了 str 这个子串
4. startsWith(String prefix) : 判断调用者是否已 prefix 开头
5. endsWith(String suffix) :判断调用者是否已 suffix 结尾
6. isEmpty() : 判断调用者是否为空串
String 类型的获取方法
1. length() : 获取字符串字符的个数
2. charAt(int index) : 返回调用者字符串中索引为 index 的字符
3. substring(int beginIndex) : 获取一个字符串, 获取到的是从当前字符串的 beginIndex 索引
开启
4. substring(int beginIndex, int endIndex) : 获取一个指定索引范围的子串
(1) 包头不包尾, 不包含 endIndex 索引指向的字符
(2) 所有的方法都无法修改字符串的本身, 一般都是返回一个新的字符串
5. indexOf 家族
(1) indexOf(int ch) : 返回 ch 字符在当前调用者字符串中, 第一次出现的索引
(2) indexOf(int ch, int fromIndex) : 从 formIndex 所以开始寻找, 找到 ch 字符在当前字符
串中第一次出现的位置
(3) indexOf(String str) : 返回的是 str 这个字符串在调用者字符串中第一次出现的索引
(4) indexOf(String str, int fromIndex) : 从 fromIndex 索引开始寻找, 找到 str 字符串在当
前字符串中第一次出现的索引
6. lastIndexOf
(1) 和 indexOf 基本一样, 只不过是从后往前找, 所有字符和字符串的索引不会发生变
化
String 类型的转换功能
1. byte[] getBytes() : 将当前字符串, 转换成字节数组
2. char[] toCharArray() : 将当前字符串, 转成字符数组
3. toUpperCase() : 将当前字符串 转换成全大写的形式
4. toLowerCase() : 将当前字符串 转换成成全小写的形式
5. concat(String str) : 将当前调用者 和 参数 str 进行拼接, 返回拼接后的长字符串(不常用,
因为更多时候使用的是 + )
6. valueOf 家族: 可以将任意数据类型的数据, 准换成字符串
String 类型的其他方法
1. replace(String oldStr, String newStr) : 将调用者中的老串替换成新串
2. trim() : 去掉字符串左右两边的空格, 制表符(中间去不掉)StringBuilder
1. StringBuilder 是 一 个 可 变 的 字 符 序 列 , 常 用 的 方 法 是 append 和 insert, 就 是 在
StringBuilder 对象本身上, 进行修改操作
2. StringBuilder 和 String 类型一样, 底层都去维护了一个字符数组
3. String 和 StringBuilder 关系 : 都是用于描述字符串
(1) String 是一个不可变的字符序列, 没有提供修改私有成员变量的方法; StringBuilder
是可变的字符序列, 因为提供了修改成员变量的方法
(2) String 长 度 本 身 也 不 可 以 发 生 变 化 , StringBuilder 长 度 可 以 变 化 , 可 以 认 为
StringBuilder 就像一个可伸缩的容器, 用于存储字符
4. 构造方法
(1) 构造方法作用 : 创建当前对象将其他类型的数据转成当前类型
(2) StringBuilder() : 创建一个生成器,初始容量为 16 个字符
(3) StringBuilder(int capacity) : 创建一个生成器, 初始容器使用 capacity 指定的大小
(4) StringBuilder(String str) : 创建一个生成器, 初始值就是 str 这些字符, 初始大小 str
长度 + 16
5. 获取容积方法
(1) capacity() : 获取当前生成器容器大小
(2) length() : 获取当前生成器中字符的个数
添加功能
1. append(任意类型) : 可以将任意类型,转成字符串, 添加生成器中
2. insert(int index, 任意数据类型) : 可以将任意数据类型 添加到指定的位置
(1) index 的范围是 0~当前缓冲区的字符大小
(2) 插入数据之后, 数据本身的索引就是参数中指定的 index
删除功能
1. deleteCharAt(int index) : 删除指定索引的字符
2. delete(int start, int end) : 删除指定范围的字符, 被删除的部分包头不包尾
替换和反转
1. replace(int start, int end, String str) : 将字符串缓冲区中的从 start 开始到 end-1 结束的这部
分内容, 替换成 str
2. reverse() : 将原有字符串序列进行反转Integer
Integer 的构造方法
1. Integer(int i ) : 将一个基本类型的 int 值, 转换成 Integer 类型对象
(1) 使用 i 给 Integer 对象中的成员变量赋值
2. Integer(String s) : 将一个字符串类型的数字, 转换成 Integer 类型的对象
Integer 类型的成员方法
1. xxxValue() : 可以将 Integer 类型的对象, 转成其他的基本数据类型
2. 常用静态方法
(1) parseInt(String str) : 将 str 以十进制的方式解读为 int 数
(2) parseInt(String str, int radix) : 将 str 以指定的进制解析成一个 int 数
(3) toBinaryString(int i) : 使用二进制的表示方式 表示数字 i
(4) toOctalString(int i) : 使用八进制的表示方式 表示数字 i
(5) toHexString(int i) : 使用十六进制的表示方式 表示数字 i
(6) Integer.valueOf(String str, int radix) : 将 str 以指定的进制 radix 进行解析, 返回值
Integer
(7) Integer.valueOf(String str) : 将 string 类型的数字解析成 Integer 类型
Integer 类型的常量
1. MAX_VALUE : int 类型的最大值
2. MIN_VALUE : int 类型的最小值
3. SIZE : int 类型在内存中的位数
4. TYPE : int 类型在方法区中的字节码对象 int.class
Integer, int, String 类型互相转换总结
1. Integer 转成 int 类型
(1) intValue()
2. int 转换成 Integer 类型
(1) 构造方法 Integer(int i)
3. Integer 转换成 String
(1) toString()
4. String 转换成 Integer
(1) 构造方法 Integer(String str)
(2) Integer.valueOf(String str)
5. int 转换成 String
(1) Integer.toString(int i)(2) 拼接一个空串
6. String 转换成 int 类型
(1) Integer.parseInt(String str)
正则表达式
正则表达式的字符类
1. 普通的字符串就是一个正则表达式, 但是只能表达自己本身, 无法匹配一类字符串
2. 判断某个字符串是否和某个正则表达式的规则匹配, 使用 String 中的 matches 方法
3. 字符类型 : 表示单个的字符, 使用的符号是中括号[]
(1) 说明 : 只要使用了中括号, 无论里面写了多少内容, 都表示单个字符
4. 中括号的表达式
(1) [abc] : a 或者 b 或者 c 中的一个
(2) [^abc] : 除了 a b c 以外的任何单个字符
(3) [a-zA-Z] : a~z A~Z 中任意单个字符
预定义字符类概述
1. 有一些字符类型经常使用, 所以就提前在正则表达式中进行了定义, 称为预定义字符类
符号
1. . 任何字符, \.表示一个确定的.字符
2. \d : 表示数字字符
3. \D : 表示非数字字符
4. \s : 表示空格字符
5. \S : 表示非空格字符
6. \w : 表示[a-zA-Z_0-9]
7. \W : 表示出了\w 以外的所有字符
数量词
1. 无论是字符类型还是预定义字符类型都只能表示单个字符, 无法表示 0 个或者多个字符.
需要使用一个数量词来修饰字符的个数
2. 注意事项 : 数量词修饰的是紧挨在自己前面的那个字符, 与其他类型字符无关
3. 分类
(1) 模糊的数量词
1 X? : 表示 X 这个字符, 出现 0 次或者 1 次
2 X+ : 表示 X 这个字符, 出现 1 次或者多次
3 X* : 表示 X 这个字符, 出现 0 次或者 1 次或者多次
(2) 精确的数量词
1 X{n} : 表示 X 这个字符, 恰好出现 n 次2 X{n,} : 表示 X 这个字符, 至少出现 n 次
3 X{n,m} : 表示 X 这个字符, 至少出现 n 次 最多出现 m 次
String 中和正则表达式有关的三个方法
1. boolean matches(String regex) : 判断当前字符串是否和参数正则表达式匹配
2. String[] split(String regex) : 使用指定的正则表达式切割当前字符串
3. String replaceAll(String regex, String replaceMent) : 将调用者字符串中的所有匹配 regex 正
则的子串, 替换成 replaceMent 新串
Math
1. 两个常量
(1) E : 自然对数的底数, 2.718...
(2) PI : 圆周率
2. 常用方法
(1) abs(数字类型) : 返回参数的绝对值
(2) cbrt(double d) : 返回 d 的开立方根
(3) sqrt(double d) : 返回 d 的正平方根
(4) floor(double d) : 返回 d 的向下取整
(5) ceil(double d) : 返回 d 的向上取整
(6) max(int a, int b) : 返回 a b 的较大值
(7) min(int a, int b) : 返回 a b 的较小值
(8) random() : 返回 0.000~0.999 的随机数
(9) round(double d) : 返回 d 的四舍五入的值
System
1. 用于描述系统资源的类型, 该类不用创建对象, 直接使用静态变量和静态方法即可
2. 常用字段
(1) System.in : 标准的输入流, 默认关联键盘上
(2) System.out : 标准的输出流, 默认关联控制台上
(3) System.err : 标准的错误输出流, 默认关联到控制台上, 用于打印错误信息, 在
eclipse 中使用该流打印的内容是红色的
3. 常用方法
(1) gc() : 强制垃圾回收器回收内存中的垃圾
(2) exit(0) : 结束虚拟机
(3) currentTimeMillis() : 返回当前时间的毫秒值, 表示的是从 1970 年 1 月 1 日 0 点 0
分 0 秒开始到现在经历的毫秒值
Date
1. 该类对象用于表示一个特定的瞬间, 根据构造方法使用的不同, 表示不同的瞬间2. 构造方法 : 大部分构造方法都已经过时(可以使用,但是不建议使用, 在 eclipse 中有横线)
(1) Date() : 表示当前时间
(2) Date(long time) : 表示一个时间对象, 表示的是从 1970 年 1 月 1 日 00:00:00 时间经
过的 time 毫秒之后的时刻
3. 成员方法
(1) after(Date d) : 比较调用者是否在参数时间 d 之后
(2) before(Date d) : 比较调用者是否在参数时间 d 之前
(3) getTime() : 获取当前 Date 对象描述的事件毫秒值
(4) setTime(long time) : 将制定的毫秒值给当前 date 对象进行设置
DateFormat
1. 直接打印 Date 对象全是英文, 不好阅读, 需要有一个格式对象, 将 Date 对象的内容以指
定的格式打印出来
2. DateFormat 类, 抽象类, 不能创建对象, 需要使用子类闯将对象, 有一个已知子类
SimpleDateFormat 类型
3. DateFormat 是属于 text 包下的, 所以需要导包
4. 重要的成员方法
(1) String format(Date d) : 将参数对象 d, 格式化成字符串
(2) Date parse(String str) : 将参数字符串 str, 解析成一个 Date 类型的对象
SimpleDateFormat
1. DateFormat 是抽象类, 无法创建对象, 所以只能使用子类来创建对象, 并调用方法.
2. 构造方法
(1) SimpleDateFormat() : 使用默认的格式创建格式对象, 默认格式 : 20-1-14 下午
3:40
(2) SimpleDateFormat(String pattern) : 使用指定格式创建一个格式对对象
1Calendar 概述
1. 表示一个精确的瞬间, 是一个包含了很多字段值的对象
2. 在 util 包下, 所以在使用的时候需要导包
3. Calendar 是一个抽象类, 无法直接创建对象
(1) 自己使用子类来创建
(2) 使用父类中的某个方法, 来获取一个对象
4. Calendar 获取对象的方法
(1) Calendar.getInstance() : 可以获取当前时间的 Calendar 对象, 在获取后就已经将各
个日历字段都初始化完成
5. 常用方法
(1) get, set 方法用于给对象的时间进行获取和设置
(2) add : 方法可以给对象的某个字段增加值
(3) getTimeInMills : 获取对象描述的毫秒值
(4) getTime : 获取一个 Date 类型的对象
Calendar 的 get 方法
1. 直接打印 Calendar 对象, 显示所有字段的值, 只需要获取某个确定字段的值, 就是用 get
方法
2. int get(int field)
(1) 传入一个表示字段顺序的值, 获取一个这个字段的值
(2) 参数其实就是一个有顺序数字, 但是不方便记忆, 所以将这些顺序数字定义成了
Calendar 类型中的常量, 使用常量名称来代替这些数字
3. 说明
(1) 获取月份 : 从 0-11 值, 获取出来之后 +1 才是我们中国人习惯的月份
(2) 获取星期 : 周日是第一天, 需要做一些其他的转换才能编程中国人习惯的星期
Calendar 的 set 方法
1. get 方法是用于给定一个字段, 获取一个字段的值. set 方法, 根据给定的字段和字段的值,
修改 Calendar 类型的对象
2. void set(int field, int value)
(1) 将制定的字段, 设定为指定的值
(2) 注意 : 如果写了不正确的时间值, 会自动进位
3. void set(int year, int month, int date)
(1) 给当前 Calendar 设置年月日
4. void set(int year, int month, int date, int hourOfDay, int minute, int second)
(1) 给当前的 Calendar 设置年月日 时分秒Calendar 的 add 方法
1. 方法功能 : 在某个字段的基础上, 进行偏移(描述的是变化, 而不是变化的结果)
2. void add(int field, int offset)
(1) 在 field 字段原来值的基础上, 进行增加 offset 量
毫秒值和 Date 类型和 Calendar 类型的相互转换
1. 毫秒值和 Date 相互转换
(1) 毫秒值转成 Date 对象 : Date 类型的构造方法 Date(long time)
(2) Date 对象转成毫秒值 : Date 类型中 getTime()方法
2. 毫秒值和 Calendar 类型的相互转换
(1) 毫秒值转换成 Calendar 类型 : setTimeInMills(long tile)
(2) Calendar 类型转成毫秒值 : getTimeInMills()
3. Date 和 Calendar 类型相互转换
(1) Calendar 类型转成 Date 对象 : getTime()
(2) Date 对象转成 Calendar 类型 : setTime(Date d)
Collection
1. 常用方法
(1) add(Object obj):将 obj 元素添加到集合中
(2) remove(Object obj):将 obj 元素从集合中删除
(3) clear():将集合中的元素清空
(4) isEmpty():判断集合是否为空
(5) contains(Object obj):判断集合中是否包含 obj 元素
(6) size():返回集合中的元素个数
(7) toArray():将调用者集合转成 Object 类型的数组
Collection 中带 all 的方法
1. addAll(Collection c):将参数 c 中的所有元素,都添加到调用者集合中。
2. removeAll(Collection c):从调用者集合中,删除那些也存在于参数 c 中的元素
3. containsAll(Collection c):判断调用者,是否能包含参数 c 中的所有元素。
4. retainAll(Collection c):参数 c 中有那些元素,就在调用者集合中,保留哪些元素(交集)
1. 获取:集合自己内部就应该有一个可以迭代自己的对象,从集合对象中获取即可。
集合遍历的第一种方式:toArray()
1. 第一种:转成数组,toArray(),不带泛型的转数组,得到的就是 Object 类型的数组集合遍历的第二种方式:迭代器
(1) Iterator iterator()
2. 迭代器的使用:
(1) 方法 iterator()返回的是一个 iterator 接口的实现类对象,可以使用的就是 iterator 接
口中的方法。
(2) hasNext():判断集合中是否还有下一个元素
(3) next():获取集合中的下一个元素
(4) remove():删除迭代器对象正在迭代的那个对象
集合遍历第三种:增强 for 循环
(1) 格式:
1 for(元素的数据类型 元素名称 : 要遍历的集合){
2
使用元素名称代表当前访问的元素。
3 }
(2) 说明:
1
元素的数据类型:指的是要遍历集合中的元素的数据类型
2
元素名称:虽然是固定的,但是随着循环的执行,每次代表的元素却不相同
3
要遍历的集合:单列集合或者是数组。
list 集合特有的遍历方式
1. 针对 list 集合特有的遍历方式
2. 可以通过集合中的size()方法获取集合索引的范围,根据索引通过get方法可以回去指定索
引的值。
List
1. 特有方法:
(1) add(int index,Object obj):在指定索引上,添加指定的元素
(2) remove(int index):删除指定索引上的值
(3) set(int index,Object obj):将指定索引上的值,修改为指定的值
(4) get(int index):根据给定的索引,获取对应位置的值
Vector
1. 特有方法
(1) addElement(Object obj):添加元素(2) removeElement(Object):删除元素
(3) elements():获取 vector 集合中的枚举对象,用于遍历集合
2. 特有的遍历方式:
(1) 使用 elements 方法,获取 Enuneration 对象
(2) 使用 Enumeration 对象的 hasMoreElements 方法判断是否有下一个元素
(3) 如果有下一个元素,就使用 nextElement 方法获取下一个元素
LinkedList
1. LinkedList 的特有方法:
(1) 由于在 LinkedList 中,维护了头和尾的节点的对象地址,所以操作头部和尾部非常
容易,提供了大量的操作头和尾的方法。
1 addFirst(Object obj):在头部添加元素
2 addLast(Object obj):在尾部添加元素
3 removeFirst():删除头部元素
4 removeLast():删除尾部元素
5 getFirst():获取头部元素
6 getLast():获取尾部元素
Map 中的常用方法
1. 增加键值对:put(K key,V value)
2. 删除方法:
(1) 根据给定的键,删除对应的键值对:remove(K key)
(2) 清空集合:clear()
3. 获取方法:
(1) 获取集合中的大小:size(),获取键值对的对数
(2) 根据跟定的键获取对应的值:V get(K key)
4. 判断方法:
(1) 判断集合中是否存在某个键:containsKey(Object obj)
(2) 判断集合中是否存在某个值:containsValue(Object obj)
5. 修改方法:
(1) 根据给定的键,修改对应的值:put(K key , V value)
(2) 如果集合中已经存在 key 这个键,那么使用 put 方法,就是修改其对应的值
(3) 如果集合中不存在 key 这个键,那么使用 put 方法,就是在集合中增加了一个键值对
Map 集合的第一种遍历思路[熟练掌握]
1. 获取 Map 集合中的所有键,放到 set 集合中,遍历 set 集合,获取到每一个键,根据键在来获
取对应的值[根据键获取值]
2. 获取 Map 集合中的所有键
(1) Set<K> keySet()3. 遍历 set 集合的两种方式
(1) 迭代器
(2) 增强 for 训话
4. 拿到每个键之后,获取对应的值
(1) V get(K key)
Map 集合的第二种遍历思路[熟练掌握]
1. 获取 Map 集合中所有的键值对对象(Entry),到 set 集合中,遍历 set 集合,拿到的是每个键值
对对象(Entry),从这个对象中分别获取键和值[根据键值对对象获取键和值]
2. 根据 Map 集合获取所有的键值对对象,到一个 set 集合当中
(1) Set<map.Entry<K, V>>
entrySet();
3. 遍历 set 集合的两种方式
(1) 迭代器
(2) 增强 for 循环
4. 获取某个键值对对象
(1) Entry 是 Map 接口中的内部接口,访问的方式 Map.Entry
(2) Entry:入口、条目、登记记录
1 getKey():获取当前键值对对象的键
2 getValue():获取当前键值对的值
Collections 工具类
1. int binarySearch(List<E> list , E e):在一个有升序顺序的 list 集合中,通过二分查找寻找 e 的
索引。
2. fill(List<E> list, E e):将 list 集合中的所有元素填充为元素 e
3. int frequency(Collection<E> c, E e):返回在集合 c 中的 e 的个数(非坤 C)
4. max , min:获取集合中的最大值和最小值
5. replaceAll(List<E> list, E oldVal , E newVal):将集合 list 中的所有指定的老元素 oldVal 都替换
成新元素 newVal
6. reverse(List<E> list):将参数集合 list 进行反转
7. shuffle(List<E> list):将 list 集合中的元素进行随机置换
8. swap(List<E> list, int a, int b):将 a 索引和 b 索引的元素进行交换
9. SynchronizedXxxx 方法系列:将一个线程不安全的集合传入方法,返回一个线程安全的集合
异常类型继承体系中的常用方法
1. 发现在异常的继承体系中,所有的方法定义在了Throwable这个顶层父类中,子类几乎没有
什么特有的方法。
2. Throwable 中的构造方法
(1) Theowable():创建一个没有任何参数的异常对象
(2) Throwable(String message):创建一个带有指定消息的异常对象3. 常用的成员方法
(1) getMessage():获取异常的详细信息
(2) toString():获取异常对象的详细信息
(3) PrintStakTrace():打印异常的调用栈轨迹(有关异常的方法调用路径)
File
File 类型的构造方法
1. File(String path) : 把字符串的路径,封装成一个 File 对象
2. File(String parent, String child) : 将父级路径和子级路径封装成一个 File 对象,其实描述的是
父级路径和子级路径拼接后的路径
3. File(File parent, String child) : 将父级 File 对象路径和子级路径封装成一个 File 对象,其实描
述的也是父级路径和子级路径的拼接后的路径
File 类型的创建方法
1. boolean createNewFile() : 创建当前 File 对象,所描述的路径的文件
2. boolean mkdir() : 创建当前 File 对象所描述的路径的文件夹(如果父级路径不存在,那么不
会自动创建父级路径)
3. boolean mkdirs() : 创建当前 File 对象所描述的路径的文件夹(如果父级路径不存在,那么自
动创建父级目录)
File 类型的删除方法
1. delete() : 删除调用者描述的文件或者是文件夹
2. 注意事项:
(1) delete 在删除文件夹的时候,只能删除空文件夹
(2) delete 方法不走回收站
File 类型的重命名的功能
1. renameTo(File dest)
(1) 调用者是当前的文件或者是文件夹的路径对象
(2) 参数是变化之后的文件或者文件夹的路径对象
2. 注意事项
(1) 如果在同一个文件夹下,修改路径,就是重命名
如果不在同一个文件夹下,修改路径,就是剪切
File 类型常用的判断功能
1. exists() : 判断当前调用者 File 对象,是否存在
2. isFile() : 判断当前调用者 File 对象,是否是文件
isDirectory() : 判断当前调用者 File 对象,是否是文件夹
File 类型的获取功能
1. getAbsolutePath() : 获取当前 File 对象的绝对路径
2. getPath() : 获取的就是构造方法中封装的路径
3. getName() : 获取最底层的简单的文件或者文件夹名称(不包含目录的路径)
4. length() :获取文件的大小(字节个数)
(1) 注意事项
1
该方法被文件 File 对象调用时,返回的是文件的字节个数
2
该方法被文件夹 File 对象调用时,返回的数据是不正确的
5. String[] list() : 获取当前文件夹下的所有文件和文件夹名称,到一个字符串的数组中
6. File[] listFiles() : 获取当前文件夹下的所有文件和文件夹的 File 对象,到一个 File 对象数组 文章来源:https://www.toymoban.com/news/detail-419326.html
中,那有了这个 File 对象的数组以后,我每次拿到一个 File 对象文章来源地址https://www.toymoban.com/news/detail-419326.html
到了这里,关于java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!