zoukankan      html  css  js  c++  java
  • 聊聊基础01

    摘要:最近和女友聊天,说我的工作需要作出调整,当前状态下压力太大,急需通过提供自身的专业技能来作出改变,所以便有了这个基础知识的整理。本来这个帖子是发布在简书的,因为考虑到简书比较好编辑和阅览,但是当我发布到简书后,女友竟然惊讶和肯定我终于开始写博客了,于是比较汗颜,就还是回归发布到这里来吧。后续针对这些基础知识做更深次,更全面的研究,本基础的一些问题来源:http://blog.csdn.net/exceptional_derek/article/details/69525715

    一:基础类

     1.hashmap的基本原理,内部数据结构,put操作的整体流程,是否线程安全以及为什么?jdk8对hashmap做了哪些优化?

        答:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变,HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。对HashMap的操作不是线程安全的,通过观察源码发现,当多个线程在某一个时刻同时对HashMap做结构性的修改,我们可以看到整个方法实现中没有任何的同步机制,那么存在一个线程获取或者修改数据结构时,存在另外一个线程获取了一个错误的结果。jdk8对hashMap的数据结构的改变有个调整,当数组达到一定的阈值时,bucket就会由链表转换为红黑树的方式进行存储,而不是进行table的扩容。

    2.String类为什么是不可变的?StringBuilder和StringBuffer的区别,字符串常量池,StringBuffer为什么是线程安全?加号的底层原理?

    答:首先String类是用final关键字修饰,这说明String不可继承,String类的成员字段value是个char[]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。不可变的好处:1.1.参考java字符串池的设计模式。比如两个字符串值相等的变量,他们只会生成一个对象放到常量池中,然后两个变量都指向它,提升效率。1.2.安全性,如果String类可以被修改,那么在多线程的情况下会造成安全漏洞。2.1 StringBuilder和StringBuffer的区别:他们都是创建字符串的常用类,长度都是可以扩充的,实现了CharSequence接口。StringBuilder非线程安全,StringBuffer线程安全,所以通常在单线程环境下可以考虑是用StringBuilder来提升速度和效率,而在多线程的环境下则应该使用SringBuffer来保证线程安全。

    3.反射、accessible,动态代理的原理,jdk动态代理与cglib的区别与各自的实现原理?

    答:反射的机制是在编译时并不确定的哪个类被jvm加载,在程序运行的时候才加载、探知、自审。动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。两者区别:jdk的代理是利用反射生成字节码,并生成对象,前提是只能代理实现了接口的类,cglib是直接修改目标类的字节码生成对象,因为原理是继承,所以不能对final修饰的类进行代理。http://rejoy.iteye.com/blog/1627405https://my.oschina.net/tearsky/blog/635321

    4.自动装箱,赋值操作,在内存里面是如何实现的?

    答:自动装箱是将内置类型转换为对应的包装类型,在自动装箱的过程中,程序会创建一个包装类型的对象,然后将该变量指向这个新创建的对象,完成装箱操作。

    5.接口和抽象类的区别

    答:

    1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

    2、抽象类要被子类继承,接口要被类实现。

    3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。

    4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

    5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

    6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果。

    7、抽象类里可以没有抽象方法。

    8、如果一个类里有抽象方法,那么这个类只能是抽象类。

    9、抽象方法要被实现,所以不能是静态的,也不能是私有的。

    10、接口可继承接口,并可多继承接口,但类只能单根继承。

    6.concurrenthashmap的原理,内部数据结构,如何提高并发性,存在全锁吗?jdk8中做了哪些优化。

    答:ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

    改进一:取消segments字段,直接采用transient volatile HashEntry[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

    改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,查看源码应该是16,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。当获取modelcount时,会全锁来统计个数。

    详细讲解:http://www.importnew.com/22007.html

    http://nanguocoffee.iteye.com/blog/907824

    7.hashset的原理?

    答:HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。

    8.GC原理,分代机制,可达性分析?

    答:对传统的、基本的GC实现来说,由于它们在GC的整个工作过程中都要“stop-the-world”,如果能想办法缩短GC一次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长,那不如只收集其中的一部分?于是就有好几种不同的划分(partition)GC堆的方式来实现部分收集,而分代式GC就是这其中的一个思路。

    9.JVM参数有哪几种,如何调优?

    -Xmx2g //JVM最大允许分配的堆内存,按需分配

    -Xms2g //JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存。-Xmn256m //年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代年轻代分三个区, 分别是enden区和两个survivor区。

    10.JMM特性有哪些?

    1.可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。

    2.有序性:这个概念是相对而言的,如果在本线程内,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的,前句是“线程内表现为串行行为”,后句是“指令的重排序”和“工作内存和主内存同步延迟”现象,模型提供了volatile和synchronized来保证线程之间操作的有序性。

    3.重排序:在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器、处理器),就是因为这些重排序,所以可能会导致多线程程序出现内存可见性问题(数据安全问题)和有序性问题。可见性、原子性、有序性.

    11、什么是跳表?

    12.static关键字的作用?

    答:1.被static修饰的变量属于类变量,可以通过类名.变量名直接引用,而不需要new出一个类来。

    2.被static修饰的方法属于类方法,可以通过类名.方法名直接引用,而不需要new出一个类来。

    3.静态块也是static的重要应用之一。也是用于初始化一个类的时候做操作用的,和静态变量、静态方法一样,静态块里面的代码只执行一次,且只在初始化类的时候执行。

    这个用得相对比前面的用法少多了,static一般情况下来说是不可以修饰类的, 如果static要修饰一个类,说明这个类是一个静态内部类(注意static只能修饰一个内部类),也就是匿名内部类。

    4.import static是JDK1.5之后的新特性,这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名.资源名,可以直接使用资源名。

    13.synchronized锁普通方法和锁静态方法?

    1.对象锁钥匙只能有一把才能互斥,才能保证共享变量的唯一性
    2.在静态方法上的锁,和 实例方法上的锁,默认不是同样的,如果同步需要制定两把锁一样。
    3.关于同一个类的方法上的锁,来自于调用该方法的对象,如果调用该方法的对象是相同的,那么锁必然相同,否则就不相同。比如 new A().x() 和 newA().x(),对象不同,锁不同,如果A的单利的,就能互斥。
    4.静态方法加锁,能和所有其他静态方法加锁的 进行互斥。
    5.静态方法加锁,和xx.class 锁效果一样,直接属于类的。

    二、多线程

    1、线程有几种状态?之间是如何切换的?

    答:新建状态、就绪状态、运行状态、阻塞状态死亡状态。主要是通过获取锁标记来获取对该资源的使用权限,当对象调用了start()进入到就绪状态,进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;接下来的状态切换就会比较复杂,主要通过线程调用不同的方法,就会切换不同的运行状态。

    2、volatile的作用(两点),volatile的原理与应用场景。

    答:volatile让变量每次在使用的时候,都从主存中取。(1.将当前处理器缓存行的数据会写回到系统内存,2.这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。)而不是从各个线程的“工作内存”。volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

    3、线程安全是什么?如何做到线程安全?怎么判断一个类是不是线程安全?

    答:类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

    4、线程同步有几种方式?为何要使用同步?

    答: java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。

    同步的实现方式总共分为七种:

    1.同步方法 : 即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    2.同步代码块:即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    3.使用特殊域变量(volatile)实现线程同步 

    a.volatile关键字为域变量的访问提供了一种免锁机制。

    b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。

     c.因此每次使用该域就要重新计算,而不是使用寄存器中的值。

     d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

     4.使用重入锁实现线程同步.

    java.util.concurrent包下的ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力.

    5.使用局部变量实现线程同步,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响

    6.使用阻塞队列实现线程同步,前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。使用LinkedBlockingQueue来实现线程的同步, LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。队列是先进先出的顺序(FIFO)。

    7.使用原子变量实现线程同步,需要使用线程同步的根本原因在于对普通变量的操作不是原子的。原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

    5、threadlocal的原理

    答:ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量。当线程中的threadlocalmap是null的时候,会调用createmap创建一个map。同时根据函数参数设置上初始值。也就是说,当前线程的threadlocalmap是在第一次调用set的时候创建map并且设置上相应的值的。在ThreadLocal的set函数中,可以看到,其中的map.set(this, value);把当前的threadlocal传入到map中作为键,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。

    6、synchronized是如何实现的?

    答:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

    2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

    3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

    对于方法的同步,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

    7、sleep和wait的区别?

    答:sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

    8、线程池有几种?各自的应用场景。

    答:1.newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

    2.newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是:

    1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

    2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

    3.newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

    4.newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

    总结: 

    一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

    二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

    9、线程池的原理,主要有几个参数?线程池满了怎么办?

    答:一个线程主要包括以下4个部分:

    1.线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
    2.工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
    3.任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
    4.任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

    ThreadPoolExecutor类可设置的参数主要有:
    corePoolSize
    核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。
    核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。
    maxPoolSize
    当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
    keepAliveTime
    当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
    allowCoreThreadTimeout
    是否允许核心线程空闲退出,默认值为false。
    queueCapacity
    任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

    10、Semaphore、futureTask?

    答:Semaphore【ˈseməfɔ:(r)】就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

    FutureTask 表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    11、submit和execute的区别。

    答:execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口

    submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口。

    12、Future接口的几个主要方法

    答:接口逻辑图

    cancel方法主要是是否可中断来设置state及中断状态。

    get()方法主要是调用awaitdone方法来阻塞来等待结果,然后根据最终状态来返回结果或者抛出异常。

    isDone()任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true 

    isCancelled () 任务是否已经取消,任务正常完成前将其取消,则返回 true 

    13、创建线程有几种方式

    答:1.继承Thread类创建线程类

    2.通过Runnable接口创建线程类

    3.通过Callable和Future创建线程

    14、可重入锁是如何实现的

    答:http://blog.jobbole.com/108571/

    和线程相关的更多问题可以移步这里:http://www.jianshu.com/p/64dae9b00eed

    三、数据库

    1、MySQL索引原理?为什么是B+树?有什么优点?

    MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是一种数据结构。 数据库查询是数据库的主要功能之一,最基本的查询算法是顺序查找(linear search)时间复杂度为O(n),显然在数据量很大时效率很低。优化的查找算法如二分查找(binary search)、二叉树查找(binary tree search)等,虽然查找效率提高了。但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织)。所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构。这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。

    一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

    从使用磁盘I/O次数评价索引结构的优劣性:根据B-Tree的定义,可知检索一次最多需要访问h个结点。数据库系统的设计者巧妙的利用了磁盘预读原理,将一个结点的大小设为等于一个页面,这样每个结点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:每次新建结点时,直接申请一个页面的空间,这样可以保证一个结点的大小等于一个页面,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。B-Tree中一次检索最多需要h-1次I/O(根结点常驻内存),渐进复杂度为O(h)=O(logdN)。一般实际应用中,出读d是非常大的数字,通常超过100,因此h非常小。综上所述,用B-Tree作为索引结构效率是非常高的。而红黑树结构,h明显要深得多。由于逻辑上很近的结点(父子结点)物理上可能离得很远,无法利用局部性原理。所以即使红黑树的I/O渐进复杂度也为O(h),但是查找效率明显比B-Tree差得多。B+Tree更适合外存索引,是和内结点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于结点内key和data的大小:dmax=floor(pagesize/(keysize+datasize+pointsize))。floor表示向下取整。由于B+Tree内结点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

    参考:http://www.cnblogs.com/tgycoder/p/5410057.html

    2、事务隔离级别有哪几种?mysql默认的隔离级别是?脏读、幻读、不可重复读是什么情况?

    答:事物隔离级别由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable。读取未提交的数据称之为脏读,一个事务只能看见已经提交事务所做的改变称之为不可重复读,指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行,称之为幻读。

    下图列的是各个事物隔离级别下可能出现的脏读、不可重复读、幻读图:

    Read Uncommitted(读取未提交内容)
    在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
    Read Committed(读取提交内容)
    这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
    Repeatable Read(可重读)
    这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
    Serializable(可串行化)
    这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

     

    参考:http://xm-king.iteye.com/blog/770721

    3、MVCC原理

    答:MVCC全称是Multi-Version Concurrent Control,即多版本并发控制。在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。在进行写操作时,将数据copy一份,不会影响原有数据,然后进行修改,修改完成后原子替换掉旧的数据,而读操作只会读取原有数据。通过这种方式实现写操作不会阻塞读操作,从而优化读效率。而写操作之间是要互斥的,并且每次写操作都会有一次copy,所以只适合读大于写的情况。

    优势:使用MVCC多版本并发控制比锁定模型的主要优点是在MVCC里, 对检索(读)数据的锁要求与写数据的锁要求不冲突, 所以读不会阻塞写,而写也从不阻塞读。

    4、mysql有哪几种锁?

    答:共享读锁,独占写锁。根据数据引擎的不同,锁的类型也不一样,对于innodb

    5、mysql的存储引擎有哪几种?区别和各自的适用场景。

    6、query cache的配置

    7、ACID

    原子性、一致性、隔离性、持久性。

    8、如何优化慢查询

    答:1.查询条件带上索引,

    9、最左前缀匹配原则,原理

    四、算法

    1、一致性哈希的原理

    答:一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似。一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用。 

    一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:

    1、平衡性(Balance):平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
    2、单调性(Monotonicity):单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
    3、分散性(Spread):在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
    4、负载(Load):负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同 的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

    在分布式集群中,对机器的添加删除,或者机器故障后自动脱离集群这些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,很多原有的数据就无法找到了,这样严重的违反了单调性原则。接下来主要讲解一下一致性哈希算法是如何设计的:

    环形Hash空间
    按照常用的hash算法来将对应的key哈希到一个具有2^32次方个桶的空间中,即0~(2^32)-1的数字空间中。现在我们可以将这些数字头尾相连,想象成一个闭合的环形。如下图



    将数据通过hash算法处理后映射到环上

    现在我们将object1、object2、object3、object4四个对象通过特定的Hash函数计算出对应的key值,然后散列到Hash环上。如下图:
    Hash(object1) = key1;
    Hash(object2) = key2;
    Hash(object3) = key3;
    Hash(object4) = key4;


    将机器通过hash算法处理后映射到环上
    在采用一致性哈希算法的分布式集群中将新的机器加入,其原理是通过使用与对象存储一样的Hash算法将机器也映射到环中(一般情况下对机器的hash计算是采用机器的IP或者机器唯一的别名作为输入值),然后以顺时针的方向计算,将所有对象存储到离自己最近的机器中。
    假设现在有NODE1,NODE2,NODE3三台机器,通过Hash算法得到对应的KEY值,映射到环中,其示意图如下:
    Hash(NODE1) = KEY1;
    Hash(NODE2) = KEY2;
    Hash(NODE3) = KEY3;

     

    通过上图可以看出对象与机器处于同一哈希空间中,这样按顺时针转动object1存储到了NODE1中,object3存储到了NODE2中,object2、object4存储到了NODE3中。在这样的部署环境中,hash环是不会变更的,因此,通过算出对象的hash值就能快速的定位到对应的机器中,这样就能找到对象真正的存储位置了。

    机器的删除与添加
    普通hash求余算法最为不妥的地方就是在有机器的添加或者删除之后会造成大量的对象存储位置失效,这样就大大的不满足单调性了,下面来分析一下一致性哈希算法是如何处理的。
    1. 节点(机器)的删除
    以上面的分布为例,如果NODE2出现故障被删除了,那么按照顺时针迁移的方法,object3将会被迁移到NODE3中,这样仅仅是object3的映射位置发生了变化,其它的对象没有任何的改动。如下图:

    2. 节点(机器)的添加
    如果往集群中添加一个新的节点NODE4,通过对应的哈希算法得到KEY4,并映射到环中,如下图:

    通过按顺时针迁移的规则,那么object2被迁移到了NODE4中,其它对象还保持这原有的存储位置。通过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,还是数据的迁移达到了最小,这样的算法对分布式集群来说是非常合适的,避免了大量数据迁移,减小了服务器的的压力。

    平衡性
    根据上面的图解分析,一致性哈希算法满足了单调性和负载均衡的特性以及一般hash算法的分散性,但这还并不能当做其被广泛应用的原由,因为还缺少了平衡性。下面将分析一致性哈希算法是如何满足平衡性的。hash算法是不保证平衡的,如上面只部署了NODE1和NODE3的情况(NODE2被删除的图),object1存储到了NODE1中,而object2、object3、object4都存储到了NODE3中,这样就照成了非常不平衡的状态。在一致性哈希算法中,为了尽可能的满足平衡性,其引入了虚拟节点。
    ——“虚拟节点”( virtual node )是实际节点(机器)在 hash 空间的复制品( replica ),一实际个节点(机器)对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以hash值排列。
    以上面只部署了NODE1和NODE3的情况(NODE2被删除的图)为例,之前的对象在机器上的分布很不均衡,现在我们以2个副本(复制个数)为例,这样整个hash环中就存在了4个虚拟节点,最后对象映射的关系图如下:

    根据上图可知对象的映射关系:object1->NODE1-1,object2->NODE1-2,object3->NODE3-2,object4->NODE3-1。通过虚拟节点的引入,对象的分布就比较均衡了。那么在实际操作中,正真的对象查询是如何工作的呢?对象从hash到虚拟节点到实际节点的转换如下图:



    “虚拟节点”的hash计算可以采用对应节点的IP地址加数字后缀的方式。例如假设NODE1的IP地址为192.168.1.100。引入“虚拟节点”前,计算 cache A 的 hash 值:
    Hash(“192.168.1.100”);
    引入“虚拟节点”后,计算“虚拟节”点NODE1-1和NODE1-2的hash值:
    Hash(“192.168.1.100#1”); // NODE1-1
    Hash(“192.168.1.100#2”); // NODE1-2

    转自:http://blog.csdn.net/cywosp/article/details/23397179

    2、手写二分查找,快速排序

    答:

    3、手写LRU算法

    4、两个链表找交点

    5、两个无限长的数字求和

    6、手写生产者消费者demo

    7、256M内存排序2G大小的文件

    8、求数组最大子序列

    9、1*2*3*4***50,一共有多少个0?

    答:0是5跟偶数相乘得到的,所以有几个0就看有几个5 50内5的倍数有10个,所以能得到10个0.但是25的倍数里有两个5,还要再加两个 总共是12个。

    五、操作系统与计算机网络

    1、如何从访问日志中找出量最大的10个ip?awk语句了解吗?

    2、jstack,jstat,jmap,jheap命令了解吗,如何使用?

    3、系统负载情况如何查看?

    4、网络分层协议了解吗?

    5、tcp三次握手,四次挥手了解吗?

    6、aio,bio,nio的区别

    7、select,poll,epoll的区别?

    8、io模型有哪些?

    六、开源框架与组件

    这部分主要根据简历以及项目的实际情况来问。

    1、对spring了解吗?ioc,aop,transaction注解

    2、spingmvc了解吗?

    3、Redis与memcache的区别

    4、redis持久化策略,rdb与aof的区别与应用场景

    5、memcached的内存是如何分配的?一致性哈希原理

    6、mq的原理与应用场景,mq是如何保证不丢消息的?

    7、tomcat的原理,主要运用了哪些设计模式?

    8、redis与memcached内存分别是如何回收的?

    9、guava的缓存是怎么实现的?

    10、Spring AOP 原理

    AOP的实现原理就是动态的生成代理类,代理类的执行过程为:执行我们增加的代码(例如方法日志记录)—-> 回调原方法 ——> 增加的代码逻辑。
    Spring AOP 动态代理可能采用JDK动态代理或CGlib动态生成代理类两种方式中的一种,决定用哪一种方式的判断标准就是被切面的类是否有其实现的接口,如果有对应的接口,则采用JDK动态代理,否则采用CGlib字节码生成机制动态代理方式。代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。代理类和委托类实现相同的接口,所以调用者调用代理类和调用委托类几乎感觉不到差别。动态代理的意思是运行时动态生成代理实现类,由于JVM的机制,需要直接操作字节码,生成新的字节码文件,也就是.class 文件。
    JDK动态代理
    JDK动态代理模式采用sun的ProxyGenerator的字节码框架。要说明的是,只有实现了接口的类才能使用 JDK 动态代理技术,实现起来也比较简单。
    1. 只要实现 InvocationHandler 接口,并覆写 invoke方法即可。Proxy.newProxyInstance方法用于动态生成实际生成的代理类,三个参数依次为被代理类的类加载器、被代理类所实现的接口和当前代理拦截器。
    覆写的 invoke 中可以加入我们增加的业务逻辑,然后回调原方法。
    jdkProxy.bind 会生成一个实际的代理类,这个生成过程是利用的字节码生成技术,生成的代理类实现了IWorker 接口,我们调用这个代理类的 dowork 方法的时候,实际在代理类中是调用了 JdkProxy (也就是我们实现的这个代理拦截器)的 invoke 方法,接着执行我们实现的 invoke 方法,也就执行了我们加入的逻辑,从而实现了切面编程的需求。
    我们把动态生成的代理类字节码文件反编译一下,也就明白了。
    CGLIB动态代理
    CGlib库使用了ASM这一个轻量但高性能的字节码操作框架来转化字节码,它可以在运行时基于一个类动态生成它的子类。不管有没有接口,凡是类都可以被继承,拥有这样的特点,原则上来说,它可以对任何类进行代码拦截,从而达到切面编程的目的。
    CGlib 不需要我们非常了解字节码文件(.class 文件)的格式,通过简单的 API 即可实现字节码操作。
    基于这样的特点,CGlib 被广泛用于如 Spring AOP 等基于 代理模式的AOP框架中。
    CGlib不支持final类,CGlib 的执行速度比较快,但是创建速度比较慢,所以如果两种动态代理都适用的场景下,有大量动态代理类创建的场景下,用 JDK 动态代理模式,否则可以用 CGlib 。

    jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。

    七、场景设计与架构

    1、秒杀场景,如何做技术选型?

    2、设计一个支持高并发的服务,写出核心代码

    3、高并发与高可用如何实现?

    4、服务降级怎么做?限流、限速、超时重试、熔断、自恢复、分别如何实现?

    5、什么是微服务?有什么好处?为什么要这么做?

    6、CAP理论是什么?项目中的哪些场景用到了CAP理论?

    7、BASE理论是什么?

    8、什么时候应该使用mq?

    八、其他

    1、平时都通过什么方式学习技术?

    2、最近学的一个知识点是什么?

    3、对带人有什么经验?

    4、最熟悉的一个项目是什么?

    5、跳槽的时候,你最看重什么?

    6、为什么跳槽?为什么选择我们公司?

  • 相关阅读:
    485串口接线
    mvc3 升级mvc5
    VB连接ACCESS数据库,使用 LIKE 通配符问题
    VB6 读写西门子PLC
    可用的 .net core 支持 RSA 私钥加密工具类
    解决 Win7 远程桌面 已停止工作的问题
    解决 WinForm 重写 CreateParams 隐藏窗口以后的显示问题
    解决安装 .net framework 发生 extracting files error 问题
    CentOS7 安装配置笔记
    通过特殊处理 Resize 事件解决 WinForm 加载时闪烁问题的一个方法
  • 原文地址:https://www.cnblogs.com/_popc/p/7228149.html
Copyright © 2011-2022 走看看