2021软件开发实习面试题记录
1.进程间通信的方式
管道通信:半双工,数据只能单向流动,只能在具有亲缘关系的进程间使用
共享内存区:共享内存由一个进程创建,但多个进程都可以访问
信号量通信:信号量是一个计数器,可以用来控制多个进程对共享资源的访问
消息队列:消息的链表
套接字:它可以让不在同一台计算机上但通过网络连接计算机上的进程进行通信
2.线程间通信的方式
互斥锁:互斥量可以保护共享数据的修改,如果线程正在等待共享数据的某个条件出现,仅用互斥量的话就需要反复对互斥对象锁定解锁,以检查值的变化,这样将频繁查询的效率非常低。
mutex
lock_guard (在构造函数里加锁,在析构函数里解锁)
unique_lock 自动加锁、解锁
atomic 基本类型的原子操作
条件变量:条件变量可以让等待共享数据条件的线程进入休眠,并在条件达成时唤醒等待线程,提供一种更高效的线程同步方式。条件变量一般和互斥锁同时使用,提供一种更高效的线程同步方式。
condition_variable
信号量
3.计算机网络模型
OSI模型:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
TCP/IP模型:网络接口层,互连网络层,传输层,应用层
OSI是制定的适用于全世界计算机网络的统一标准,是一种理想状态。TCP/IP是独立于特定的计算机硬件和操作系统,可移植性好,具有普适性
4.Java实现多线程的方式与线程池的种类
(1)创建线程的几种方法和优缺点
①通过继承Thread
单继承,继承之后无法继承别的类,需要为每个线程单独创建一个class文件,效率低
- 创建一个继承于Thread类的子类
- 重写Thread类中的run():将此线程要执行的操作声明在run()
- 创建Thread的子类的对象
- 调用此对象的start():①启动线程 ②调用当前线程的run()方法
②通过实现runnable接口
public interface Runnable { public abstract void run(); }
只用实现runnable,不影响正常代码。可以通过匿名内部类的方式实现,比较简单
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
- 创建Runnable接口实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用Thread类中的start():① 启动线程 ② 调用线程的run() --->调用Runnable接口实现类的run()
③通过callable方式
public interface Callable<V> { V call() throws Exception; }
- 实现的call()方法相比run()方法,主要的不同之处是call()方法是有返回值的
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
④开启线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
(2)线程池的种类
①可缓存线程池(NewCachedThreadPool):
可按需创建新线程,也可重用以前构造的线程,如果没有可重用的,就创建一个新线程。同时移除长期不用的线程。
②指定工作线程的线程池(NewFixedThreadPool):
可重用固定n个线程数的线程池。任意时刻大多数线程会处于处理任务的活跃状态。如果n个线程都满了时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果任何线程在执行期间意外终止,那么一个新线程将代替它执行后续的任务。只有显示的关闭才能关闭线程。
③定时的线程池(NewScheduledThreadPool):
创建一个线程池,它可安排在延迟时间后运行命令或者定期地执行
④单线程线程池(NewSingleThreadExecutor):
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程) ,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
5.define 和 const 的区别
const定义常量是有数据类型的,而#define宏定义常量却没有。编译器可以对const进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意料不到的错误
define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用
define占用代码段空间,const占用数据段空间
6.程序的内存分为几个区
堆、栈、自由存储区、全局/静态存储区和常量存储区
栈:里面的变量通常是局部变量、函数参数等,就是由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。
堆:由new分配的内存块,他们的释放由应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:由malloc等分配的内存块,和堆是十分相似的,用free来结束自己的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,他们共同占用同一块内存区。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改
7.面向对象
封装:外部通过接口来访问封装内部
继承:派生类继承基类,实现代码的复用性,减少代码
多态:对不同的数据类型采用不同的处理方法
8.New和Delete,Malloc和Free
相同点:都可用于动态内存分配与释放
malloc/free 是c语言中的库函数,需要头文件支持;而new/delete 是c++中的关键字
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,所以无法执行构造函数和析构函数
new自动计算需要分配的空间,而malloc需要手工计算计算字节数
new是类型安全的,而malloc不是
9.进程和线程的区别以及状态
(1)区别
进程是资源分配的基本单位
线程是资源调度的基本单位
(2)共包括以下 5 种状态:
1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
- (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
10.C++空类默认产生哪些成员函数
(1)默认构造函数
可以被重载,不能被重写
(2)析构函数
(3)拷贝构造函数
(4)赋值函数
11.class和struct的区别
class中变量默认是private,struct中的变量默认是public
class继承默认是private继承,而struct继承默认是public继承
12.如何定义内联函数
类内:可以不用在函数头部加inline关键字,因为编译器会自动将类内定义的函数声明为内联函数
类外:加上inline关键字
inline void temp()
内联函数由编译器实现
13.cin是一个流对象
14.虚函数
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
class A{ public: virtual void foo(){ cout<<"A::foo() is called"<<endl; } }; class B:public A{ public: void foo(){ cout<<"B::foo() is called"<<endl; } }; int main(){ A *a = new B(); a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的! return 0; }
一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。 虚函数只能借助于指针或者引用来达到多态的效果。
15.八种基本数据类型
数据类型 | 关键字 | 在内存中占用字节数 | 取值范围 | 默认值 |
布尔型 | boolean | 1 | true/false | false |
字节型 | byte | 1 | -128~127 | 0 |
短整型 | short | 2 | -2^15~2^15-1 | 0 |
整型 | int | 4 | -2^31~2^31-1 | 0 |
长整型 | long | 8 | -2^63~2^63-1 | 0 |
字符型 | char | 2 | 0~2^16-1 | '\u0000' |
单精度浮点型 | float | 4 | 1.4013E-45~3.4028E+38 | 0.0F |
双精度浮点型 | double | 8 | 4.9E-324~1.7977E+308 | 0.0D |
装箱和拆箱
自动装箱时Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如把int转化为Integer,double转化成Double等,繁殖时自动拆箱
原始类型:boolean,char,byte,short,int,long,float,double
封装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
String类型不是基本数据类型
16.选取成绩前10的所有学生信息
select top 10 * from 表[Student] order by score desc
17.static全局静态变量
全局变量前加上关键字static,全局变量就定义成一个全局静态变量
初始化:未经初始化的全局静态变量会被自动初始化为0
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾
18.MySQL的like是否使用索引
在使用like的时候,如果使用‘%%’,会不会用到索引呢?
EXPLAIN SELECT * FROM `user` WHERE username LIKE '%ptd_%';
EXPLAIN SELECT * FROM `user` WHERE username LIKE 'ptd_%';
EXPLAIN SELECT * FROM `user` WHERE username LIKE '%ptd_';
19.HashMap死锁情况
多线程put后导致get死循环
可能会启动多个线程,这个时候
Thread t = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), i); } System.out.println("t3 over"); } };
产生这个死循环的根源在于对一个未保护的共享变量 — 一个"HashMap"数据结构的操作。当在所有操作的方法上加了"synchronized"后,一切恢复了正常
多线程put可能导致元素丢失:主要问题出在addEntry方法的new Entry (hash, key, value, e),如果两个线程都同时取得了e,则他们下一个元素都是e,然后赋值给table元素的时候有一个成功有一个丢失
多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。
当另外一个线程get 这个Entry List 死循环的key的时候,这个get也会一直执行。最后结果是越来越多的线程死循环,最后导致服务器dang掉。我们一般认为HashMap重复插入某个值的时候,会覆盖之前的值,这个没错。但是对于多线程访问的时候,由于其内部实现机制(在多线程环境且未作同步的情况下,对同一个HashMap做put操作可能导致两个或以上线程同时做rehash动作,就可能导致循环键表出现,一旦出现线程将无法终止,持续占用CPU,导致CPU使用率居高不下),就可能出现安全问题了。
20.常用中文字符用utf-8编码占用3个字节
21.Redis常见的5种不同的数据类型
基本用法:https://www.cnblogs.com/dddyyy/p/9803828.html
22.Java中能否在switch case中用String类型
能,1.7以后支持
23.Java内存模型
JMM并不真实存在,它描述了一组规则或规范,用于实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的
Java内存模型规定所有的实例变量,静态变量都存储在主内存(堆加方法区)中
每个线程都有自己的工作内存,里面保存了用到的变量和主内存的拷贝,叫做工作内存,线程对变量进行操作都在这个拷贝中操作,而不能直接读写主内存中的变量
每个线程的工作内存都是独立的,线程操作数据只能在工作内存(虚拟机栈)中进行,然后刷回到主存(堆加方法区)。这是 Java 内存模型定义的线程基本工作方式
整个Java内存模型实际上是围绕着三个特征建立起来的。
也就是多线程的三个特性,分别是:原子性,可见性,有序性
①原子性:指的是一个操作是不可分割,不可中断的,一个线程在执行时不会被其他线程干扰,JMM只能保证基本的原子性,如果要保证一个代码块的原子性,提供了monitorenter 和 moniterexit 两个字节码指令,也就是 synchronized 关键字。因此在 synchronized 块之间的操作都是原子性的。
②可见性:指当一个线程修改共享变量的值,其他线程能够立即知道被修改了,Java是利用volatile关键字来提供可见性的。当变量被volatile修饰时,这个变量被修改后会立刻刷新到主内存,当其它线程需要读取该变量时,会去主内存中读取新值。而普通变量则不能保证这一点。除了volatile关键字之外,final和synchronized也能实现可见性。synchronized的原理是,在执行完,进入unlock之前,必须将共享变量同步到主内存中。final修饰的字段,一旦初始化完成,如果没有对象逸出(指对象为初始化完成就可以被别的线程使用),那么对于其他线程都是可见的。
③有序性:在Java中,可以使用synchronized或者volatile保证多线程之间操作的有序性。实现原理有些区别。volatile关键字是使用内存屏障达到禁止指令重排序,以保证有序性。synchronized的原理是,一个线程lock之后,必须unlock后,其他线程才可以重新lock,使得被synchronized包住的代码块在多线程之间是串行执行的。
参考资料:https://zhuanlan.zhihu.com/p/258393139?utm_source=cn.wiz.note
24.获取class对象
(1)通过对象静态属性 .class来获取对应的Class对象
Class a = 类名.class;
(2)Object类中的getClass()方法
Class b = 类的实例对象.getClass();
(3)只要通过给定类的字符串名称就可以获取该类,更为拓展,forName
try { Class c = Class.forName("类的全类名"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
25.如何优化查询
①使用索引,在频繁排序或分组的列上创建索引,在频繁连接但未指定为外键的列上创建索引
②应简化或避免对大表进行重复排序。 当索引可用于以正确的顺序自动产生输出时,优化程序将避免排序步骤。
③消除对大表行数据的顺序访问。 使用联合避免顺序访问。 如果要连接两个表,则需要在连接字段上建立索引
④避免相关子查询
⑤避免困难的正则表达式
⑥使用临时表加快查询速度
⑦使用排序而不是非顺序访问
26.Java中string和stringbuffer和stringbuilder的异同
相同:都是final不允许被继承
string buffer是线程安全的,同步的,可以应用于多线程
string builder是不安全的,不同步,不能用于多线程
27.HashMap和HashTable的区别,并介绍currentHashMap
(1)HashMap
内部基于哈希表,底层数组+链表实现,存储键值对,可以存储null键和null值,线程不安全
HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。
在多线程环境中使用HashMap的put方法有可能导致程序死循环,因为多线程可能会导致HashMap形成环形链表,即链表的一个节点的next节点永不为null,就会产生死循环。这时,CPU的利用率接近100%,所以并发情况下不能使用HashMap
HashMap默认创建一个大小为16的entry数组,当HashMap里存的entry个数达到12,就会发生扩容。加载因子为默认的0.75,如果加载因子越大,对空间的利用更充分,但是查找效率会降低
每次扩充,容量变为原来的2倍。扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入。HashMap的计算效率高,但是hash冲突却也增加了。
HashMap插入重复键值会怎样:java中 util.map中 是不允许直接插入重复键值对的,值可以重复,但是键一旦重复就会发生覆盖的现象
(2)HashTable
内部基于哈希表,存储键值对,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足时,同样会自动增长
底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低
是同步的,hashtable所有的方法都加了标记锁,适用于多线程。被synchronized关键字修饰,因此是线程安全。
Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。hashtable运算需要进行除法,比较耗时。
(3)CurrentHashMap
底层采用分段的数组+链表实现。线程安全
链表在数据过多时转为红黑树,链表上的元素超过7时就会转成红黑树(平衡二叉树存储)
JDK1.8版本的currentHashMap
采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作
28.抽象类和接口的区别,接口可以定义变量吗
抽象类:抽象类就是为了继承而存在的,包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法
//抽象方法 abstract void fun(); //抽象类:包含抽象方法的类,但是如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类 [public] abstract class ClassName { abstract void fun(); }
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
在其他方面,抽象类和普通的类并没有区别
接口:英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数
[public] interface InterfaceName { }
接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量,而方法会被隐式地指定为public abstract方法且只能是public abstract方法,并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。接口可以定义变量,但是必须要是public static final类型,可以说是一个常量
要让一个类遵循某组特地的接口需要使用implements关键字
class ClassName implements Interface1,Interface2,[....]{ }
可以看出,允许一个类遵循多个特定的接口。如果一个非抽象类遵循了某个接口,就必须实现该接口中的所有方法。对于遵循某个接口的抽象类,可以不实现该接口中的抽象方法。
接口是公开的,里面不能有私有的方法或变量,可以实现多重继承,实现接口的一定要实现接口里定义的所有方法
二者语法区别:
(1).抽象类可以有构造方法,接口中不能有构造方法。
(2).抽象类中可以有普通成员变量,接口中没有普通成员变量
(3).抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
(4). 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
(5). 抽象类中可以包含静态方法,接口中不能包含静态方法
(6). 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
(7). 一个类可以实现多个接口,但只能继承一个抽象类。
二者应用上的区别:
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约
抽象类在代码实现方面发挥作用,可以实现代码的重用
参考文献:https://www.cnblogs.com/dolphin0520/p/3811437.html
29.索引失效的情况有哪些
(1)列与列对比
某个表中,有两列(id和c_id)都建了单独索引
select * from test where id=c_id;
这种情况会被认为还不如走全表扫描
(2)存在NULL值条件
如果索引列是可空的,是不会给其建索引的,索引值是少于表的count(*)值的,所以这种情况下,执行计划自然就去扫描全表了。
(3)NOT值
当查询条件为非时,索引定位就困难了,执行计划此时可能更倾向于全表扫描
(4)Like通配符
当使用模糊搜索时,尽量采用后置的通配符,例如:name||’%’,因为走索引时,其会从前去匹配索引列,这时候是可以找到的,如果采用前匹配,那么查索引就会很麻烦
(5)使用函数
查询条件上尽量不要对索引列使用函数
(6)复合索引前导列分区大
复合索引前导列区分小的时候,我们有INDEX SKIP SCAN,当前导列区分度大,且查后导列的时候,前导列的分裂会非常耗资源
(7)数据类型的转换
当查询条件存在隐式转换时,索引会失效
30.MySQL数据库优化的方式
(1)选取最适用的字段属性
在创建表的时候,为了获得更好的性能,我们可以将表中字段的大小设得尽可能小
应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL值
对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为ENUM类型。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能
(2)使用连接来代替相关子查询
使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)
子查询
SELECT * FROM customerinfo WHERE CustomerIDNOT in ( SELECT CustomerID FROM salesinfo )
连接
SELECT * FROM customerinfo LEFT JOIN salesinfo ON customerinfo.CustomerID=salesinfo.CustomerID WHERE salesinfo.CustomerID IS NULL
连接(JOIN)..之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作
(3)使用联合(UNION)来代替手动创建的临时表
它可以把需要使用临时表的两条或更多的select查询合并的一个查询中,在客户端的查询会话结束的时候,临时表会被自动删除,使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了
SELECT Name,Phone FROM client UNION SELECT Name,BirthDate FROM author UNION SELECT Name,Supplier FROM product
(4)事务
设想一下,要把某个数据同时插入两个相关联的表中,可能会出现这样的情况:第一个表中成功更新后,数据库突然出现意外状况,造成第二个表中的操作没有完成,这样,就会造成数据的不完整,甚至会破坏数据库中的数据。要避免这种情况,就应该使用事务,它的作用是:要么语句块中每条语句都操作成功,要么都失败。换句话说,就是可以保持数据库中数据的一致性和完整性
事物以BEGIN关键字开始,COMMIT关键字结束。在这之间的一条SQL操作失败,那么,ROLLBACK命令就可以把数据库恢复到BEGIN开始之前的状态
BEGIN; INSERT INTO salesinfo SET CustomerID=14; UPDATE inventory SET Quantity=11 WHERE item='book'; COMMIT;
事务的另一个重要作用是当多个用户同时使用相同的数据源时,它可以利用锁定数据库的方法来为用户提供一种安全的访问方式,这样可以保证用户的操作不被其它的用户所干扰
(5)锁定表
尽管事务是维护数据库完整性的一个非常好的方法,但却因为它的独占性,有时会影响数据库的性能
假设有很多用户同时访问一个数据库系统,例如访问一个电子商务网站,就会产生比较严重的响应延迟
锁表的发生环境 :
第一、 A程序执行了对 tableA 的 insert ,并还未 commite时,B程序也对tableA 进行insert 则此时会发生资源正忙的异常 就是锁表
第二、锁表常发生于并发而不是并行(并行时,一个线程操作数据库时,另一个线程是不能操作数据库的,cpu 和i/o 分配原则)
有些情况下我们可以通过锁定表的方法来获得更好的性能
LOCK TABLE inventory WRITE SELECT Quantity FROM inventory WHERE Item='book'; …… UPDATE inventory SET Quantity=11 WHERE Item='book'; UNLOCK TABLES
(6)使用外键
锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键
外键可以保证每一条销售记录都指向某一个存在的客户,在这里,外键可以把customerinfo表中的CustomerID映射到salesinfo表中CustomerID,任何一条没有合法CustomerID的记录都不会被更新或插入到salesinfo中
CREATE TABLE customerinfo( CustomerID INT NOT NULL, PRIMARY KEY(CustomerID) ) TYPE=INNODB; CREATE TABLE salesinfo( SalesID INT NOT NULL, CustomerID INT NOT NULL, PRIMARY KEY(CustomerID,SalesID), FOREIGN KEY(CustomerID) REFERENCES customerinfo(CustomerID) ON DELETE CASCADE ) TYPE=INNODB;
注意例子中的参数“ONDELETECASCADE”。该参数保证当customerinfo表中的一条客户记录被删除的时候,salesinfo表中所有与该客户相关的记录也会被自动删除。如果要在MySQL中使用外键,一定要记住在创建表的时候将表的类型定义为事务安全表InnoDB类型。该类型不是MySQL表的默认类型。定义的方法是在CREATETABLE语句中加上TYPE=INNODB
(7)建立索引
索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显
一般说来,索引应建立在那些将用于JOIN,WHERE判断和ORDERBY排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引
(8)优化查询语句
绝大多数情况下,使用索引可以提高查询的速度,但如果SQL语句使用不恰当的话,索引将无法发挥它应有的作用
首先,最好是在相同类型的字段间进行比较的操作
其次,在建有索引的字段上尽量不要使用函数进行操作
在搜索字符型字段时,我们有时会使用LIKE关键字和通配符,这种做法虽然简单,但却也是以牺牲系统性能为代价的
应该注意避免在查询中让MySQL进行自动类型转换,因为转换过程也会使索引变得不起作用
31.SQL调优
Ⅰ.select检查
(1)SQL规范性检查
SQL语句的select后面使用了自定义函数UDF,SQL返回多少行,那么UDF函数就会被调用多少次,这是非常影响性能的
(2)text类型谨慎使用
如果select出现text类型的字段,就会消耗大量的网络和IO带宽
(3)gorup_concat谨慎使用
是一个字符串聚合函数,会影响SQL的响应时间
(4)内联子查询
在select后面有子查询的情况称为内联子查询,SQL返回多少行,子查询就需要执行过多少次,严重影响SQL性能
select id,(select userName from userInfo) as name from info
Ⅱ.from检查
(1)表的连接方式
在MySQL中不建议使用Left Join,即使ON过滤条件列索引,一些情况也不会走索引,导致大量的数据行被扫描,SQL性能变得很差,同时要清楚ON和Where的区别。
SELECT column_name(s) FROM table1 LEFT JOIN table2 ON table1.column_name=table2.column_name;
(2)子查询
由于MySQL的基于成本的优化器CBO对子查询的处理能力比较弱,不建议使用子查询,可以改写成Inner Join
(3)索引列被运算
当一个字段被索引,同时出现where条件后面,是不能进行任何运算,会导致索引失效
Ⅲ.group by检查
前缀索引
group by后面的列有索引,索引可以消除排序带来的CPU开销,如果是前缀索引,是不能消除排序的
详细内容:大厂是怎么进行SQL调优的? - 敖丙 - 博客园 (cnblogs.com)
32.数据清洗
数据清洗主要是删除原始数据集中的无关数据、重复数据,过滤与挖掘主题无关的数据,处理缺失值和异常值
(1)缺失值的处理:
equals是值的对比
==是地址的对比
36.MySQL中是否区分大小写
MySQL 在Windows下不区分大小写,但在 Linux下默认是区分大小写
37.数据库中update如何使用
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
38.mybatis对比jdbc的好处
(1).数据库连接池(减少数据库关闭开启时数据库的资源浪费)
(2).SQL配置文件(减少硬编码)
(3).动态SQL语句(只专注写sql,各干各的事,专一)
(4).映射(resultset直接转为java对象)
39.出现死锁的几种情况
互斥条件
不可剥夺条件
请求和保持
循环等待
40.线程同步机制
问题起因:多个线程并发访问共享变量、共享资源
锁:一个共享变量或者资源只能被一个线程访问,访问结束后其他线程才能访问。保障原子性、可见性和有序性
volatile关键字
final关键字
static关键字
41.current包介绍
42.http和https
HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全
43.客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。
44.软拷贝和硬拷贝是什么,有什么区别
软拷贝:复制对象的值
硬拷贝:复制这个对象所有的东西,如果该对象引用了其他对象,则引用也会改变到复制的新对象那边
45.hashcode和equals的区别
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。哈希码的作用是确定该对象在哈希表中的索引位置,Java中的任何类都包含有hashCode() 函数
equals它的作用也是判断两个对象是否相等,Java中的任何类都包含有equals()函数。如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。
相等的对象equals必须具有相等的hashCode,相等的hashCode不一定equals。但是hashCode效率高,equals效率低,因此先比较hashCode如果相等在比较equals,这样效率高。
46.resultType和resultMap的区别
对象bai不同、描述不同、类型适用不同
对于单表查询的话用resultType是最合适,相应的pojo中必须有和它相同的字段对应,而resultType中的内容就是pojo在本项目中的位置
resultMap如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系
47.spring部分注解
@RequestMapping:在Spring MVC 中使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置
@ResponseBody:作用其实是将java对象转为json格式的数据
@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””):将请求参数绑定到你控制器的方法参数上
SpringMVC三层架构注解@Controller、@Service和@Repository
①@Controller
@Controller用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller对象,分发处理器会扫描使用该注解的类的方法,并检测该方法是否使用了@RequestMapping注解
@Controller只是定义了一个控制器类,而使用@RequestMapping注解的方法才是处理请求的处理器
@Controller表示在tomcat启动的时候,把这个类作为一个控制器加载到Spring的Bean工厂,如果不加,就是一个普通的类,和Spring没有关系
②@Service()
默认按照名称进行装配,如果名称可以通过name属性指定,如果没有name属性,注解写在字段上时,默认去字段名进行查找,如果注解写在setter方法上,默认按照方法属性名称进行装配。当找不到匹配的bean时,才按照类型进行装配,如果name名称一旦指定就会按照名称进行装配
③@Repository持久层
此注解式持久层组件,用于标注数据访问组件,即DAO组件
@Repository(value="userDao") public class UserDaoImpl extends BeansDaoImpl<User>{
@Repository(value=“userDao”)注解告诉Spring ,让Spring创建一个名字叫做"userDao"的UserDapImpl实例
48.深拷贝和浅拷贝的区别是什么?
深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。
浅拷贝是将对象的引用复制给另一个对象。因此,如果我们在副本中进行更改,则会影响原对象。
49.项目数据库如何设计
(1)建表命名规范
表名为单数
小数为decimal
表达是与否概念的字段,必须使用is_xxx的方式命名
选取合适的字符存储长度
(2)设计
单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表
均采用 utf8字符集
(3)使用规范
超过三个表禁止 join。需要 join的字段,数据类型保持绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
在 varchar字段上建立索引时,必须指定索引长度
页面搜索严禁左模糊或者全模
对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或 表名)进行限定
50.count(*)和count(列明)的区别
count(*)会统计值为NULL 的行,而 count(列名)不会统计此列为 NULL 值的
51.多态性
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,
而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。
若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)
52.重写和重载
重写override
子类中的方法和父类中的方法名和参数相同,通过子类创建的实例,会调用子类的方法名而不是父类的方法名,相当于把父类中定义的那个完全相同的方法给覆盖了
重载overload
相同的方法名,不同的参数列表,同名不同参
53.计算机网络中的七层网络协议和传输单位
名称 | 单位 | 作用 |
物理层 | 比特流 | 透明传输比特流,处理信号通过介质的传输 |
数据链路层 | 帧 | 点到点的通信,成帧,差错控制,流量控制,链路连接,帧定界帧同步保证数据正确和完整 |
网络层 | 数据报 | 传输数据报,为上层数据加上首部,分组交换,路由选择,流量控制,拥塞差错控制 |
传输层 | 报文段或用户数据报 | 流量控制,主机进程的通信 |
会话层 | 数据 | 不同主机间进程对话,可在文件中插入同步点防止中断重传 |
表示层 | 数据 | 格式转换压缩为上层数据加上首部 |
应用层 | 数据 | 用户和网络的接口,为上层数据加上首部 |
54.TCP/IP交互流程
(1)建立连接:三次握手
客户端向服务器发送一个数据包(SYN),请求建立连接
服务器收到SYN请求数据包后,对客户端进行确认
客户端收到确认包后,再对服务器进行确认
(2)数据传输
发送数据:客户端向服务器发送一个带有数据的数据包
确认收到:服务器收到该数据包,向客户端发送一个确认包
(3)断开连接:四次挥手
客户端完成数据传输后,主动向服务器发送一个终止包(FIN)
服务器收到终止数据包后,向客户端发送确认包进行确认
服务器完成数据传输后,向客户端发送一个终止包(FIN)
客户端收到终止数据包后,向服务器发送确认包进行确认
55.Linux常用命令
ls 遍历
mkdir 创建文件夹
cd 进入文件夹
rm-rf 文件删除
grep xxx 搜索出含有xxx的字符串
tar 命令是类Linux中比价常用的解压与压缩命令
kill 杀掉进程
56.一个网段IP地址多,如何提高测试效率
对于一个网段ip地址众多,如果单个检测实在麻烦,那么我们可以直接批量ping网段检测,那个ip地址出了问题,一目了然
for /L %D in (1,1,255) do ping (192.168.1.%D)你自己想查的地址
57.协程是什么
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
58.纯虚函数的意义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,有时基类本身生成对象是不合情理的,需要纯虚函数
virtual void funtion1()=0
59.Git常用命令
git checkout dev //切换分支
git clone //下载远程仓库
git commit //提交代码到本地仓库
git merge //合并分支
git push //将本地仓库修改后的代码提交到远程仓库
60.大顶堆和小顶堆
大顶堆就是每个元素都大于他的左右子树元素
小顶堆就是每个元素都小于他的左右子树元素
61.http请求和响应全过程
(1)建立TCP连接
先解析域名得到 IP 再找到主机进行3 次握手建立TCP连接
(2)web浏览器向服务器发送请求命令
例如get那哪个页面,同时发送的请求中还包括请求头user-agent等信息
然后发送空白行通知服务器请求结束
(3)web服务器应答
发出请求后,服务器会客户机进行应答
应答内容包括:协议的版本号和应答状态码 ,响应头信息来记录服务器自己的数据,被请求的文档内容。
最后发送一个空白行来表示头信息的发送到此为结束
接着以Content-Type响应头信息所描述的格式发送用户所请求的实际数据
(4)Web服务器关闭TCP链接
一般情况下,一旦 Web 服务器向浏览器发送了请求的数据,它就要关闭 TCP 连接,除非还发送Connection:keep-alive则保持连接
(5)浏览器接收数据
浏览器接受服务器应答回来的 html 代码和css,和js代码再进行页面的渲染或者接受到应答的文件进行保存等操作
总结:
浏览器发起请求-> 解析域名得到ip进行TCP连接 ->浏览器发送HTTP请求和头信息发送->服务器对浏览器进行应答,响应头信息和浏览器所需的内容-> 关闭TCP连接或保持-> 浏览器得到数据进行操作
62.正则表达式部分字段意思
^:从字符串开头进行匹配
$:从字符串末尾进行匹配
63.Linux内存页默认大小是4K
64.字符串相关函数
strcpy(字符数组1,字符串2):作用是将字符串2复制到字符数组1中去
memcpy函数:memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
strncpy(str1,str2,2):作用是将str2中最前面2个字符复制到str1中,取代str1中原有的最前面2个字符
65.对哈希冲突的理解
关键字和它在表中存储位置之间存在一种函数关系。这个函数我们称为为哈希函数。不同的输入可能会产生相同的散列值,即这种状况下他们对应的哈希数的值是相同的,因为值相同所以不可能由散列值确定唯一的输入值,这种情况就是哈希冲突.
键(key)经过hash函数得到的结果作为地址去存放当前的键值对(key-value)(hashmap的存值方式),但是却发现该地址已经有值了,就会产生冲突。这个冲突就是hash冲突了
有以下的方式可以解决哈希冲突:
- 开放定址法:线性探测再散列,二次(平方)探测再散列,伪随机探测再散列
- 再哈希法:这种方式是同时构造多个哈希函数,当产生冲突时,计算另一个哈希函数的值
- 链地址法:将所有哈希地址相同的都链接在同一个链表中 ,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。hashmap就是用此方法解决冲突的
- 建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
66.Mybatis如何防止SQL注入
mybatis是如何防止SQL注入的 - 王的微笑 - 博客园 (cnblogs.com)
67.int和long型数据在内存中怎么存储
volatile关键字的作用
(1)保证线程间变量的可见性。
volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的。普通变量则需要重新读取才能获得最新值
volatile保证可见性的流程大概就是这个一个过程:
volatile不一定能保证线程安全
可见性不能保证操作的原子性,例如count++不是原子性操作,会当做三步,首先读取count的值,然后+1,最后赋值回去count变量。
需要保证线程安全的话,需要使用synchronized关键字或者lock锁,给count++这段代码上锁
private static synchronized void add() { count++; }
(2)禁止CPU进行指令重排序。
首先要讲一下as-if-serial语义,不管怎么重排序,(单线程)程序的执行结果不能被改变。
为了使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率,只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
重排序的种类分为三种,分别是:编译器重排序,指令级并行的重排序,内存系统重排序。整个过程如下所示:
指令重排序在单线程是没有问题的,不会影响执行结果,而且还提高了性能。但是在多线程的环境下就不能保证一定不会影响执行结果了。所以在多线程环境下,就需要禁止指令重排序。
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
private static int a;//非volatile修饰变量 private static int b;//非volatile修饰变量 private static volatile int k;//volatile修饰变量 private void hello() { a = 1; //语句1 b = 2; //语句2 k = 3; //语句3 a = 4; //语句4 b = 5; //语句5 //以下省略... }
变量a,b是非volatile修饰的变量,k则使用volatile修饰。所以语句3不能放在语句1、2前,也不能放在语句4、5后。但是语句1、2的顺序是不能保证的,同理,语句4、5也不能保证顺序。并且,执行到语句3的时候,语句1,2是肯定执行完毕的,而且语句1,2的执行结果对于语句3,4,5是可见的
69.多线程安全
①synchronized
Synchronized关键字保证了数据读写一致和可见性等问题,但是他是一种阻塞的线程控制方法,在关键字使用期间,所有其他线程不能使用此变量,这就引出了一种叫做非阻塞同步的控制线程安全的需求;(同步机制采用了“以时间换空间”的方式)
②volatile
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 就跟C中的一样 禁止编译器进行优化
③ThreadLocal
ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。(ThreadLocal采用了“以空间换时间”的方式)
70.聚簇索引和非聚簇索引
①聚簇索引:
将数据存储与索引放到了一块,找到索引也就找到了数据,由于聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引。聚簇索引默认是主键,如果表中没有定义主键,InnoDB 会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB 会隐式定义一个主键来作为聚簇索引。聚簇索引性能最好而且具有唯一性,所以非常珍贵,必须慎重设置。一般要根据这个表最常用的SQL查询方式来进行选择,某个字段作为聚簇索引,或组合聚簇索引。
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。
优点:
数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快
聚簇索引对于主键的排序查找和范围查找速度非常快
缺点:
插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键
更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。
71.什么时候会发生full GC,majorGC和fullGC的区别
fullGC:通常意义上而言指的是一次特殊GC的行为描述,这次GC会回收整个堆的内存,包含老年代,新生代,metaspace等
majorGC:只收集老年代
for (int i = 0; i < arrayList.size(); i++) { System.out.println(arrayList.get(i)); }
for (String string : arrayList) { System.out.println(string); }
Iterator<String> iterator = arrayList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }
- 可以让程序员通过代码的方式决定某些对象的生命周期;
- 有利于JVM进行垃圾回收
①强引用
如果一个对象具有强引用,那垃圾回收器不会回收它。它是默认的引用类型,强引用的对象是可触及的,垃圾收集器就永远不会回收被引用的对象,它可能导致内存泄漏,可以直接访问目标对象。
②软引用
只要被软引用关联的对象,系统将要发生内存溢出前,会把这些对象列进回收的范围之中进行二次回收,如果还没有足够的内存,才会抛出内存溢出异常。软引用通常用来实现内存敏感的缓存,如高速缓存。
③弱引用
用来描述那些非必需的对象,被弱引用关联的对象只能生存到下一次垃圾回收发生为止。只要是弱引用,发现即回收。但是垃圾回收的线程通常优先级很低,因此,并不一定很快的发现,再这种情况下,弱引用对象可以存在很长时间。
④虚引用
它不能单独使用,也无法通过虚引用来获取被引用的对象,当试图通过虚引用的get()方法取得对象时,总是null。为一个对象设置虚引用关联的唯一目标在于跟踪垃圾回收过程。
74.什么是B+ 树和B树、红黑树?
B+树
B树
如图所示,区别有以下两点:
1. B+树中只有叶子节点会带有指向记录的指针(ROWID),而B树则所有节点都带有,在内部节点出现的索引项不会再出现在叶子节点中。
2. B+树中所有叶子节点都是通过指针连接在一起,而B树不会。
红黑树
(1)每个节点或要么黑色,要么红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。用于确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
75.MySQL数据库索引为什么选择B+树?
MySQL中的数据一般是放在磁盘中的,读取数据的时候肯定会有访问磁盘的操作,磁盘中有两个机械运动的部分,分别是盘片旋转和磁臂移动。盘片旋转就是我们市面上所提到的多少转每分钟,而磁盘移动则是在盘片旋转到指定位置以后,移动磁臂后开始进行数据的读写。那么这就存在一个定位到磁盘中的块的过程,而定位是磁盘的存取中花费时间比较大的一块,毕竟机械运动花费的时候要远远大于电子运动的时间。当大规模数据存储到磁盘中的时候,显然定位是一个非常花费时间的过程,但是我们可以通过B树进行优化,提高磁盘读取时定位的效率。
为什么B类树可以进行优化呢?我们可以根据B类树的特点,构造一个多阶的B类树,然后在尽量多的在结点上存储相关的信息,保证层数尽量的少,以便后面我们可以更快的找到信息,磁盘的I/O操作也少一些,而且B类树是平衡树,每个结点到叶子结点的高度都是相同,这也保证了每个查询是稳定的。
总的来说,B/B+树是为了磁盘或其它存储设备而设计的一种平衡多路查找树(相对于二叉,B树每个内节点有多个分支),与红黑树相比,在相同的的节点的情况下,一颗B/B+树的高度远远小于红黑树的高度(在下面B/B+树的性能分析中会提到)。B/B+树上操作的时间通常由存取磁盘的时间和CPU计算时间这两部分构成,而CPU的速度非常快,所以B树的操作效率取决于访问磁盘的次数,关键字总数相同的情况下B树的高度越小,磁盘I/O所花的时间越少。
B+树是应文件系统所需而产生的一种B树的变形树(文件的目录一级一级索引,只有最底层的叶子节点(文件)保存数据)非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中,这不就是文件系统文件的查找吗?
我们就举个文件查找的例子:有3个文件夹a、b、c, a包含b,b包含c,一个文件yang.c,a、b、c就是索引(存储在非叶子节点), a、b、c只是要找到的yang.c的key,而实际的数据yang.c存储在叶子节点上。所有的非叶子节点都可以看成索引部分!
76.数据库事务
BEGIN TRANSACTION //事务开始 SQL1 SQL2 COMMIT/ROLLBACK //事务提交或回滚
关于事务的定义有几点需要解释下:
- 1.数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。
- 2.构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行。
- 3.构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
- 4.以上即使在数据库出现故障以及并发事务存在的情况下依然成立。
事物的特性
原子性:事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败
一致性:事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态
隔离性:并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样
持久性:事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
77.重写equals方法需要重写hashcode方法吗?
当我们将equals方法重写后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定
https://zhuanlan.zhihu.com/p/50206657
78.Spring、SpringMVC和Springboot有什么区别
Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。
Spring MVC提供了一种轻度耦合的方式来开发web应用。它是Spring的一个模块,是一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。
Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)。
总结:
Spring 是一个“引擎”;
Spring MVC 是基于Spring的一个 MVC 框架;
Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。
79Java垃圾回收机制:
insert [into] <表名> (列名) values (列值) 例:insert into Strdents (姓名,性别,出生日期) values ('开心朋朋','男','1980/6/15')
删
delete from <表名> [where <删除条件>] 例:delete from a where name='开心朋朋'(删除表a中列值为开心朋朋的行)
改
update <表名> set <列名=更新值> [where <更新条件>] 例:update tongxunlu set 年龄=18 where 姓名='蓝色小名'
查
精确(条件)查询 select <列名> from <表名> [where <查询条件表达试>] [order by <排序的列名>[asc或desc]]
81.垃圾回收机制中生代的理解,引用计数法和可达性的分析的优缺点
JVM中的堆,一般分为三大部分:新生代、老年代、永久代
①新生代
主要用来存放新生的对象,一般占堆的1/3空间,由于频繁创建对象,所以会频繁进行MinorGC垃圾回收
新生代又分为三个区:
Eden区:Java新对象的出生地,当无法为新建对象分配空间的时候,MinorGC触发
SurvivorTo区:保留垃圾回收幸存者
SurvivorFrom区:上一次垃圾回收的幸存者且这一次垃圾回收的被扫描者
MinorGC的过程:
首先把Eden和SurvivorFrom区域中存活的对象复制到ServicorTo区域
其次把对象的年龄+1
清空Eden和SurvivorFrom区域的对象,吧SurvivorFrom和SurvivorTo区域对换,原SurvivorTo成为下一次GC的SurvivorFrom区
②老年代
老年代的对象比较稳定,不会频繁执行MinorGC。
每当新生代的对象进入老年代,空间不够用了才会触发垃圾回收。另外,当老年代的连续内存空间不够分配的时候也会触发MajorGC垃圾回收机制腾出空间
MajorGC采用标记清除算法
首先扫描全部老年代,标记出存活的对象
然后回收没有标记的对象
MajorGC回收的耗时比较长,因为要扫描再回收,会产生内存碎片
当老年代发生异常,会抛出Out of Memory异常
③永久代
已经被移除变为“元数据区”
元空间使用本地内存。元空间的大小只受本地空间的限制
82.spring如何完成自动配置
SpringBootApplication
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ……代码 }
SpringBoot启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作
83.@Autowired和@Resource注解的区别
共同点:两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
不同点:
(1)@Autowired
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
(2)@Resource
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略
84.MySQL如何分页
86.请解释 Spring Bean 的生命周期?
在一个 bean 实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。
同样的,当一个 bean 不在被调用时需要进行相关的析构操作,并从 bean 容 器中移除。
Spring bean factory 负责管理在 spring 容器中被创建的 bean 的生命周期。
Bean 的生命周期由两组回调(call back)方法组成:
①初始化之后调用的回调方法。
②销毁之前调用的回调方法。
87.Java和JSP的关系
可以把jsp理解为脚本,JSP是JAVA基础上建立的动态网页代码,JSP是使用了JAVA技术,网页里面嵌入JAVA程序的服务器技术。
java为面向对象语言
jsp作用主要是负责页面展现,java负责逻辑,业务处理
jsp 本质 是servlet,servlet本质是 java类,就是这么个关系。
88.JVM内存分区
①程序计数器(Program Counter Register):
程序计数器(Program Counter Regist)也有称作为PC寄存器,在汇编语言中,程序计数器是指CUP中的寄存器就,它保存的是程序当前执行的指令地址(也可以说是下一条指令的所在存储单元地址),当CUP需要指令时,需要从程序计数器中得到当前 执行的指令所在存储单元地址,然后根据得到的地址获取到指令,在得到指令后,程序计数器便会自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有指令
②Java栈(VM Stack):
Java栈中存放的是一个个栈帧,每个栈帧对应着一个被调用的方法,在栈帧中包括局部变量表(Local Variable)、操作数栈(Operaand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant tool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
③本地方法区(Native Method Stack):
本地方法栈与Java栈的作用和原理相似,区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是执行本地方法(Native Method)服务的,在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构做强制规定,虚拟机可以自由实现它,在HOTSpot虚拟机中直接把本地方法栈和Java栈合二为一。
④方法区(Method Area):
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程池共享的区域。在方法区中,存储每个类的信息(包括类的名称、方法信息、字段信息)静态变量、常量以及编译器变异后的的代码等。
在class文件中除了类的字段、方法、接口等描述信息外,还有一项是常量池,用来存储编译期间生成的字面量和符号引用。
⑤堆(Heap):
在c语言中,堆这部分空间是唯一一个程序员管理的内存区域,程序员可以通过malloc函数和free函数在堆上申请和释放空间
Java中的堆是用来存储对象本身以及数组(当然,数组引用是放在Java栈中的)。只不过和c语言不通,在Java中,程序员基本不关心空间释放的问题,Java的垃圾回收机制会自动进行处理,因此这部分空间也是Java垃圾收集器管理的主要区域。另外堆是被所有线程池共享的,在JVM中只有一个堆。
89.SpringMVC请求流程
90.进程的状态转换
91.类加载过程和类加载器
①类加载的定义
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据
②初始化的时机
创建类的实例,也就是new一个对象
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(Class.forName("com.lyj.load"))
初始化一个类的子类(会首先初始化子类的父类)
JVM启动时标明的启动类,即文件名和类名相同的那个类
③类加载器
类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:
1)根类加载器(bootstrap class loader):
它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。 是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
2)扩展类加载器(extensions class loader):
它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
3)系统类加载器(system class loader):
被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader
④类加载器加载Class的8个步骤:
检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步。
如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步。
请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步。
请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步。
当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步。
从文件中载入Class,成功后跳至第8步。
抛出ClassNotFountException异常。
返回对应的java.lang.Class对象。
⑤类加载机制
全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
92.什么是虚拟内存
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为「连续的虚拟内存地址」,以借此「欺骗」程序,使它们以为自己正在使用一大块的「连续」地址。
也就是说虚拟内存能提供一大块连续的地址空间,对程序来说它是连续的,完整的,实际上虚拟内存是映射在多个物理内存碎片上,还有部分映射到了外部磁盘存储器上。虚拟内存有以下两个优点:
1.虚拟内存地址空间是连续的,没有碎片
2.虚拟内存的最大空间就是cup的最大寻址空间,不受内存大小的限制,能提供比内存更大的地址空间
93.数据库三大范式
第一范式:原子不可再分(原子性)
第二范式:非主属性必须完全依赖所有主属性(消除部份依赖)
第三范式:非主属性不可以依赖于其他非主属性(消除传递依赖)
94.Mybatis接口如何与XML文件绑定
通过XML里面的mapper标签的namespace值与mapper接口的包路径.接口名进行绑定的
95.CAS操作是什么
在currentHashMap中有很多CAS操作
CAS加volatile关键字是实现并发包的基石。没有CAS就不会有并发包,synchronized是一种独占锁、悲观锁,java.util.concurrent中借助了CAS指令实现了一种区别于synchronized的一种乐观锁。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样当第二个线程想拿这个数据的时候,第二个线程会一直堵塞,直到第一个释放锁,他拿到锁后才可以访问。传统的数据库里面就用到了这种锁机制,例如:行锁,表锁,读锁,写锁,都是在操作前先上锁
乐观锁:乐观锁概念为,每次拿数据的时候都认为别的线程不会修改这个数据,所以不会上锁,但是在更新的时候会判断一下在此期间别的线程有没有修改过数据,乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。
乐观锁主要就是两个步骤:冲突检测和数据更新。当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程可以更新变量的值,其他的线程都会失败,失败的线程并不会挂起,而是告知这次竞争中失败了,并可以再次尝试。
CAS操作包括三个操作数:需要读写的内存位置(V)、预期原值(A)、新值(B)。
如果内存位置与预期原值的A相匹配,那么将内存位置的值更新为新值B。
如果内存位置与预期原值的值不匹配,那么处理器不会做任何操作。
CAS其实就是一个:我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可
96.lambda表达式介绍(Java8新特性)
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
详细资料:https://www.runoob.com/java/java8-lambda-expressions.html