java工程师求职面试准备(2)

一、Java相关

Arraylist与LinkedList默认空间是多少;

ArrayList的构造方法的源码:

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
this(10);

    }

默认空间是10;

 private transient Entry<E> header = new Entry<E>(null, null, null);

 private transient int size = 0;

默认空间是0;

Arraylist与LinkedList区别与各自的优势;

1.ArrayList是实现基于动态数组的数据结构,LinkedList基于链表的数据结构

2.对于随机访问的get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针

3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动元素

public void add(int index, E element) {
if (index > size || index < 0)
    throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;

    }


List 和 Map 区别;

List是有顺序的 可重复的

Map是通过键值对进行取值的    key和value是一一对应的

这张图简单揭示了Set、List与Map之间的相对关系。 
需要说明下的是,图中的实现并不指这么简单的实现,这个稍后会说到。

heap 和 stack 的区别 

stack: 先进后出 
函数调用栈,有结构,查询快,线程独占的,存储引用和基本类型

heap: 
先进先出,没有结构,查询慢,线程共享的,存储数值,等待垃圾回收

谈谈HashMap,哈希表解决hash冲突的方法;

hashCode的实现源码:

public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
}

为什么要重写hashcode()和equals()以及他们之间的区别与关系;

情景一:两对象根据算法的实现要求本来哈希值应该相等,但是因调用父类的hashcode方法,也许就不等了,这与原理就相违背了;

情景二,例如在HashMap中,根据indexFor(hash, table.length)可以直接将Entry定位到buckt的具体指针处。

通常想查找一个集合中是否包含某个对象,就是逐一取出每个元素与要查找的元素进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则返回否定的信息,如果一个集合中有很多元素譬如成千上万的元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出成千上万个元素进行逐一比较才能得到结论,于是,有人就发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域.

hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

hashCode方法的作用:

对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在java中也一样,hashCode方法的主要内容是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

equals也可以进行比较,但如果业务场景中数据比较大,则会影响性能。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashCode的值,实际上在HashMap的具体实现中会用到一个table保存已经存进去的对象的hashCode的值,如果table中没有该hashCode的值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashCode值,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列到其它地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确实是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里看,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

不能直接根据hashCode值判断两个对象是否相等,因为不同的对象可能会生成相同的hashCode值。虽然不能根据hashCode值判断两个对象是否相等,但是可以直接根据hashCode值判断两个对象不等,则必定是两个不同的对象。如果要判断两个对象是否相等,必须通过equals方法。

也就是说两个对象如果调用equals方法得到的结果为true,则两个对象的hashCode值必定相等;

如果equals方法得到的结果为false,则两个对象的hashCode值不一定不同;

如果两个对象的hashCode值不等,则equals方法得到的结果必定为false;

如果两个对象的hashCode值不等,则equals方法得到的结果未知。

在HashMap进行get操作时,因为得到的hashCode值不同(虽然两个对象存储地址不同也有可能得到相同的hashCode的值),所以导致在get方法中for循环不会执行,直接返回null。

在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。

如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。

如果两个对象根据equals方法比较是不等的,则hashCode方法不一定返回不同的整数。

在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段

hashCode是jdk中根据对象的地址算出来的一个int数字,即对象的哈希码值,代表了该对象在内存中的存储位置。hashCode()方法是顶级类Object类的提供一个方法,所有的类都可以进行对hashCode方法重写。在比较一个类是否相同时,往往会重写equals方法,值得注意的是,重写equals方法的同时也要重写hashCode方法,多次调用一个对象的hashCode方法必须返回一个数字,这也是必须遵守的一个规范。

hash冲突:

当两个对象equals相同,hashCode规定也必须相同,但反过来不一定,两个对象对应一个hashCode,但equals却并不相等。这就是传说中的hash冲突。HashMap是以hashCode取模数组形式存放值的,那两个对象hashCode一样会不会造成前一个对象的覆盖呢?答案是不会的,因为它采用了另外一种链表数据结构来解决hash冲突,即使两个对象的hashCode一样,它们会放到当前数组索引位置的链表中。

HashSet通过HashMap来实现的,用来存储不重复数据的,怎么判断里面的对象是否重复呢?判断对象是否重复即是判断对象里面的属性是否都一样,这时必须是重写了equals方法去比较对象里所有的值,而不是比较引用地址,比较引用地址它们永远都不相等,除非是同一个对象。通过equals比较的过程性能是非常不佳的,所以有了hashCode这个设计,简单两个数字的比较性equals没法比的,所以可以先通过比较对象的hashCode是否一样确定是不同一个对象,如果hashCode不一样这时肯定就不是同一对象,反之如果hashCode一样而且equals或者==也一样这肯定就是同一对象。所以先比较数字的hashCode再比较equals或者==,这样效率会明显提升。

假如重写了equals而不重写hashCode方法,多个对象属性值一样的它们的hashCode肯定是不一样的,这时作为key在put到map中的时候,就会有多个这样的key,而达不到对象作为key的场景,同样也达不到HashSet去重的效果。

Object的hashcode()是怎么计算的?



若hashcode方法永远返回1或者一个常量会产生什么结果?

https://blog.csdn.net/cnq2328/article/details/50436175

Java Collections和Arrays的sort方法默认的排序方法是什么;

https://blog.csdn.net/timheath/article/details/68930482

引用计数法与GC Root可达性分析法区别;

http://baijiahao.baidu.com/s?id=1583441733083989684&wfr=spider&for=pc

浅拷贝和深拷贝的区别;

https://blog.csdn.net/wangxueming/article/details/52034841

String s="abc"和String s=new String("abc")区别;

https://blog.csdn.net/u010644448/article/details/51980370

HashSet方法里面的hashcode存在哪,如果重写equals不重写hashcode会怎么样?

https://blog.csdn.net/gewuban/article/details/52484356

HashSet和TreeSet的区别;

HashSet是按照哈希来存取元素的,因此速度较快。HashSet继承自抽象类AbstractSet,然后实现了Set、Cloneable、Serializable接口。 
TreeSet也是继承自AbstractSet,不过不同的是其实现的是NavigableSet接口。而NavigableSet继承自SortedSet。SortedSet是一个有序的集合。其添加的元素必须实现了Comparable接口,因为其在添加一个元素的时候需要进行排序。NavigableSet则提供了更多的有关元素次序的方法,

反射的作用与实现原理;

https://blog.csdn.net/WildGrasses/article/details/70243342

https://blog.csdn.net/winy_lm/article/details/49470355

Java中的回调机制;

https://www.cnblogs.com/xrq730/p/6424471.html

https://blog.csdn.net/caihongdao123/article/details/51657840

模板方法模式;

https://blog.csdn.net/eson_15/article/details/51323902

开闭原则说一下;

https://blog.csdn.net/hfreeman2008/article/details/52344022

发布/订阅使用场景;

redis的发布订阅场景

https://blog.csdn.net/fly910905/article/details/78495971

KMP算法(一种改进的字符串匹配算法);

JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思,

运行异常和一般异常的区别?

Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形.应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出).假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的.

  Exception:表示可恢复的例外,这是可捕捉到的。

  Java提供了两类主要的异常:runtime exceptionchecked exceptionchecked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误.

但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等.处理RuntimeException的原则是:假如出现RuntimeException,那么一定是程序员的错误.例如,可以通过检查数组下标和数组边界来避免数组越界访问异常.

出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。

如果不想终止,则必须扑捉所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。在这个场景这样处理可能是一个比较好的应用,但并不代表在所有的场景你都应该如此。如果在其它场景,遇到了一些错误,如果退出程序比较好,这时你就可以不太理会运行时异常,或者是通过对异常的处理显式的控制程序退出。异常处理的目标之一就是为了把程序从异常中恢复出来


二、多线程

AtomicInteger底层实现原理;

CAS原理和自旋锁原理

synchronized与ReentraLock哪个是公平锁;

ReentraLock可以设置为公平锁

CAS机制会出现什么问题;

ABA问题

用过并发包下边的哪些类;

CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用途:
将计数器1初始化的CountDownLatch用作一个简单的开/关锁存器,或入口:在通过调用countDown()的线程打开入口前,所有调用await的线程都等在入口处。
用N初始化的CountDownLatch可以使一个线程在N个线程完成某项操作之前一直等待,或者使其在某项操作完成N次之前一直等待。
不要求调用countDown方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await。

CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到达到某个公共屏障点。在涉及一组固定大小的线程的程序中,这些线程必须不时地相互等待。因为该barrier在释放等待线程后可以重用,所以它成为循环的barrier。
如果屏障操作在执行时不依赖于正挂起的线程,则线程组中的任何线程在获得释放时都能执行该操作。

内存中一致性的效果:线程中调用await()之间的操作,那些是屏障操作的一部分的操作,后者依次happen-before紧跟在从另一个线程中对应await()成功返回。

AbstractQueuedSynchronizer:

https://www.cnblogs.com/longshiyVip/p/5211298.html

http://raychase.iteye.com/blog/1998965

一个线程连着调用start两次会出现什么情况?

出现异常

wait方法能不能被重写,wait能不能被中断;

不能被重写,可以被中断

线程池的实现?四种线程池?重要参数及原理?任务拒接策略有哪几种?

拒绝策略1:将抛出 RejectedExecutionException.

策略2ThreadPoolExecutor.CallerRunsPolicy

用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务

策略3

RejectedExecutionHandler handler = 

new ThreadPoolExecutor.DiscardOldestPolicy();

策略4 ThreadPoolExecutor.DiscardPolicy
用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
线程状态以及API怎么操作会发生这种转换;

常用的避免死锁方法;

1、避免一个线程同时获取多个锁 
2、避免一个线程同时占用多个资源,尽量保证每个锁只占用一个资源 
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制 
4、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

什么是乐观锁和悲观锁;

乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观苏:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

三、JVM

为什么进行垃圾回收?什么时候触发GC?

垃圾回收(gc)的目的是释放堆中不需要保存的对象,达到内存的充分利用。

1.回收哪些对象的判定
垃圾回收最简单的思路是采用引用计数的方式,即记录对象被引用的次数,直到一段时间内对象都没有被其他对象引用,此时可以确定该对象能被回收,引用计数实现简单,运行高效,但是有一个循环引用的问题,即两个本应被回收的对象因为互相引用而无法被回收,针对这个问题又有了弱引用,即把两个互相引用的一个引用计数改为弱引用,弱引用不会使次数加1,c++即是这么做的。

jvm虚拟机的使用的是根寻路算法,其大致思想是看除堆区以外的内存区域能否通过引用链找到堆中的对象,找不到就证明该对象可以被回收。
2.如何回收
jvm有两种回收方式,一种是标记完待回收的对象之后一起释放内存,这种方法的缺点是会产生较多难以重复利用的内存碎片。另一种为了避免内存碎片的出现,将内存分为两块,一块使用,一块不使用,标记完所有待回收的对象之后,将还要使用的内存复制到不使用的区域,然后对使用的整体区域进行内存回收,这种方法没有内存碎片问题,但是每次回收的复制工作很耗性能。
通过统计发现,在内存中存活越久的对象就越不容易被回收,越是新分配的内存对象就越可能会被回收。根据这个特性,把内存区域分为新生代和老年代(有的虚拟机会分为很多代),新生代容易被回收,采用复制内存再回收的方法,老年代不容易被回收,采用标记后回收和复制内存相结合的方法。
3.什么时候回收

程序员可以手动调用gc,一般是系统等到新生代的内存区占满了又需要分配内存的时候,这个时候新生代就变成了老年代,等老年代的内存占满之后开始回收老年代所占的内存区。

垃圾回收机制的要点:

1.垃圾回收线程是一个守护线程(指在程序运行的时候在后台提供一种通用服务的线程)
2.程序员可以主动申请垃圾回收,使用代码System.gc();但是由于垃圾回收是由JVM负责的,因此请求可能被JVM可以拒绝。
3.垃圾回收器的分类:串行垃圾回收器,并行垃圾回收器,G1垃圾回收器
4.回收机制:分代复制垃圾回收,增量回收
5.作用:防止内存泄漏。
   当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用 情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式 确定哪些对象是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为"不可达" 时,GC 就有责任回收这些内存空间。这可以有效的防止内存溢出。
   内存溢出是指:长生命周期的对象持有短生命周期的对象的引用,这会导致短生命周期的对象的所占堆内存无法被释放。
①比如把一些不怎么使用的对象存放到全局Map中,
②或者创建了内部类对象,而其外部类即使长时间没有使用也不能释放其内存空间。

Minor GC与Full GC分别在什么时候发生?什么时候触发Full GC;

Minor GC发生在新生代中的垃圾收集动作,采用复制算法;

Full GC是发生在老年代垃圾回收动作,采用标记-整理算法;

1system.gc();2.老年代空间不足,3.gc担保不足

GC收集器有哪些?CMS收集器与G1收集器的特点。

Java在什么时候会出现内存泄漏;

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

Java中的大对象如何进行存储;

通过缓存进行存储

https://blog.csdn.net/zeratyl/article/details/79094482

rt.jar被什么类加载器加载,什么时间加载;

自己写的类被什么加载,什么时间加载;

https://blog.csdn.net/lin353809836/article/details/69939400

自己写的两个不同的类是被同一个类加载器加载的吗?为什么?

为什么新生代内存需要有两个Survivor区?


        如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。你也许会问,执行时间长有什么坏处?频发的Full GC消耗的时间是非常可观的,这一点会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。

方案优点缺点
增加老年代空间更多存活对象才能填满老年代。降低Full GC频率随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长
减少老年代空间Full GC所需时间减少老年代很快被存活对象填满,Full GC频率增加

显而易见,没有Survivor的话,上述两种解决方案都不能从根本上解决问题。

我们可以得到第一条结论:Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。

2 为什么要设置两个Survivor区

设置两个Survivor区最大的好处就是解决了碎片化,下面我们来分析一下。

为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。假设现在只有一个survivor区,我们来模拟一下流程: 
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。 
我绘制了一幅图来表明这个过程。其中色块代表对象,白色框分别代表Eden区(大)和Survivor区(小)。Eden区理所当然大一些,否则新建对象很快就导致Eden区满,进而触发Minor GC,有悖于初衷。 



几种常用的内存调试工具:jmap、jstack、jconsole;
类加载的五个过程:加载、验证、准备、解析、初始化;
G1停顿吗,CMS回收步骤,CMS为什么会停顿,停顿时间;

栈主要存的数据是什么,堆呢

栈内存:线程是私有的,也就是说局部变量和方法是不可共享的。

堆内存:对象和数组是在堆内存中创建的,所有线程都可以访问,包括成员变量、静态变量和数组元素是可共享的;

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类 型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。

Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时 动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

堆分为哪几块,比如说新生代老生代,那么新生代又分为什么?

java垃圾收集管理器的主要区域,因此很多时候被称为“GC堆”。

分为新生代和老年代;

新生代分为:Eden和Survivor。

软引用和弱引用的使用场景(软引用可以实现缓存,弱引用可以用来在回调函数中防止内存泄露);

软引用可是实现缓存,弱引用可以用来在回调函数中防止内存泄漏。

四、数据库

数据库索引,什么是全文索引,全文索引中的倒排索引是什么原理;

https://blog.csdn.net/u011066470/article/details/51768314

数据库最佳左前缀原则是什么?

https://blog.csdn.net/SkySuperWL/article/details/52583579

数据库的三大范式;

https://www.cnblogs.com/wujianrui/p/7077864.html

悲观锁和乐观锁的原理和应用场景;

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;

但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.

左连接、右连接、内连接、外连接、交叉连接、笛卡儿积等;
一般情况下数据库宕机了如何进行恢复(什么是Write Ahead Log机制,什么是Double Write机制,什么是Check Point);

什么是redo日志、什么是undo日志;

日志在内存里也是有缓存的,这里将其叫做log buffer。磁盘上的日志文件称为log file。log file一般是追加内容,可以认为是顺序写,顺序写的磁盘IO开销要小于随机写。

Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;Redo日志记录某数据块被修改后的值,可以用来恢复未写入data file的已成功事务更新的数据。下面的示例来自于杨传辉《大数据分布式存储系统 原理解析与架构实践》,略作改动。

例如某一事务的事务序号为T1,其对数据X进行修改,设X的原值是5,修改后的值为15,那么Undo日志为<T1, X, 5>,Redo日志为<T1, X, 15>

也有把undo和redo结合起来的做法,叫做Undo/Redo日志,在这个例子中Undo/Redo日志为<T1, X, 5, 15>

当用户生成一个数据库事务时,undo log buffer会记录被修改的数据的原始值,redo会记录被修改的数据的更新后的值。

redo日志应首先持久化在磁盘上,然后事务的操作结果才写入db buffer,(此时,内存中的数据和data file对应的数据不同,我们认为内存中的数据是脏数据),db buffer再选择合适的时机将数据持久化到data file中。这种顺序可以保证在需要故障恢复时恢复最后的修改操作。先持久化日志的策略叫做Write Ahead Log,即预写日志。

在很多系统中,undo日志并非存到日志文件中,而是存放在数据库内部的一个特殊段中。本文中就把这些存储行为都泛化为undo日志存储到undo log file中。

对于某事务T,在log file的记录中必须开始于事务开始标记(比如“start T”),结束于事务结束标记(比如“end T”、”commit T”)。在系统恢复时,如果在log file中某个事务没有事务结束标记,那么需要对这个事务进行undo操作,如果有事务结束标记,则redo。

在db buffer中的内容写入磁盘数据库文件之前,应当把log buffer的内容写入磁盘日志文件。

有一个问题,redo log buffer和undo log buffer存储的事务数量是多少,是按照什么规则将日志写入log file?如果存储的事务数量都是1个,也就意味着是将日志立即刷入磁盘,那么数据的一致性很好保证。在执行事T时,突然断电,如果未对磁盘上的redo log file发生追加操作,可以把这个事务T看做未成功。如果redo log file被修改,则认为事务是成功了,重启数据库使用redo log恢复数据到db buffer和 data file即可。

如果存储多个的话,其实也挺好解释的。就是db buffer写入data file之前,先把日志写入log file。这种方式可以减少磁盘IO,增加吞吐量。不过,这种方式适用于一致性要求不高的场合。因为如果出现断电等系统故障,log buffer、db buffer中的完成的事务会丢失。以转账为例,如果用户的转账事务在这种情况下丢失了,这意味着在系统恢复后用户需要重新转账。

checkpoint是为了定期将db buffer的内容刷新到data file。当遇到内存不足、db buffer已满等情况时,需要将db buffer中的内容/部分内容(特别是脏数据)转储到data file中。在转储时,会记录checkpoint发生的”时刻“。在故障回复时候,只需要redo/undo最近的一次checkpoint之后的操作。

在日志文件中的操作记录应该具有幂等性。幂等性,就是说同一个操作执行多次和执行一次,结果是一样的。例如,5*1 = 5*1*1*1,所以对5的乘1操作具有幂等性。日志文件在故障恢复中,可能会回放多次(比如第一次回放到一半时系统断电了,不得不再重新回放),如果操作记录不满足幂等性,会造成数据错误。

数据库中的隔离性是怎样实现的;原子性、一致性、持久性又是如何实现的;

        为了实现原子性,需要通过日志:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已经执行成功的操作撤销,从而达到“全部操作失败”的目的。最常见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,可以继续被使用。

        为了保证并发情况下的一致性,引入了隔离性,即保证每一个事务能够看到的数据总是一致的,就好象其它并发事务并不存在一样。用术语来说,就是多个事务并发执行后的状态,和它们串行执行后的状态是等价的。怎样实现隔离性,已经有很多人回答过了,原则上无非是两种类型的锁:

        一种是悲观锁,即当前事务将所有涉及操作的对象加锁,操作完成后释放给其它对象使用。为了尽可能提高性能,发明了各种粒度(数据库级/表级/行级……)/各种性质(共享锁/排他锁/共享意向锁/排他意向锁/共享排他意向锁……)的锁。为了解决死锁问题,又发明了两阶段锁协议/死锁检测等一系列的技术。

        一种是乐观锁,即不同的事务可以同时看到同一对象(一般是数据行)的不同历史版本。如果有两个事务同时修改了同一数据行,那么在较晚的事务提交时进行冲突检测。实现也有两种,一种是通过日志UNDO的方式来获取数据行的历史版本,一种是简单地在内存中保存同一数据行的多个历史版本,通过时间戳来区分。

什么是组合索引,组合索引什么时候会失效;
关系型数据库和非关系型数据库区别;

数据库死锁如何解决;

https://blog.csdn.net/qq_16681169/article/details/74784193

MySQL并发情况下怎么解决(通过事务、隔离级别、锁);

https://blog.csdn.net/zhangliangzi/article/details/51554204

MySQL中的MVCC机制是什么意思,根据具体场景,MVCC是否有问题;

https://blog.csdn.net/sofia1217/article/details/50778906

MySQL数据库的隔离级别,以及如何解决幻读;

 未提交读(READUNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)。

提交读(READCOMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。

可重复读(REPEATABLEREAD)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。

串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥。

read view(或者说 MVCC)实现了一致性不锁定读(Consistent Nonlocking Reads),从而避免了幻读

事务的传播行为  
PROPAGION_XXX :事务的传播行为 
* 保证同一个事务中 
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认) 
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务 
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常 
* 保证没有在同一个事务中 
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务 
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务 
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常 

PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行


五、缓存服务器
Redis中zSet跳跃表问题;
Redis的set的应用场合?
Redis高级特性了解吗?
Redis的pipeline有什么用处?
Redis集群宕机如何处理,怎么样进行数据的迁移;
Redis的集群方案;
Redis原子操作怎么用比较好;
Redis过期策略是怎么实现的呢?
六、SSM相关

Spring中@Autowired和@Resource注解的区别?

https://blog.csdn.net/wangzuojia001/article/details/54312074

Spring声明一个 bean 如何对其进行个性化定制;

https://blog.csdn.net/foreverling/article/details/50878599

MyBatis有什么优势;

https://blog.csdn.net/u014788653/article/details/68489301

MyBatis如何做事务管理;

https://blog.csdn.net/majinggogogo/article/details/72026693

七、操作系统
Linux静态链接和动态链接;
什么是IO多路复用模型(select、poll、epoll);
Linux中的grep管道用处?Linux的常用命令?
操作系统中虚拟地址、逻辑地址、线性地址、物理地址的概念及区别;
内存的页面置换算法;
内存的页面置换算法;
进程调度算法,操作系统是如何调度进程的;
父子进程、孤儿进程、僵死进程等概念;
fork进程时的操作;
kill用法,某个进程杀不掉的原因(僵死进程;进入内核态,忽略kill信号);
系统管理命令(如查看内存使用、网络情况);
find命令、awk使用;
Linux下排查某个死循环的线程;
八、网络相关
数据链路层是做什么的?
数据链路层的流量控制?
网络模型的分层、IP和Mac地址在那个层、TCP和HTTP分别在那个层;
TCP滑动窗口;
TCP为什么可靠;
TCP的同传,拆包与组装包是什么意思;

Https和Http有什么区别;

https://www.cnblogs.com/wqhwe/p/5407468.html

Http 为什么是无状态的;

https://blog.csdn.net/wu1991924/article/details/8548051

TCP三次握手,为什么不是三次,为什么不是四次;

https://blog.csdn.net/to_be_better/article/details/54885684

TCP的拥塞控制、流量控制详细说明?

https://blog.csdn.net/qing101/article/details/48653797

Http1.0和Http2.0的区别;

https://blog.csdn.net/linsongbin1/article/details/54980801

两个不同ip地址的计算机之间如何通信;
地址解析协议ARP;
OSI七层模型分别对应着五层模型的哪一部分;
TCP三次握手数据丢失了怎么办?那如果后面又找到了呢?
九、分布式相关
消息队列使用的场景介绍和作用(应用耦合、异步消息、流量削锋等);
如何解决消息队列丢失消息和重复消费问题;
Kafka使用过吗,什么是幂等性?怎么保证一致性,持久化怎么做,分区partition的理解,LEO是什么意思,如何保证多个partition之间数据一致性的(ISR机制),为什么Kafka可以这么快(基于磁盘的顺序读写);
异步队列怎么实现;
你项目的并发是多少?怎么解决高并发问题?单机情况下Tomcat的并发大概是多少,MySQL的并发大致是多少?
什么是C10K问题;
高并发情况下怎么办;
分布式理论,什么是CAP理论,什么是Base理论,什么是Paxos理论;
分布式协议的选举算法;
说一下你对微服务的理解,与SOA的区别;
Dubbo的基本原理,RPC,支持哪些通信方式,服务的调用过程;
Dubbo如果有一个服务挂掉了怎么办;
分布式事务,操作两个表不在一个库,如何保证一致性。
分布式系统中,每台机器如何产生一个唯一的随机值;
系统的量级、pv、uv等;
什么是Hash一致性算法?分布式缓存的一致性,服务器如何扩容(哈希环);
正向代理、反向代理;
什么是客户端负载均衡策略、什么是服务器端负载均衡策略;
如何优化Tomcat,常见的优化方式有哪些;
Nginx的Master和Worker,Nginx是如何处理请求的;
十、系统设计相关

如何防止表单重复提交(Token令牌环等方式);

https://blog.csdn.net/cor_twi/article/details/48596537

有一个url白名单,需要使用正则表达式进行过滤,但是url量级很大,大概亿级,那么如何优化正则表达式?如何优化亿级的url匹配呢?
常见的Nginx负载均衡策略;已有两台Nginx服务器了,倘若这时候再增加一台服务器,采用什么负载均衡算法比较好?
扫描二维码登录的过程解析;
如何设计一个生成唯一UUID的算法?
实现一个负载均衡的算法,服务器资源分配为70%、20%、10%;

有三个线程T1 T2 T3,如何保证他们按顺序执行;

在后面的线程start()之前调用前面线程的join()方法,进行等待;

三个线程循环输出ABCABCABC....
十一、安全相关
什么是XSS攻击,XSS攻击的一般表现形式有哪些?如何防止XSS攻击;
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值