一、Java基础语法
1.参数传递
void change(String s, Integer x, Double f, ArrayList<Integer> list, Node node, char[] a);
- 前三个是传值调用,方法里修改形参不会对原来的值造成影响;
- 后三个是引用调用,方法里修改形参会对原来的值(地址中的值)造成影响;
2.静态变量能否用this调用?
在语法上行得通,但是静态变量是所有实例共享的一个变量,而this通常指当前对象;用对象名调用普通变量,用类名调用静态变量更加直观。许多题目把this调用直接打死,按它们的来。
3.形参引用
静态变量或普通变量a和方法形参a同名,不论类型是否相同,在方法里都是优先使用形参。方法里形参是老大,所以set方法形参同名需要加上this,不同名可以不加this。
4.初始化二维数组
不一定按照n*m矩阵来初始化
int[][] a=new int[][]{ {1,2},{3,4,5,6,7},{1},{33},{21,352} };
a.length=5;a[0].length=2;a[1].length=5;
等号左边合法的有:int[][] a、int[] b[]、int c[][];等号右边合法的有new int[5][5]、new int[5][]、{}、{{}};
5.try-catch-finally
- try中有异常不会执行异常语句后的代码,因此有异常时try中的return作废;
- catch,有异常才会执行,return等级>try的return;
- finally,有没有异常都会执行,return等级>catch>try;
- try必然要有,catch和finally至少保留一个;
(1)try和finally都没有return
- 如果try没有异常则执行try→finally;
- 如果try有异常,则执行try→catch→finally;
- 并且会继续执行finally后的代码块,若是停了那就体现不出捕获异常的作用了;
如果try里有异常并且finally里又有异常,如果不捕获finally里的异常则程序终止(不会运行finally后的代码),捕获则按常理度之;
(2)try有return,catch没有return,finally没有return
由于try中异常语句后的代码不会执行,所以finally代码块后还有需要有return语句兜底;
- 如果try有异常,执行try-catch-finally,最终返回值取finally代码块后的return;(如果catch里有return 则取catch里的,最后的return语句作废;)
- 如果没有异常,执行try代码块就跑了,返回值取try里的;
(3)try有return,catch有return,finally没有return
- try没有异常,返回值取try的;
- try有异常,返回值取catch的;
finally代码块后不可以有代码,因为try或者catch会返回函数,不执行到后面;
(4)finally有return
不论try和catch有没有return,返回值都取finally里的,并且finally代码块后不允许有代码了;
(5)没有catch,有finally
无返回值时,发生异常程序终止(不会执行finally后的语句),因此catch是解决异常的关键;
6.运算&赋值
- System.out.println( (5>4)?8:4.05 );//输出8.0
- (short)10/10.2*2; 结果是double类型,先将10强转为short,又因为运算过程自动向上转型变成double的运算,最后结果是double类型
- int x=20,y=5;System.out.println(x+y+""+(x+y)+y);//25255
- System.out.println(5+6+""+5+7);//1157 任何字符与字符串相加都是字符串,但是字符串前面的按原格式相加后拼接,字符串后面的直接按字符串拼接
- 运算优先级:>、<、&&、||。例如b=x>50&&y>60||x>50&&y<-60||x<-50&&y>60||x<-50&&y<-60;变成b=(x>50&&y>60)||(x>50&&y<-60)||(x<-50&&y>60)||(x<-50&&y<-60);
- 乘除模运算优先级相同,10%3*2=1*2=2;
- int a=1_0_2_4;数值间插入下划线这是合法的;
- char、byte和short参与运算时会自动将这些值转换为int类型再进行运算;short a1=3,a2=4,a3;a3=a1*a2;这样会报错,但是a3=3*4;不会报错;
7.Java变量
- 实例变量:定义在类中但不在方法里,即类的成员属性。具有默认值,数值类型默认0,布尔类型默认false,引用类型默认null,可以在声明时指定,也可以在构造方法中指定;
- 局部变量:方法中的变量,需要先赋值再运算。不赋值直接使用会导致 编译不通过。在方法中任意写个花括号也属于一个局部,在花括号内定义的变量不能用在括号外;
- 静态变量:类似方法,只加载一次,放在堆里,类的多个对象共享一个静态变量(方法);
8.StringBuilder、StringBuffer、String
- 线程安全性:StringBuffer和String线程安全,StringBulider线程不安全。
- 运行速度:StringBuilder>StringBuffer>String。
9.数组复制效率
System.arraycopy > clone > Arrays.copyOf > for循环
System.arraycopy和clone都是native方法,记一下就好。Arrays.copyOf是对System.arraycopy的调用,多了一层肯定不是最快的,for循环我都会写肯定是最慢的。
10.Java程序的种类
内嵌于Web文件中由浏览器观看的Applet;可独立运行的Application;服务器端的Servlets。
11.注释与编译
注释是给人看的,不是给机器看的。编译器不会编译注释中的内容;字节码文件也没有注释内容;注释过多也不会导致编译后的程序尺寸变大。
点开一些jar包的看看字节码文件.class,发现全都没有注释,然而Java文件就有注释。
12.基本数据类型的声明
double d0=1;//正确,输出1.0 double d1=1.0;//正确,输出1.0 Double d2=1.0;//正确,输出1.0 Double d3=1;//错误;正确的是1D,输出1.0 double d4=666.00;//正确,输出666.0 float f1=4.0;//错误;正确的是4.0F,输出4.0 float f2=4;//正确,输出4.0 Float f3=4;//错误;正确的是4F,输出4.0 Float f4=4.0;//错误;正确的是4.0F,输出4.0 int i=(int)true;//错误,boolean不可以和其他类型转换 long l1=012;//正确 0前导表示八进制,0x表示十六进制 long l2=12;//正确 Long l3=12;//错误;正确的是12L Long l4=012;//错误;正确的是012L
13.集合类的线程安全
14.构造方法
- 构造方法没有返回值,并不是void;
- 构造方法优先级最低;
- 构造函数不能被继承,构造方法只能被显式或隐式的调用。
15.初始化代码执行顺序
父类静态变量→父类静态代码块→子类静态变量→子类静态代码块→
父类普通变量→父类普通代码块→父类构造方法→
子类普通变量→子类普通代码块→子类构造方法
严格的说,静态变量+静态代码块=静态域,不包括静态方法,谁先谁后根据在类中的位置,由于一般都是先声明变量的,所以有这样的总结。这里有一个难以想象的题目,十有八九会被坑,详看代码及注释,节省篇幅。
public class B { public static B t1 = new B(); public static B t2 = new B(); { System.out.println("构造块"); } static { System.out.println("静态块"); } public static void main(String[] args) { B t = new B(); } } /** 输出: 构造块 构造块 静态块 构造块 -------------------------------------------------- 解释: 1.加载B.class 2.按照顺序先对t1和t2进行初始化,默认为null 3.又需要对t1和t2进行显示初始化,所以需要加载默认的构造方法和普通代码块 4.为什么不会加载静态代码块呢? 5.因为刚开始加载B.class时就算是加载了静态域,现在还没加载完,又不能重复加载,所以没有在初始化t1的时候输出静态块 6.t1和t2加载完了按顺序就轮到静态代码块了 7.走main方法,又创建一次B对象,执行构造方法和普通代码块 要点:静态域只加载一次,不能重复加载 */
16.内部类
普通内部类
- private:本类
- default:本类、本包
- protected:本类、本包、子类(哪怕子类外包)
- public:本类、本包、子类、外包
静态内部类才可以声明静态方法,静态方法不能使用非静态变量。
17.泛型
提高数据安全性,不提高性能。(又方便又能提高性能简直没天理了,有得必有失。)
泛型是语法糖,不会影响JVM生成的汇编代码,在编译阶段会有类型擦除,还原成没有泛型的代码,所以运行速度提高或者降低应该都是不对的,应该是编译速度慢一点而已。
18.重写
只要是被子类重写的方法,不被super调用都是调用子类方法
public class Main{ static int[] a=new int[5]; static int[] vis=new int[5]; static int ans=0; public static void main(String []args) { Base base=new Derived(); base.methodOne();//ABDC } } class Base { public void methodOne() { System.out.print("A"); methodTwo(); } public void methodTwo() { System.out.print("B"); } } class Derived extends Base { public void methodOne() { super.methodOne(); System.out.print("C"); } public void methodTwo() { super.methodTwo(); System.out.print("D"); } }
19.栈与堆
(1)整型常量池,byte、int、short、long都对[-128,127]之间的数创建了常量池,如果没有显示调用new方法,默认不开辟新的堆内存,统一指向堆中常量池的地址。(至于不同类型同一个数字是不是同一个常量池就不清楚了,hashcode直接返回值看不出来,==则说不同类型无法比较)
(2)拆箱,任何比较中有基本数据类型例如i2,全部自动拆箱比较数值,i1至i6与i2比较都返回true
20.ASCII码
- 标准ASCII只使用7个bit,扩展的ASCII使用8个bit;
- ASCII码包含一些特殊空字符,所以不全都是可以印字符,例如' ';
21.取模和取余
取模运算(“Modulus Operation”)和取余运算(“Remainder Operation ”)两个概念有重叠的部分但又不完全一致。主要的区别在于对负整数进行除法运算时操作不同。取模主要是用于计算机术语中,取余则更多是数学概念。
刷算法题也好、业务也好,用的都是小学数学教的取余运算。x%y中,x叫做被除数,y叫做除数,x%y的结果,符号与被除数x相同,数值部分按照两个都是正数来算。
int x,y; x=12;y=5;//x%y=2 x=-12;y=-5;//x%y=-2 x=-12;y=5;//x%y=-2 x=12;y=-5;//x%y=2
22.字符串分割
String.split(String regex)
没有切割的匹配字符串时将原字符串放入字符串数组
String s="";//s.length()=0 String[] res=s.split(",");//res.length=1;res[0]=""; s="123"; res=s.split(",");//res.length=1;res[0]="123";
23.序列化
序列化保存的是类对象的状态,静态变量属于类的状态;因此,静态变量不会被序列化。
用关键字transient修饰的变量也不会被序列化。
24.接口interface
(1)接口方法默认public abstract,变量默认为public static final。
(2)方法和变量不可以使用private修饰;因为接口本身就是为了实现,用private就失去意义了。
(3)接口方法不可以用静态static修饰
25.主函数main
public static void main(String[] args){};严格一致才是主函数;
public void main(String[] args){};可以存在,不是主函数;
private static void main(String[] args){};可以存在,不是主函数;
26.关键字
尤其注意几个容易混淆的非关键字,true、false、null、sizeof
27.异常
- throw用于抛出异常;
- throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象;
编译时异常必须显示处理,就是IDEA看到的那些爆红,手动去改;运行时异常交给虚拟机,看不出来的,运行的时候去捕获、抛出;
运行时异常如果不处理,出现异常后就层层上抛:多线程由Thread.run()抛出,线程终止;单线程由main()抛出,主程序终止;
28.浮点数和数组比较
(!)浮点数比较
- 切记不要用==比较
- 一般认定一个阈值THRESHOLD,精度差小于某个阈值即可,Math.abs(a-b)<THRESHOLD
- BigDecimal类,用compareTo()方法比较;BigDecimal类的equals()方法会比较位数,导致1.0和1.00不一致
- 在运算过程中会产生精度差 double d1=0.1*3;//0.30000000000000004 double d2=0.3;//0.3
(2)数组比较
- 数组a.equals(数组b):比较地址,相当于==
- Arrays.equals(数组a,数组b):逐个比较元素
29.重载和重写
重载:同一个类、方法名相同、形参列表不同(个数不同 或者 类型不同);与修饰符、返回值无关;
重写:两同、两小、一大。方法名和参数列表相同;抛出异常类型和返回值类型<=父类;访问权限>=父类;
30.常规&偏门概念
(1)前台线程指非守护线程如main函数,后台线程指守护线程例如GC,JRE判断程序结束是否执行结束的标准是所有前台线程执行完毕
(2)一般关系数据模型和对象数据模型之间有以下对应关系:表对应类,记录对应对象,表的字段对应类的属性。没有上升到类的依赖层面
(3)Java程序中public修饰的类名必须与文件名一样
(4)jdk、jre、jvm、javac之间的关系
- jdk:java development kit,是Java开发工具包
- jre:java runtime environment,是指java运行环境。
- jvm:java virtual machine,就是我们常说的java虚拟机,它是整个java实现跨平台的最核心的部分,.class文件就是在这里运行
- javac:java编译器,读取Java源代码,将其编译为字节码文件.class文件
关系:jdk包含jre和javac,jre包含jvm。
(5)Java的屏幕坐标是以像素为单位,容器的左上角被确定为坐标的起点。
(6)一个Java源程序文件中定义几个类和接口,则编译该文件后生成几个以.class为后缀的字节码文件。
31.字符串拼接和替换问题
(1)字符串拼接
String test="javaandpython"; String str1="java"; String str2="and"; String str3="python"; String str4=str1+str2+str3; System. out. println(test=="java"+"and"+"python");//true System. out. println(test ==str1 + str2 + str3);//false System.out.println(test=="java"+"and"+str3);//false System.out.println(test==str4);//false
- 字面量拼接,编译器会进行优化“+”操作得到一个“javaandpython”的字符串,如果常量池没有这个字符串,就创建一个,有就直接用。这是在编译期间进行的;
- 引用量拼接,运算在Java运行期间执行的,会在堆中重新创建一个字符串对象,引用量的存在证明前面已经对引用量赋值了,常量池本就有str1、str2、str3的存在;因此,凡是有引用量进行操作的都在运行期间生成的对象,==比较会返回fasle;
(2)字符串替换
- 普通替换:String replace(char/String a, char/String b),字符换字符或者字符串换字符串;
- 正则表达式替换:String replaceAll(String regex, String replacement),第一个参数是正则表达式,例如"."表示全部字符替换成后者;
32.字节流和字符流
字符流是字节流根据编码集解析获得的,简言之,字符流=字节流+编码集
- 字节流:以Stream结尾
- 字符流:以Reader或者Writer结尾
33.中英文字符串的字节数和长度
英文每个字符占1字节、1长度。
中文每个字符占1长度,GBK编码占2字节,UTF-8占3字节,Java默认使用UTF-8。
34.Math的三个取整相关方法
- public static double floor(double a):floor意为地板,向下取整,返回不大于它的最大整数,注意正负并且返回的是浮点数!
- public static double ceil(double a):ceil意为天花板,向上取整,返回不小于它的最大整数,注意正负并且返回的是浮点数!
- public static long round(double a)或者public static int round(float a):round意为大约,表示“四舍五入”,注意正负并且返回的是整数;
35.移位运算
移位运算是补码在移位,不是原码,由于一般都是正数在计算,补码和原码一样,让人误以为是二进制原码在移位。
(1)左移<<,只有<<,没有<<<,因为都是右边补0,不需要管左边的符号位
(2)右移>>,左边正数补符号位0,负数补符号位1
(3)无符号右移>>>,左边补0
public static void main(String[] args) { int a; a=-5; /** -5 * 原码:10000000 00000000 00000000 00000101 * 反码:11111111 11111111 11111111 11111010 * 补码:11111111 11111111 11111111 11111011 */ System.out.println(a<<5);//-160 /** -160 * 原码:10000000 00000000 00000000 10100000 * 反码:11111111 11111111 11111111 01011111 * 补码:11111111 11111111 11111111 01100000 */ a=5; /** 5 * 原码:00000000 00000000 00000000 00000101 * 反码:00000000 00000000 00000000 00000101 * 补码:00000000 00000000 00000000 00000101 */ System.out.println(a<<2);//20 /** 20 * 原码:00000000 00000000 00000000 00010100 * 反码:00000000 00000000 00000000 00010100 * 补码:00000000 00000000 00000000 00010100 */ a=234567; /** 234567 * 原码:00000000 00000011 10010100 01000111 * 反码:00000000 00000011 10010100 01000111 * 补码:00000000 00000011 10010100 01000111 */ System.out.println(a>>2);//58641 System.out.println(a>>>2);//58641 /** 58641 * 原码:00000000 00000000 11100101 00010001 * 反码:00000000 00000000 11100101 00010001 * 补码:00000000 00000000 11100101 00010001 */ a=-123456; /** -123456 * 原码:10000000 00000001 11100010 01000000 * 反码:11111111 11111110 00011101 10111111 * 补码:11111111 11111110 00011101 11000000 */ System.out.println(a>>2);//-30864 /** -30864 * 原码:10000000 00000000 01111000 10010000 * 反码:11111111 11111111 10000111 01101111 * 补码:11111111 11111111 10000111 01110000 */ System.out.println(a>>>5);//134213870 /** 1073710960 * 原码:00000111 11111111 11110000 11101110 * 反码:00000111 11111111 11110000 11101110 * 补码:00000111 11111111 11110000 11101110 */ }
二、数据库
1.有一个成绩表tb_course,有姓名name、科目course、分数字段score,用一条SQL语句查询出所有单科成绩都大于80分的学生姓名
select name from tb_course group by name having (Min(score)>80);
2.事务隔离
- 读未提交:脏读,针对未提交的数据;
- 读已提交:不可重复读,针对已提交的数据项;
- 可重复读(MySQL默认隔离):幻读,针对已提交的一批数据;
- 序列化:不会产生读取问题
3.MySQL中的关键字不可以用作字段名,加上反单引号`可以使关键字作为字段名,选择题中默认不可以
4.组合索引
例如index( name,age )作为组合索引,左边第一个字段(name)需要作为条件才可以在查询中使用索引
- where name like 'shoulinniao' [ and age=22 ] 使用索引,explain执行计划的type为range级别
- where name = 'shoulinniao' [ and age=22 ] 使用索引,explain执行计划的type为ref级别
- where age = 22 不使用索引,explain执行计划的type为ALL级别
5.Oracle数据文件和表空间
数据文件由表空间进行组织管理,表空间是数据文件的逻辑集合。
一个表空间至少包含一个数据文件,一个数据文件只能属于一个表空间。
6.主键索引可以是联合索引
三、经典问题
1.虚数概念
i = i; i2 = -1; i3 = i2 * i = -i; i4 = i2 * i2 = (-1)*(-1)=1;
2.生日悖论
只要23个人,至少两人生日是同一天的概率高达50%
3.三门问题
这是一个游戏,有三个门,一个有礼物,两个空的;
参与者先选定一个门但是不打开;
主持人随机打开剩下的两个门的其中一个,发现是空的;
主持人问参与者要不要修改刚才选择的门?
不改中奖率是1/3,改了中奖率是2/3。
4.红帽子和黑帽子
舞会上人均一个帽子,非红即黑,全场至少有1顶黑帽子,每个人只能看别人的帽子,不能看到自己的帽子,不能交流,关灯时认为自己是黑帽子就打自己一巴掌。
- 假设只有一顶黑帽子,那么他看到的都是红帽子,则自己就是黑帽子,第一次关灯就打自己;
- 假设只有两顶黑帽子,那么他们都看到外面有一顶黑帽子,第一次关灯都在等那个黑帽子自己掌嘴,结果对面也在等自己掌嘴,说明全场不止一顶黑帽子,但是看到的只有一顶黑帽子,那自己就是黑帽子,第二次关灯两个人都掌嘴;
三次至n次同理,需要第3次至第n次关灯才有人掌嘴;
5.银行家算法
以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。有以下规定:
- 客户的最大需求资金不超过银行的现有资金时,可以接纳该客户;
- 客户可以分期贷款,相当于进程后续需要再申请资源,但是贷款总额度不应该超过最大需求资金;
- 如果当前银行不能满足某个客户,则让这个客户等待,但是会在有限时间里(避免死锁)接纳该客户;
- 客户获取资金后,能在有限时间内归还资金;
有两个常见结论:安全状态一定不会导致死锁;不安全状态不一定会导致死锁。
6.哲学家用餐问题(互斥访问有限资源的竞争问题)
五位哲学家围起来,任意两个哲学家之间有一根筷子,如何避免死锁?
- 至多4个哲学家同时拿起左筷子,这能保证至少有一个哲学家正常用餐,最后释放两只筷子给其他人使用
- 规定奇数号哲学家先拿左筷子再拿右筷子,偶数则相反
- 当哲学家左右两根筷子都可以用时,才拿起它们
- 拿起一只筷子后无法获取另一根筷子时,放下筷子,随机等待一段时间后再尝试获取
7.读者-写者问题
四、算法与数据结构
2.关键路径是起点到终点之间最长路径长度的路径
3.环形队列大小 size = (rear + n - front)%n;
五、JVM
1.jvm指令,混个脸熟
- jps:查看本机Java进程信息;
- jstack:打印线程的栈信息,制作线程dump文件
- jmap:打印内存映射,制作堆dump文件
- jstat:性能监控工具
- jhat:内存分析工具
- jconsole:简易的可视化控制台
- jvisualvm:功能强大的控制台
2.JVM内存五大区域
PC寄存器就是程序计数器,方法区和堆是线程共享的,其他属于线程私有。
3.堆外内存off-heap
不受JVM管控,直接受操作系统管控。
将对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上一样,但它仍然在RAM(主存)中。对象在这种状态下不能直接使用,它们必须首先反序列化,也不受垃圾收集。序列化和反序列化将会影响部分性能(所以可以考虑使用FST-serialization)使用堆外内存能够降低GC导致的暂停。堆外内存不受垃圾收集器管理,也不属于老年代,新生代。
六、计算机网络
1.各层协议
2.状态码
200:请求成功
403:服务器理解请求客户端的请求,但是拒绝执行此请求
404:服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500:服务器内部错误,无法完成请求
3.单播、多播、广播、任播
单播对象是针对某一台机,广播对象是与本机的相连的所有机,多播对象是限定某组里多台机,任播是限定某组里一台机。
七、操作系统
1.缓存淘汰算法
2.页面置换算法
3.段页式存储
4.虚拟内存容量只受硬盘剩余空间的限制
5.计算机所能处理的最小的数据项是位
6.孤儿进程
孤儿进程指的是在其父进程执行完成或被终止后仍继续运行的一类进程。这些孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。
7.僵尸进程zimbie
子进程比父进程先退出,但是父进程没有调用wait/waitpid回收子进程的资源,则子进程变成僵尸进程,等父亲收尸。
(1)造成什么影响?
子进程一直处于z状态,需要进程控制块PCB维护,浪费资源,导致内存泄漏。
(2)如何避免?
改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后, 会发送SIGCHLD信号给父进程,父进程收到此信号后,执行 waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略, 如果想响应这个消息,可以设置一个处理函数。
把父进程杀死,让僵尸进程成为孤儿进程。
8.操作系统的调度
一般说来,作业从进人系统到最后完成,可能要经历三级调度:高级调度、中级调度和低级调度。
高级调度:作业调度
中级调度:为了使内存中同时存放的进程数目不至于太多,有时需要把某些进程从内存中移到外存中,以减少多道程序的数目。特别是在采用虚拟存储技术的系统中或分时系统中。引入中级调度的主要目的是为了提高内存的利用率和系统吞吐量。它实际上就是存储器中的对换功能。
低级调度:进程调度
生产调度:生产指挥者对当日生产过程的具体安排和实时的调整,对生产过程发出的是指令性的命令。
十、设计模式
1.设计模式六大原则
- 开闭原则:对扩展开放、对修改关闭。要扩展时不改动原来的代码。这是最基础的设计原则,其他五个原则都是开闭原则的具体形态。
- 单一职责原则:一个类只负责一个功能领域中的相应职责,不能承载太多功能,导致被复用的可能性变小。
- 里氏替换原则:所有引用父类的地方必须能透明地使用其子类对象,即子类的所有方法必须在父类中声明,尽量把父类设计为抽象类或接口。
- 依赖倒置原则:细节依赖抽象,核心思想是面向接口编程。每个类尽量提供接口或抽象类,任何类不应从具体类派生。
- 接口隔离原则:接口粒度合适,只服务一个模块业务,不要把太多方法堆在一个接口里。单一职责原则注重的是职责、约束类,接口隔离原则注重接口隔离、针对抽象和整体框架的构建。
- 迪米特法则:只和朋友对话,不要和陌生人说话。如果真的有需要,通过中间人传递信息。朋友指的是和当前对象有关联的。
2.JDBC使用了桥接模式,提供两套接口,一个面向数据库厂商,一个面向JDBC使用者
随缘刷题,持续更新修正!