Java面试题--dalao总结版

1.UUID:当前日期和时间+时钟序列+全局唯一的IEEE机器识别号(如果有网卡,从网卡MAC地址获取)

import java.util.UUID;

UUID.randUUID().toString().replace("_","");

2.栈与堆

(1)栈时运行时的单位,而堆时存储的单元。

(2)栈中存什么?堆中存什么?

栈中存的是基本数据类型和堆中对象的引用。堆中存的是对象。

一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4 byte的引用(栈堆分离的好处)。

(3)为什么不把基本类型放堆中?

因为其占用的空间一般是1~~8个字节--需要空间比较少,而且因为是基本类型,所有不会出现动态增长的情况(长度固定),因此栈中存储就够了。

(4)栈和堆的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

3.在java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。

Object   obj=new Object();

这样在程序中完成了一个Java对象的声明,但是它所占的空间为:4byte+8byte。4byte是上面部分所说的java栈中保存引用的所需空间。

4.引用类型

对象引用类型分为强引用,软引用,弱引用和虚引用

强引用:就是我们一般声明对象是虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会垃圾回收。

软引用:软引用一般被作为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间,如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemeory时,肯定是没有软引用存在的。

弱引用:弱引用与软引用类似,都是作为缓存来使。但与软引用不同,弱引用进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

虚引用:不会对生存时间产生任何的影响。虚引用的唯一作用是这个对象被收集器收集是收到一个系统通知。

5.java GC:Garbage Collection,垃圾回收

该机制对JVM(Java VirtualMachine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动地回收内存,永不停息(Never Stop)地保证JVM中的内存空间,防止出现内存泄漏和溢出的问题。

6.String,StringBuilder与StringBuffer

(1)String是不可变的对象,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,然后将指针指向新的String对象,所有经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无用对象 多了以后,JVM的GC就会开始工作,那速度是一定相当慢的。

(2)如果是StringBuffer或StringBuilder,每次运算都会对StringBuffer/StringBuilder对象本身进行操作,而不是生成新的对象,在改变对象引用。

(3)而在某些特殊情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所有这些时候String对象的速度并不会比StringBuffer对象慢。例如:

String S1="This is only a"+" simple"+" test";

StringBuffer Sb=new StringBuilder("This is only a").append(" simple").append(" test");

在JVM眼里,这个

String S1="This is only a"+" simple"+" test";其实是:String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:

String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;

String S1 = S2 +S3 + S4;

这个时候JVM会规矩地按照原来的方式去做。

(4)StringBuilder与StringBuffer区别在于StringBuffer是线程安全的,而StringBuilder非线程安全,即StringBuilder不保证同步。该类被设计用作StringBuffer的一个检验替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。两者的方法基本相同。

7.Java自动增加ArrayList大小的思路是:向ArrayList添加对象时,原对象数目加1如果大于原底层数组的长度,则以适当长度新建一个原数组的拷贝,并修改原数组,指向这个新建数组。原数组自动抛弃(java垃圾回收机制自动回收)。size则在向数组添加对象,自增1.

8.synchronized需要添加Object的原因:

(1)需要对进行操作,Object有相关方法。

(2)synchronized只是关键字,本身不带锁。

synchronized同步函数使用的锁是this,静态的同步函数(即static synchronized)使用的锁,是该函数所属的字节码对象,可以用this.getClass()获取,也可以用类名.class表示。

(3)synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。

在执行monitorenter指令时,首先要尝试获取对象的锁。如果这个对象没被锁定,获取当前线程已经拥有了那个对象的锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁就别释放。

9.OSI参考模型:物理层,数据链路层,网络层,传输层,会话层,表示层,应用层

   TCP/IP参考模型: 主机-网络层,互联网层,传输层,应用层。

10.log4j

日志等级:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL

OFF 这是最高等级,为了关闭日志记录
FATAL 指定非常严重的错误事件,这可能导致应用程序中止
ERROR 错误事件可能仍然允许应用程序继续运行
WARN 指定具有潜在危害的情况
INFO 指定能够突出在粗粒度级别的应用程序运行情况的信息的消息
DEBUG 指定细粒度信息事件是最有用的应用程序调试
ALL         各级包括自定义级别
 

TRACE 指定细粒度比DEBUG更低的信息事件

Appender,日志输出目的,有以下几种:

(1)org.apache.log4j.ConsoleAppender(控制台),

(2)org.apache.log4j.FileAppender(文件),

(3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件),

(4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件),

(5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方).

11.访问限制符

(1)public:可以被所有其他类所访问。

(2)private:只能被自己访问和修改。

(3)protected:自身,子类及同一个包中类可以访问。

(4)default(默认):同一包中的类可以访问,声明时没有加修饰符,认为是friendly

12.

(1)类的唯一标识

类加载器实例 + 全类名
(2)类加载器
1. 引导类加载器 Bootstrap ClassLoader 加载系统类
(通常从rt.jar中加载,C语言实现,引导类加载器没有对应的ClassLoader)
2. 扩展类加载器 Extension ClassLoader jre/lib/ext
3. 应用类加载器 System ClassLoader classpath环境变量或-classpath命令行选项设置的路径查找类
4. 自定义加载器 
(2)双亲委派模型
如果一个类加载器收到类加载的请求,首先不会自己尝试去加载这个类,而是委派给父加载器去完成。只有当父加载器反馈自己无法完成此次加载时,子加载器才会自己去加载。
(3)双亲委派模型意义
防止用户自己定义系统类,比如用户定义了String类,父加载器可以加载,就不会加载用户自己定义的类。
为什么需要自定义类加载器
(1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
(2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。

(3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。

13.垃圾回收机制
1. java语言规范没有明确的说明JVM 使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做两件基本事情:
(1)发现无用的信息对象;(2)回收将无用对象占用的内存空间。使该空间可被程序再次使用。
判断对象是否存活有以下两种算法:
2. 引用记数法:引用计数算法是垃圾回收器中的早期策略,在这种方法中,堆中的每个对象实例都有一个引用计数器,点一个对象被创建时,且该对象实例分配给一个变量,该变量计数设置为1 ,当任何其他变量赋值为这个对象的引用时,计数加1 ,(a=b ,则b引用的对象实例计数器+1)但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1,任何引用计数器为0 的对象实例可以当做垃圾收集。 当一个对象的实例被垃圾收集是,它引用的任何对象实例的引用计数器减1.
缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0.
3. 可达性分析算法
可达性算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一系列节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
可作为GC ROOT的对象包括以下几种:
1. 虚拟机栈中引用的对象
2. 方法区中类静态属性引用的对象
3. 方法区中常量引用的对象
4. 本地方法栈中JNI(native方法)引用的对象
5. Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。

6. 垃圾收集器判断一个对象的内存空间是否无用的标准是:如果该对象不能再被程序中任何一个"活动的部分"所引用,此时我们就说,该对象的内存空间已经无用。所谓"活动的部分",是指程序中某部分参与程序的调用,正在执行过程中,尚未执行完毕。

14.开放系统互连,Open System Internetwork,OSI
物理层:比特流透明连接,单位是比特
数据链路层:差错控制,流量控制,屏蔽物理层采用的传输技术的差异,单位是帧
网络层:选择传输路径,实现流量控制,拥塞控制,网络互联,单位是分组
传输层:为不同地理位置计算机提供端-端连接,单位是报文
会话层:维护主机间连接的建立,管理,终止
表示层:数据格式交换,数据加密解密,数据压缩与恢复
应用层:应用程序间通信控制
TCP/IP参考模型:
主机-网络层:发送和接收IP分组
互联网络层:使用IP协议,处理来自传输层的数据发送请求,处理接收的分组,路由选择,流量控制,拥塞控制
传输层:传输控制协议(Transport Control Protocol,TCP),UDP
应用层:各种标准的网络应用协议
#15
三次握手:
1. 客户端向服务器发送“连接建立请求报文”;
2. 服务器接收到请求后,如果同意建立连接,则向客户端发送“连接建立请求确认报文”,进入“准备接收”状态;
3. 客户端接收到“连接建立请求确认报文”后,发送一次“连接建立请求确认报文”,然后客户端进入“已进入连接”状态,服务端接收到报文后,也进入“已进入连接”状态。
四次挥手
客户与服务器都可以主动提出连接释放请求
1. 客户准备释放连接时,向服务端发送一次“连接释放请求报文”,停止发送数据;
2. 服务器接收到报文后,向客户端发送“连接释放请求确认报文”;
   客户到服务器的连接断开,但是服务器到客户的连接还没断开,如果服务器还有数据报文要发送,可以继续发送直至完毕,这时服务端处于“半关闭”状态;
3. 服务器没有数据可以发送时,会向客户端发送“连接释放请求报文”;
4. 客户端接收到报文后,向服务器发送“连接释放请求确认报文”。
#16
HashMap原理:
HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表。当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,计算方法是先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入HashMap中,当计算出的位置相同时,由于存入位置是一个链表,则把这个key-value对插入链表头。
HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。
初始容量和加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中槽的数量(即哈希数组的长度),初始容量是创建哈希表时的容量(从构造函数中可以看出,如果不指明,则默认为16),加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize 操作(即扩容)。
扩容是是新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。
如果加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。如果我们在构造方法中不指定,则系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。
#17
HashMap与HashTable区别:
1. 父类不同,但都实现了Map接口。
2. HashTable的方法加了synchronized同步锁,所以HashTable是线程安全的,而HashMap是非线程安全的。多线程环境下可以使用ConcurrentHashMap代替HashMap
3. Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
HashMap中key和value都允许为null。
4. HashMap不包含contains方法,只有containsKey和containsValue;
   而HashValue除了这两个方法,还有contains方法,跟containsValue一样;
   HashMap废除contains方法是因为造成歧义,看不出contains是containsKey还是containsValue
#18
句柄:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等
#19
哈希冲突解决方法:
开放定址法
线性探测法
二次探测法
伪随机探测法
链地址法:具有相同散列地址的记录放在同一个单链表中,称为同义词链表。
再哈希法:同时构造多个不同的哈希函数,这种方法不易产生聚集
建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的值,一律放入溢出表
#20
sleep和wait区别:
sleep是Thread类的方法,wait是Object类的方法。
sleep让正在执行的线程让出CPU使用权,让CPU去执行其他任务,但是不释放锁,指定时间后继续执行。
wait方法会放弃对象锁,进入等待队列,等到调用notify/notifyAll方法,才会继续参与锁的竞争。
#21
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;
而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。
#22
jdk1.6中加入了很多针对锁的优化措施,也就是jdk1.6发布后,synchronized与ReentrantLock的性能基本上是完全持平了。
悲观锁假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。
所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
悲观的并发策略,总是认为只要不去做正确的同步措施(例如加锁),那就肯定会出现问题,无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。随着硬件指令集的发展,我们有了另外一个选择:基于冲突检测的乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(最常见的补偿措施就是不断地重试,直到成功为止)。
synchronized是悲观锁,ReentrantLock是乐观锁。
ReentrankLock实现了Lock接口。
(synchronized是非公平锁,ReentrankLock支持公平锁和非公平锁,默认非公平)
#23
进程与线程
进程是程序执行过程中分配和管理资源的最小单位。
线程是进程的一部分,是CPU调度的基本单位。
进程可分为单线程进程和多线程进程。
使用线程的好处是有多个任务需要处理机处理时,减少处理机的切换时间;而且,线程的创建和结束所需要的系统开销也比进程的创建和结束要小得多。
要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。
#24
并发与并行
并发是两个或多个事件在同一时间间隔内执行。
并行是两个或多个事件在同一时刻执行。
#25
接口与抽象类
1 接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
2 接口里只能定义静态常量,不能定义普通成员变量,抽象类里则既可以定义普通成员变量,也可以定义静态常量。
3 接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
4 接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
5 一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承不足。

1.语法层面上的区别

  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

  4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

2.设计层面上的区别

  1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

  2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

#26
MySql索引
1. 普通索引 这是最基本的索引,它没有任何限制。 
CREATE INDEX indexName ON mytable(username(length));
2. 唯一索引 它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。 
CREATE UNIQUE INDEX indexName ON mytable(username(length)); 
3. 主键索引
 CREATE TABLE mytable(   ID INT NOT NULL,    username VARCHAR(16) NOT NULL,   PRIMARY KEY(ID) ); 
4. 组合索引 
 ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
5. 全文索引 FULLTEXT 全文索引可以在VARCHAR或者TEXT类型的列上创建
 如果在firstname、lastname、age这三个列上分别创建单列索引,效果是否和创建一个firstname、lastname、age的多列索引一样呢?答案是否定的,两者完全不同。当我们执行查询的时候,MySQL只能使用一个索引。如果你有三个单列的索引,MySQL会试图选择一个限制最严格的索引。但是,即使是限制最严格的单列索引,它的限制能力也肯定远远低于firstname、lastname、age这三个列上的多列索引。
 索引的缺点:
 1. 索引要占用磁盘空间。通常情况下,这个问题不是很突出。但是,如果你创建每一种可能列组合的索引,索引文件体积的增长速度将远远超过数据文件。如果你有一个很大的表,索引文件的大小可能达到操作系统允许的最大文件限制。
2. 对于需要写入数据的操作,比如DELETE、UPDATE以及INSERT操作,索引会降低它们的速度。这是因为MySQL不仅要把改动数据写入数据文件,而且它还要把这些改动写入索引文件。
#27
数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。非聚集索引与聚集索引的区别在于非聚集索引的辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的主键索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在主键索引中找到完整的行记录数据。
https://www.cnblogs.com/vianzhang/p/7922426.html
#28
B树(B-树)、B+树
1. 二叉查找树:不一定平衡,因此查找效率不能保证。
2. B-树:多路搜索树,关键字遍布整棵树,不一定在叶子节点。
3. B+树:B-树的变体,也是多路搜索树,所有关键字都在叶子节点出现,有N棵子树的结点中含有N个关键字。
#29
1. 单一职责原则:类的职责要单一,不能将太多职责放在同一个类。
2. 开闭原则:软件实体对扩展是开放的,对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能。
3. 里氏代换原则:在软件系统中,一个可以接收基类对象的地方,必然可以接收子类对象。
(即子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。
如果方法只存在子类,父类不提供声明,则无法在父类对象直接使用该方法。
如果子类不实现父类的某些方法,则父类使用这些的方法的时候,子类无法使用。)
4. 依赖倒转原则:要针对抽象层编程,而不要针对具体类编程。(里氏代换原则是依赖倒转的基础)
5. 接口隔离原则:使用多个专门的接口来取代一个统一的接口(要注意接口的粒度,接口太小会导致接口泛滥,不利于维护;
接口太大就违背接口隔离原则,灵活性差)
6. 合成复用原则:在系统中应尽量使用组合和聚合关联关系,尽量少甚至不使用继承关系。
#30
innodb与myisam的选择
1. 如果需要支持事务,选择innodb,如果不需要可以考虑myisam
2. 如果表中绝大多数都是读,可以考虑myisam,如果读写都很频繁,考虑innodb
3.  MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
MEMORY存储引擎使用内容中的数据来创建。每个MEMORY表对应一个磁盘文件,格式为.frm。
MEMORY表的访问速度非常快,因为他是放在内存中的,而且默认使用HASH索引,但是一旦服务关闭,表中数据全部丢失。
mysql> create table tab_mem engine = memory select * from student;
MEMORY存储引擎主要用于那些内容变化不频繁的代码表,或者作为统计操作的中间结果,便于高效地对中间表进行分析。
#40
ArrayList与LinkedList性能比较:
(ArrayList内部是数组,LinkedList内部是双向循环链表)
添加:LinkedList效率更高
原因:当元素个数大于ArrayList当前长度时,ArrayList会创建一个新的数组,将原来的元素复制过去,效率很低。
而LinkedList的头指针的上一个结点就是最后一个结点,数据再多,都是添加到最后一个结点的下一个,效率较高。
删除:分情况,综合来讲,LinkedList效率较高。
获取和设置:ArrayList效率远远高于LinkedList。
注意:
ArrayList删前面元素效率高,删后面元素效率低。
LinkedList删前面和后面元素效率高,删中间元素效率低。
ArrayList循环删除数据时,从后面向前遍历,性能大大提高。比如100个排队,最后一人走了,前面99人都不用动,但如果第1个人走了,后面99个人都要向前移动。
#41
回滚:事务执行过程中发生异常,取消之前的操作(也就是事务未提交)
数据库脏读:假如A将B的工资从1000修改为1w,事务还未提交,这是B查询到自己工资升到了1w,开心得请同事吃饭,然后事务发生了异常,回滚后,B的工资又回到1k,这个过程就是脏读
#42
java内存分为:
线程共享:
方法区(即永久代):被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
堆:对象实例
线程私有:
虚拟机栈:每个方法在执行的时候,都会创建一个栈帧,用于存放局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈入栈出栈的过程。
本地方法栈:与虚拟机栈的作用相似,虚拟机栈为java方法服务,本地方法为native方法服务。
程序计数器:当前线程执行的字节码指令的地址
#43
class文件除了有版本、字段、方法、接口等描述信息,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。
#44
HotSpot虚拟机中,对象的内存布局可以分为三个区域:
对象头:
对象自身运行时数据,如哈希码(HashCode),GC分代年龄,锁标志状态、线程持有的锁、偏向线程ID、偏向时间戳等;
类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针确定对象是哪个类的实例。
实例数据:各种类型的字段内容,包括父类继承来的。
对齐填充:对象的大小必须是8字节的整数倍,当对象实例部分没有对齐时,需要填充。
#45
java程序通过栈上的reference数据来操作堆上的具体对象。
对象的访问定位:
1.句柄。java堆划分一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄包含了对象实例数据和类型数据各自的地址信息。
2.直接指针。reference中存储的直接就是对象实例数据的地址。
优缺点:
1.句柄方法稳定,对象被移动时,改变的是句柄中对象实例数据和类型数据的地址信息,句柄本身的地址不变,即reference不变。
2.直接指针方法快速,少了一次指针定位的时间开销。
#46
除了程序计数器,虚拟机内存的其他区域都可能发生OutOfMemoryError(OOM)
#47
程序计数器、虚拟机栈、本地方法栈,这几个内存区域的生命周期跟线程一样,线程结束时,因此这几个区域的内存分配和回收具备确定性。
而堆和方法的内存分配和回收是动态的,垃圾回收器关注的是这部分内存。
#48
Java 的屏幕坐标是以像素为单位,容器的左下角被确定为坐标的起点。
#49
1.静态代码块 2.构造代码块3.构造方法的执行顺序是1>2>3;明白他们是干嘛的就理解了。
1.静态代码块:是在类的加载过程的第三步初始化的时候进行的,主要目的是给类变量赋予初始值。
2.构造代码块:是独立的,必须依附载体才能运行,Java会把构造代码块放到每种构造方法的前面,用于实例化一些共有的实例变量,减少代码量。
3.构造方法:用于实例化变量。
1是类级别的,2、3是实例级别的,自然1要优先23.
#50
System.gc()函数的作用是提醒虚拟机,程序员希望进行垃圾回收,但虚拟机不一定会回收
#51
垃圾回收算法:
标记清除法
复制法
%52
java堆内存分为:
permanent 持久代,主要存放类定义信息,与垃圾回收要收集的对象关系不大。
heap = {OLD + NEW = Eden + Survivor * 2},OLD即年老代old generation,NEW即年轻代young generation。
当一组对象生成时,内存申请过程如下:
JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域。
当Eden区空间足够时,内存申请结束。否则执行下一步。
JVM试图释放在Eden区中所有不活跃的对象(Young GC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
Survivor区被用来作为Eden区及年老代的中间交换区域。当年老代空间足够时,Survivor区中存活了一定次数的对象会被移到年老代。
当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
Full GC后,若Survivor区及年老代仍然无法存放从Eden区复制过来的对象,则会导致JVM无法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。
OOM(“Out of Memory”)异常一般主要有如下2种原因:
1. 年老代溢出,表现为:java.lang.OutOfMemoryError:Javaheapspace
这是最常见的情况,产生的原因可能是:设置的内存参数Xmx过小或程序的内存泄露及使用不当问题。
例如循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如MAT就很不错。
2. 持久代溢出,表现为:java.lang.OutOfMemoryError:PermGenspace
通常由于持久代设置过小,动态加载了大量Java类而导致溢出,解决办法唯有将参数 -XX:MaxPermSize 调大(一般256m能满足绝大多数应用程序需求)。将部分Java类放到容器共享区(例如Tomcat share lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库。
#53
线程结束的三个原因:
1、run方法执行完成,线程正常结束
2、线程抛出一个未捕获的Exception或者Error
3、直接调用该线程的Stop方法结束线程(不建议使用,容易导致死锁,stop和suspend、resume一样,也可能发生不可预料的结果)
#54
抽象类:用abstract修饰,抽象类中可以没有抽象方法,但抽象方法肯定在抽象类中,且抽象方法定义时不能有方法体;抽象类不可以实例化只能通过继承在子类中实现其所有的抽象方法;抽象类如果不被继承就没有任何意义;抽象类为子类定义了一个公共类型,封装了子类中的重复内容。
接口:同Interface关键字定义接口,是特殊的抽象类因为类中只包含抽象方法;接口中不能定义成员变量可以定义常量;接口是其通过其他类使用implements关键字定义实现类,一个类一旦实现接口就必须实现其中的所有抽象方法,一个类可以实现多个接口,接口名之间用逗号隔开即可;一个接口可以通过extends关键字继承另一个接口,与此同时继承了父类中的所有方法。
#55
Servlet的生命周期一般可以用三个方法来表示:
1. init():仅执行一次,负责在装载Servlet时初始化Servlet对象
2. service() :核心方法,一般HttpServlet中会有get,post两种处理方式。在调用doGet和doPost方法时会构造servletRequest和servletResponse请求和响应对象作为参数。
3. destory():在停止并且卸载Servlet时执行,负责释放资源
初始化阶段:Servlet启动,会读取配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,将ServletConfig作为参数来调用init()方法。
#56
<<表示左移位
>>表示带符号右移位
>>>表示无符号右移
但是没有<<<运算符
#57
wait方法会抛出InterruptedException异常
#58
mysql的drop、truncate、delete
drop:删除整个表
truncate:删除表中所有数据
delete:删除表中部分数据
#59
List<String> list = new ArrayList();只能放String类型
List list = new ArrayList<String>();可以放任意类型
#60
Comparable与Comparator
Comparable用于自然排序,Comparator用于自定义排序。
#61
方法可以与Class同名,eclipse会警告"This method has a constructor name",但不影响运行
#62
hashmap的底层是数组加链表的结构,它的线程是不安全的,而hashtable是在hashmap的基础上,对整个哈希表(数组)加锁 sychronized实现线程安全。而我们知道,方法访问数组中的数据,可能只涉及到数组的部分,对整个数组加锁会降低线程并发执行的效率,所以如果对数组分段加锁,使用segment分片锁,这样一个线程只会锁住数组的一片,其他线程仍可以访问数组的其他片进行写操作,具有这样的分片锁的机制是就是concurrenthashmap。
(segment继承自ReentrantLock)
JDK1.8 的 ConcurrentHashMap 采用CAS+Synchronized保证线程安全。 JDK1.7 及以前采用segment的分段锁机制实现线程安全
#63
Arrays.asList()  返回  java.util.Arrays.ArrayList 对象,这里的 ArrayList 是 Arrays 私有的内部类,
而不是java.util.ArrayList
#64
正则表达式元字符:
\d 匹配一个数字
\D 匹配一个非数字
\n 换行符
\r 回车符
\s 不可见字符
\S 可见字符
\w 匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
^ 字符串的开始位置
$ 字符串的结束位置
* 匹配前面的子表达式任意次。例如,zo*能匹配“z”,“zo”以及“zoo”。*等价于{0,}。
+ 匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
. 匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。
#65
linux which命令:在环境变量#PATH设置的目录中,查看命令是否存在以及命令的位置
#66
应用 应用层协议 运输层协议
名字转换 DNS UDP
文件传输 TFTP UDP
路由选择协议 RIP UDP
IP地址配置 BOOTP,DHCP UDP
网络管理 SNMP UDP
远程文件服务器 NFS UDP
IP电话 专用协议 UDP
流式多媒体通话 专用协议 UDP
多播 IGMP UDP
电子邮件 SMTP TCP
远程终端输入 TELNET TCP
万维网 HTTP TCP
文件传送 FTP TCP
邮局协议版本3 POP3 TCP
#67
volatile:
1.volatile可以保证在多线程读取的数据是最新的(这句话是有点问题的,后面会解释),因为在多线程对应多核处理器的环境下,每一个线程占用一个处理器,每一个处理器拥有自己的缓存,当线程需要某一个数据的时候,如果这个数据没有被volatile修饰,线程会先从缓存中读取,如果发现在处理器的缓存中存在该数据,就直接读取缓存里的数据,如果没有,才会去内存中读取,对这个数据操作完后,会将操作完的数据放入缓存中,而不是马上放入内存中的。这样就会导致两个线程在同时对某一个数据操作的时候,一个线程对这个数据做出了改变,但是只是将做出改变的数据存入到了自己的缓存里面,而没有存入内存中,另一个线程从内存中读取的数据就不是最新的。如果在数据前面加上volatile就会让线程读取数据的时候强制的从内存中去取,也会在线程对数据做出改变后,就让数据立即更新到内存中。
2.volatile还拥有hapen-befor性,happens-before 关系是程序语句之间的排序保证,这能确保任何内存的写,对其他语句都是可见的。当写一个 volatile 变量时,随后对该变量读时会创建一个 happens-before 关系。所以,所有在 volatile 变量写操作之前完成的写操作,将会对随后该 volatile 变量读操作之后的所有语句可见。
3.volatile不能保证数据在多线程下的原子性。这是将数据放入处理器的缓存和放入到内存是两个原子操作,当一个线程对某个数据做出改变,先将这个改变后的数据放入缓存中,但这时刚好有另一个线程从内存中读取了该数据,但是已经改变了的数据却没有被放入到内存中,所以另一个线程读取的数据是有问题的。
(volatile只是强制线程从内存读取数据,并不能保证内存的数据是最新的,即1、3点并不矛盾)
#68
可变参数:
public static double max(double... values){
double max = values[0];
for(int i = 1; i < values.length; i++){
max = max > values[i] ? max : values[i];
}
return max;
}
values相当于double类型的数组
System.out.printf("%d %s", new Object[]{12, "hello"});
可以输出正常输出
#69
TCP如何确保连接建立:三次握手
#70
&11111 相当于对11111取模
#71
对于整数a,b来说,取模运算或者求余运算的方法要分如下两步:
1.求整数商:c=a/b
2.计算模或者余数:r=a-(c*b)
求模运算和求余运算在第一步不同
取余运算在计算商值向0方向舍弃小数位
取模运算在计算商值向负无穷方向舍弃小数位
例如:4/(-3)约等于-1.3
在取余运算时候商值向0方向舍弃小数位为-1
在取模运算时商值向负无穷方向舍弃小数位为-2
所以
4rem(-3)=1
4mod(-3)=-2
#72
客户端使用https与服务端进行通信的流程:
1. 客户使用https的url访问服务器,与服务器建立ssl连接。
2. 服务器接收到请求后,将网站的证书信息(包括公钥)发送给客户端。
3. 客户端的浏览器与服务器协商ssl连接的安全等级,也就是信息加密的等级。
4. 客户端跟据安全等级,建立会话密钥,然后用公钥将会话密钥加密,然后发送给服务端。
5. 服务端利用自己的私钥解密出会话密钥。
6. 服务器利用会话密钥加密与客户端之间的通信。
SSL协议:提供数据安全和完整性的协议
#73
An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter.
any Unicode character that is a "Java letter"
如上,标识符由java letter或java digit组成,任何unicode都是java letter,所以中文可以作为类/方法/变量名。
#74
String str = "我是";
str.length()等于2,因为Java是unicode字符
#75
平均存取时间 = 寻道时间 + 旋转延迟时间(磁头定位到所在扇区的时间)+ 传输时间
扇区:磁盘上的每个磁道被等分为若干个弧段,这些弧段便是磁盘的扇区. 扇区是磁盘最小的物理存储单元
寻道时间:磁头从开始移动到移动到数据所在磁道所需时间
旋转延迟时间:通过盘片的旋转,使得要读取的扇区转到读写头的下方,这段时间称为旋转延迟时间
旋转延迟中,最多旋转1圈,最少不用旋转,平均情况下,需要旋转半圈
例:一个 7200(转 / 每分钟)的硬盘,每旋转一周所需时间为 60×1000÷7200=8.33 毫秒,则平均旋转延迟时间为 8.33÷2=4.17 毫秒
#76
长连接与短连接:
在HTTP/1.0中,默认使用短连接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断。
如果客户端访问的某个HTML或其他类型的WEB页面包含其他的WEB资源,如Javascript文件、图像文件、CSS文件等,浏览器每遇到这样一个WEB资源,就会建立一次HTTP连接。
从HTTP/1.1起(目前广泛使用),默认使用长连接,一个网页打开完成后,客户端和服务器用于传输数据的TCP连接不会关闭,如果客户端继续访问这个服务器的页面,会继续使用这个连接。
使用长连接的HTTP协议,请求头会有Connection: Keep-alive这行代码。
Keep-alive不会永久保持连接,他有一个保持时间,实现长连接需要客户端和服务器都支持。
HTTP协议的长连接和短连接,实际上是TCP协议的长连接和短连接。
长连接用于操作比较频繁、而且连接数不多的情况,因为TCP连接的建立和释放开销较大。
短连接用于访问量较多、但操作不频繁的WEB网站,如果有成千上万个用户都用长连接,资源消耗就会很大。
#77
ArrayList<String> arr = new ArrayList<>();
for(int i = 0; i < 10; i++){
arr.add(i + "");
}
List<String> list = new ArrayList<String>(arr);
System.out.println(list.size());
//输出10
#78
List list1 = new ArrayList<>();
List list2 = new ArrayList<String>();
List<String> list3 = new ArrayList<>();
List<String> list4 = new ArrayList<String>();
list1和list2可以Object类型的数据,list3和list4只能放String,list3是list4的简写而已。
#79
[[Ljava.lang.String
[[指二维数组,L指引用类型
#80
myisam与innodb
1. 区别:
(1)事务处理:
MyISAM是非事务安全型的,而InnoDB是事务安全型的(支持事务处理等高级处理);
(2)锁机制不同:
MyISAM是表级锁,而InnoDB是行级锁;
(3)select ,update ,insert ,delete 操作:
MyISAM:如果执行大量的SELECT,MyISAM是更好的选择
InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表
(4)查询表的行数不同:
MyISAM:select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含   where条件时,两种表的操作是一样的
InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行
(5)外键支持:
mysiam表不支持外键,而InnoDB支持
2. 为什么MyISAM会比Innodb 的查询速度快。
INNODB在做SELECT的时候,要维护的东西比MYISAM引擎多很多;
1)数据块,INNODB要缓存,MYISAM只缓存索引块,  这中间还有换进换出的减少; 
2)innodb寻址要映射到块,再到行,MYISAM 记录的直接是文件的OFFSET,定位比INNODB要快
3)INNODB还需要维护MVCC一致;虽然你的场景没有,但他还是需要去检查和维护
MVCC ( Multi-Version Concurrency Control )多版本并发控制 
3. 应用场景
MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且行锁定的机会比较大的情况。
#81
怎么实现订单超时后失效:
定时轮询。启动一个计划任务,每隔一定时间处理一次,这种处理方式只是适用比较小而简单的项目
定时轮询的不足:
1、时效性差,会有一定的延迟,这个延迟时间最大就是每隔一定时间的大小,如果你设置每分钟定时轮询一次,那么理论上订单取消时间的最大误差就有一分钟,当然也可能更大,比如一分钟之内有大量数据,但是一分钟没处理完,那么下一分钟的就会顺延。
2、效率低。
在服务启动时,查询数据库中的已下单未支付的订单数据,按下单时间先后存入队列中,先下单的存到头不,后下单存入队列尾部,取队列的头元素
检测与现在的时间,如果超过40分钟,则执行数据操作,即关闭订单,但是只关闭未支付的订单,之后在将头元素从队列移出,并取出下一个元素进行检测,以此类推
如果检测出时间未到40分钟,则线程等待相应的时间差,之后在执行订单操作
索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。
聚集索引与非聚集索引:
https://www.cnblogs.com/aspwebchh/p/6652855.html
数据库ACID:
https://blog.csdn.net/shuaihj/article/details/14163713
Atomicity: 原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。
Consistency: 一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
比如,对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNT表中aaa和bbb的存款总额为2000元。
 数据库层面的一致性是,在一个事务执行之前和之后,数据会符合你设置的约束(唯一约束,外键约束,Check约束等)和触发器设置。这一点是由SQL SERVER进行保证的。比如转账,则可以使用CHECK约束两个账户之和等于2000来达到一致性目的
Isolation:隔离性,多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
Durability: 持久性, 持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。
SQL SERVER通过write-ahead transaction log来保证持久性。write-ahead transaction log的意思是,事务中对数据库的改变在写入到数据库之前,首先写入到事务日志中。而事务日志是按照顺序排号的(LSN)。当数据库崩溃或者服务器断点时,重启动SQL SERVER,SQLSERVER首先会检查日志顺序号,将本应对数据库做更改而未做的部分持久化到数据库,从而保证了持久性。
低基数索引会对性能产生负面影响(基数:索引中唯一值的数量),比如1w条记录,如果某个索引的基数只有2,就会把是数据划分成一边5k,这样查找效率还是很低。
线程池的作用:
https://www.oschina.net/question/565065_86540
线程池作用就是限制系统中执行线程的数量。
     根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
不调用size()方法确定List的大小:如果知道最大范围,就从0到最大范围,对list.contains二分,如果为false就往小范围找,如果为true(且下一个元素也为true)就往大范围找,如果为true且下一个元素为false,就找到了。
如果不确定最大范围,就从INT_MAX或LONG_MAX找,最多找32或64次。
初始的java版本中定义了一个stop方法来终止一个线程还定义了一个suspend方法来阻塞一个线程,直到另一个线程调用resume方法。这两个方法在Java SE 1.2之后就被弃用了,因为这两种方法都不安全,下面我们分别来讨论一下为什么不安全和应该怎样做才是安全的。
一、stop方法为什么不安全
其实stop方法天生就不安全,因为它在终止一个线程时会强制中断线程的执行,不管run方法是否执行完了,并且还会释放这个线程所持有的所有的锁对象。这一现象会被其它因为请求锁而阻塞的线程看到,使他们继续向下执行。这就会造成数据的不一致,我们还是拿银行转账作为例子,我们还是从A账户向B账户转账500元,我们之前讨论过,这一过程分为三步,第一步是从A账户中减去500元,假如到这时线程就被stop了,那么这个线程就会释放它所取得锁,然后其他的线程继续执行,这样A账户就莫名其妙的少了500元而B账户也没有收到钱。这就是stop方法的不安全性。
二、suspend方法为什么被弃用
suspend被弃用的原因是因为它会造成死锁。suspend方法和stop方法不一样,它不会破换对象和强制释放锁,相反它会一直保持对锁的占有,一直到其他的线程调用resume方法,它才能继续向下执行。
假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在获得相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。这就是suspend被弃用的原因
实体完整性:主属性(主键的属性,一个或多个)不能取空值,否则就不是完整的实体
参照完整性:一个关系的外键的取值,要么是空值,要么从以该外键为主键的表的主键中取值。
(外键:如果A表的某一列或多列,是B表的主键,这一列或多列就是A表的外键)
用户自定义完整性:根据应用环境的要求和实际的需要,对某一具体应用所涉及的数据提出约束性条件。
如何创建索引:
较频繁的作为查询条件的字段应该创建索引
 延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
静态内部类只有被调用才会加载
http是无状态协议,无状态指服务器不保存客户端上一次访问的数据,比如:
有状态:
A:你今天中午吃的啥?
B:吃的大盘鸡。
A:味道怎么样呀?
B:还不错,挺好吃的。

无状态:
A:你今天中午吃的啥?
B:吃的大盘鸡。
A:味道怎么样呀?
B:???啊?啥?啥味道怎么样?

通过增加cookie和session机制,现在的网络请求其实是有状态的
Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;Redo日志记录某数据块被修改后的值,可以用来恢复未写入data file的已成功事务更新的数据。下面的示例来自于杨传辉《大数据分布式存储系统 原理解析与架构实践》,略作改动。
例如某一事务的事务序号为T1,其对数据X进行修改,设X的原值是5,修改后的值为15,那么Undo日志为<T1, X, 5>,Redo日志为<T1, X, 15>。
lock table table_name read;
设置其他会话对当前表只能读,不能写
1.当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。例如,两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文档。最后保存其更改复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。

2. 脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。例如,一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。


3.不可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

4.幻觉读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

一个类实现了序列化接口,则该类里的所有对象都要求实现序列化接口,不然在进行序列化进会抛异常。因为序列化好比深层克隆,它会序列化各个对象属性里的对象属性。 如果一个属性没有实现可序列化,而我们又没有将其用transient 标识, 则在对象序列化的时候, 会抛出java.io.NotSerializableException 异常。
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了(一般升序采用大顶堆,降序采用小顶堆)。
复制算法:
将可用内存分为大小相等的两部分,每次只使用其中的一块。回收的时候,将存活的对象复制到另外一块,再对刚才使用的一块空间进行垃圾回收,这样就不用考虑内存碎片的问题。但是代价是将可用内存缩小到了50%,造成资源浪费。
IBM公司的研究表明,新生代中98%的对象是朝生夕死的,也就是每次垃圾回收时,存活对象占的比例很小。
所以可以将可用内存分为较大的一块Eden区和两块较小的Survivor区,每次使用Eden和其中一块Survivor,垃圾回收时,将Eden和其中一块Survivor中存活的对象复制到另一块Survivor,然后将Eden和刚才使用的Survivor空间清理掉。HotSpot虚拟机默认Eden和Survivor的比例时8:1,也就是每次新生代可用内存占整个新生代容量的90%。
但是考虑到存活对象大于10%的情况,当Survivor空间不够用时,需要老年代进行分配担保,就是另外一块Survivor空间不够存放上一次的存活对象时,这些对象通过分配担保机制进入老年代。

标记-整理算法:
将可回收的对象进行标记,然后让存活的对象向另一端移动,然后直接清理边界以外的内存。

分代收集算法:
把java的堆分成新生代和老年代,根据不同年代的特点选择不同的算法。
新生代每次存活的对象很少,采用复制算法,需要复制的成本较低。
老年代每次存活的对象较多,没有额外空间分配担保,最好采用标记-整理算法。

java所有包装类直接或间接实现了serializable。
所有包装类都是不可变的。

String str = "hello";
str += "world";
执行第一句时,str指向了字符串常量"hello",执行第二句时,str指向了"hello world"字符串常量,只是改变了str指向的内容,原先的"hello"并没有改变

String类不可变性的好处
只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
public class JoinTest {
    public static void main(String [] args) throws InterruptedException {
        ThreadJoinTest t1 = new ThreadJoinTest("小明");
        ThreadJoinTest t2 = new ThreadJoinTest("小东");
        t1.start();
        /**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
         程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
         所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
         */
        t1.join();
        t2.start();
    }


}
class ThreadJoinTest extends Thread{
    public ThreadJoinTest(String name){
        super(name);
    }
    @Override
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(this.getName() + ":" + i);
        }
    }
}
 /**join方法可以传递参数,join(10)表示main线程会等待t1线程10毫秒,10毫秒过去后,
         * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行
         */
t1.join(10);
S (share) 共享锁,又称为读锁,若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
X (exclusive) 独占锁,又称为写锁,若事物T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。
进程同步:信号量、管程
最初整型信号量被定义为一个用于表示资源数目的整型量S,除初始化外,仅能通过两个标准的原子操作wait(S)和signal(S)来访问。这两个操作分别称为P、V操作。
管程相当于围墙,把共享变量和对它进行操作的若干过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,二管程一次只允许一个进程进入管程,从而实现了进程互斥。
进程与管程:
进程的目的是实现系统的并发性,管程的目的是解决共享资源的互斥问题。
线程进程的区别体现在4个方面:
第一:因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。

第二:体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。。

第三:体现在CPU系统上面,线程使得CPU系统更加有效,因为操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

第四:体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助。

进程与线程的选择取决以下几点:1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

因为我的项目中需要对数据段的数据共享,可以被多个程序所修改,所以使用线程来完成此操作,无需加入复杂的通信机制,使用进程需要添加复杂的通信机制实现数据段的共享,增加了我的代码的繁琐,而且使用线程开销小,项目运行的速度快,效率高。

如果只用进程的话,虽然安全性高,但是对代码的简洁性不好,程序结构繁琐,开销比较大,还需要加入复杂的通信机制,会使得我的项目代码量大大增加,切换速度会变的很慢,执行效率降低不少。。

进程间通信方式:
1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
6. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
7. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
8. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
文件描述符
https://blog.csdn.net/u013078669/article/details/51172429


一个作业从提交开始直到完成,往往要经历以下三级调度:

1) 作业调度。又称高级调度,.其主要任务是按一定的原则从外存上处于后备状态的作业中挑选一个(或多个)作业,给它(们)分配内存、输入/输出设备等必要的资源,并建立相应的进程,以使它(们)获得竞争处理机的权利。简言之,就是内存与辅存之间的调度。对于每个作业只调入一次、调出一次。

多道批处理系统中大多配有作业调度,而其他系统中通常不需要配置作业调度。作业调度的执行频率较低,通常为几分钟一次。

2) 中级调度。又称内存调度。引入中级调度是为了提高内存利用率和系统吞吐量。为此,应使那些暂时不能运行的进程,调至外存等待,把此时的进程状态称为挂起状态。当它们已具备运行条件且内存又稍有空闲时,由中级调度来决定,把外存上的那些已具备运行条件的就绪进程,再重新调入内存,并修改其状态为就绪状态,挂在就绪队列上等待。

3) 进程调度。又称为低级调度,其主要任务是按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它。进程调度是操作系统中最基本的一种调度,在一般操作系统中都必须配置进程调度。进程调度的频率很高,一般几十毫秒一次。

JNI: java native interface,java访问本地方法的接口

HashTable或者Collections.synchronizedMap,不管是读还是写操作,他们都会给整个集合上锁,导致同一时间的其他操作被阻塞,所以性能较低

临界资源:一次仅允许一个进程使用的资源
临界区:访问临界资源的一段代码

t1, t2,t3 设计一个方案,t1运行结束之后,t2才能运行,t2运行结束之后,t3才能运行:
t1.start();
t1.join();

t2.start();
t2.join();

t3.start();
t3.join();

mysql隔离等级
丢失更新:两个事务对同一个数据做了更改,然后才提交,这样第一个更改就丢失了、
脏读:一个事务读到另一个事务未提交的中间数据
不可重复读:一个事务的两次查询之间,由于其他事务的影响,得到了不同的结果
幻读(虚读):例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

现在来看看MySQL数据库为我们提供的四种隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

myisam与innodb的索引比较:

myisam是非聚集索引,其叶子节点存的是行记录在内存中的物理地址。

innodb是聚集索引,如果是主键索引,其叶子节点存放的是完整的行数据;如果是辅助索引(即非主键索引),其叶子节点存放的是主键值。
因此,innodb按主键查询时非常快,但是主键一般是无意义的唯一序列,很少情况下会按主键查询。
而innodb的辅助索引实际上要经过两次查询,先拿到主键,再按主键查询。
因此innodb一般情况下,查询速度慢于myisam。
内存分配原则:
1.对象优先分配在Eden区,如果Eden区没有足够的空间,就会触发一次Minor GC,然后把存活的对象放到Survivor0,
然后把Eden清空。下一次触发Minor GC的时候,S0和Eden中存活的对象被复制到S1中,对S0和Eden清空。
2.如果对象不能在Eden区创建,它会直接在老年代中创建,如果老年代空间不够,会促发Full GC(Full GC包括新生代,年老代和持久代).
3.大对象直接进入老年代(大对象指需要大量连续内存空间的对象),这样是为了避免Eden区和Survivor区之间大量的内存拷贝。
4.虚拟机为每个对象创建了一个年龄计数器,每经过一次Minor GC,计数器1会加1,如果到达阈值,就会进入老年代。
5.空间分配担保,每次Minor GC时,JVM会计算Survivor区移动到老年代的对象总大小,如果大于老年代的剩余空间,就会促发Full GC。
浅复制和深复制:https://blog.csdn.net/crpxnmmafq/article/details/71211377
浅复制:对值类型的成员变量进行值的复制,对引用类型的成员变量只复制引用,不复制引用的对象。
深复制:对值类型的成员变量进行值的复制,对引用类型的成员变量进行引用对象的复制。
深复制:
class Obj implements Cloneable{
private Date birth = new Date();
public Object clone(){
Obj o = null;
o = (Obj)super.clone(); //浅复制
o.birth = (Date)this.getBirth().clone(); //深复制
return o;
}
}
对象创建方式:
new
clone(被克隆的类要实现Cloneable接口,重写clone方法)、
反射,Class.forName()
反序列化
内存泄漏:不在被程序使用的对象或变量还在内存中占有存储空间
内存溢出:程序无法申请到足够的内存
JVM参数
-Xmx:JVM最大可用内存
-Xms:JVM初始内存(最小可用内存)
-Xmn:年轻代大小
-Xss:每个线程的栈大小
-XX:NewSize:年轻代初始值
-XX:MaxNewSize:年轻代最大值
-XX:PermSize:永久代初始值
-XX:MaxPermSize:永久代最大值
-XX:SurvivorRatio:年轻代中Eden区与Survivor区的大小比值
-XX:PretenureSizeThreshold:大于这个值的对象直接在老年代分配
(设置-Xmx与-Xms相同,可以避免每次垃圾回收后重新对JVM分配内存)
hashCode方法是Object类继承过来的,返回的是对象在内存中的地址转换成的int值
如果对象没有重写hashCode方法,任何对象的hashCode方法的返回值都是不想等的
重写equals方法的同时,必须重写hashCode方法:
如果重写了equals方法,但没有重写hashCode方法,两个相同内容的对象就会同时存在于集合,比如set,这与set集合的性质不同
尽量使用对象的同一属性来生成hashCode和equals两个方法
Throwable是所有异常的父类
Throwable
Error:运行期间发生非常严重的、不可恢复的错误,如OutOfMemoryError
Exception
Checked Exception:编译阶段,编译器强制要求捕获,如IOException
RuntimeException:运行阶段抛出,如空指针
捕获异常时,先捕获子类异常,再捕获父类异常。
多态:同一个操作作用在不同对象时,会产生不同结果
编译时多态:方法重载,同一个类有多个同名的方法
运行时多态:方法重写,子类重写父类中的方法
重载只能通过方法参数(类型,个数,顺序)来区分,不能通过访问权限、返回类型、抛出的异常进行重载。
子类不能覆盖父类的static或private方法
public class ExtendsStatic extends Parent{
public static void p() {
System.out.println("son...");
}
public static void main(String[] args) {
Parent obj = new ExtendsStatic();
obj.p();
}
}
class Parent{
public static void p() {
System.out.println("parent...");
}
}
输入parent..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值