面试题真的是博大精深,也通过这个面试题学到了很多东西,很多笔者也不是很懂,如有描述错误的地方还望大佬赐教,
每一次面试都可能问到相同的问题,一面问到,二三面还可能会问到,笔者认为这一点是整理这篇面试题收获最大的一点。
目录:
一面
1.1、HashMap和Hashtable的区别
1.2、实现一个保证迭代顺序的HashMap
1.3、 说一说排序算法,稳定性,复杂度
1.4、 说一说GC
1.5、 可以保证的实习时长
1.6、 职业规划
二面
2.1、 自我介绍。
2.2、 JVM如何加载一个类的过程,双亲委派模型中有哪些方法?
2.3、 HashMap如何实现的?
2.4、 HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
2.5、 HashMap和HashTable 区别,HashTable线程安全吗?
2.6、 进程间通信有哪几种方式?
2.7、 JVM分为哪些区,每一个区干吗的?
2.8、 JVM如何GC,新生代,老年代,持久代,都存储哪些东西?
2.9、 GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?
2.10、 快速排序,过程,复杂度?
2.11、 什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤。
2.12、 TCP如何保证可靠传输?三次握手过程?
2.13、 TCP和UDP区别?
2.14、 滑动窗口算法?
2.15、 Linux下如何进行进程调度的?
2.16、 Linux下你常用的命令有哪些?
2.17、 操作系统什么情况下会死锁?
2.18、 常用的hash算法有哪些?
2.19、 什么是一致性哈希?
2.20、 如何理解分布式锁?
2.21、 数据库中的范式有哪些?
2.22、 数据库中的索引的结构?什么情况下适合建索引?
2.23、 Java中的NIO,BIO,AIO分别是什么?
2.24、 用什么工具调试程序?JConsole,用过吗?
2.25、 现在JVM中有一个线程挂起了,如何用工具查出原因?
2.26、 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
2.27、 同步和异步有什么区别?
2.28、 线程池用过吗?
2.29、 如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?
2.30、 concurrent包下面,都用过什么?
2.31、 常用的数据库有哪些?redis用过吗?
2.32、 了解hadoop吗?说说hadoop的组件有哪些?说下mapreduce编程模型。
2.33、 你知道的开源协议有哪些?
2.34、 你知道的开源软件有哪些?
2.35、 你最近在看的书有哪些?
2.36、 你有什么问题要问我吗?
2.37、 了解哪些设计模式?说说都用过哪些设计模式
2.38、 如何判断一个单链表是否有环?
2.39、 操作系统如何进行分页调度?
2.40、 匿名内部类是什么?如何访问在其外面定义的变量?
三面
3.1、 自我介绍,做过什么项目。
3.2、java虚拟机的区域如何划分,
3.3、 双亲委派模型中,从顶层到底层,都是哪些类加载器,分别加载哪些类?
3.4、 有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类?
3.5、 HashMap的结构,get(),put()是如何实现的?
3.6、 ConcurrentHashMap的get(),put(),又是如何实现的?ConcurrentHashMap有哪些问题? ConcurrentHashMap的锁是读锁还是写锁?
3.7、 sleep()和wait()分别是哪个类的方法,有什么区别?synchronized底层如何实现的?用在代码块和方法上有什么区别?
3.8、 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
3.9、 什么是死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息?
3.10、 查看jvm虚拟机里面堆、线程的信息,你用过什么命令?
3.11、 垃圾回收算法有哪些?CMS知道吗?如何工作的?
3.12、 数据库中什么是事务?事务的隔离级别?事务的四个特性?什么是脏读,幻读,不可重复读?
3.13、 数据库索引的结构有哪些? 介绍B+树的结构。
3.14、 数据库中的分页查询语句怎么写?
3.15、 什么是一致性哈希?用来解决什么问题?
3.16、 Redis的存储结构,或者说如何工作的,与mysql的区别?有哪些数据类型?
3.17、 项目中用到redis,为什么选用redis,了解其他NoSQL数据库吗?在你的项目中是如何运用redis的?key是什么,value是什么?
3.18、 归并排序的过程?时间复杂度?空间复杂度?你平常用什么排序?快速排序。说说在那些场景下适用,哪些场景下不适用。
3.19、 Solr是如何工作的?
一面
1.1、HashMap和Hashtable的区别
继承:
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
锁:
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。
方法:
HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。
Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
是否可以为null:
Hashtable中,key和value都不允许出现null值。
HashMap中,null可以作为键,这样的键只有一个。
Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
Tips:
当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
遍历:
Hashtable、HashMap都使用了 Iterator。但是,Hashtable还使用了Enumeration的方式 。
计算Hash值:
HashTable直接使用对象的hashCode。
HashMap的Hash值:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
容量:
HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,
Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
1.2、实现一个保证迭代顺序的HashMap
使用HashMap的一个子类LinkedHashMap(顺序遍历的HashMap)进行研究
放入方法中:
重写了最关键的Node<K, V> newNode(int hash, K key, V value, Node next)方法,该方法首先创建一个HashMap.Node的子类LinkedHashMap.Entry,它比Node多了两个指针Entry<K, V> before, after;//用于保存前后两个节点的信息。
Tips:
LinkedHashMap拥有两个瞬时的属性
transient LinkedHashMap.Entry<K,V> tail;//用于保存上一个元素,即表尾;
transient LinkedHashMap.Entry<K, V> head;//用于保存第一个元素,即表头。
遍历:
使用LinkedEntryIterator
迭代器进行遍历,继承于 abstract class LinkedHashIterator
抽象类。该迭代器拥有两个Entry的指针next
和current
,并结合Entry中的before
和after
指针,实现了LinkedHashMap中元素的顺序遍历。
LinkedHashIterator的nextNode
方法使用了LinkedHashMap.Entry<K, V>的after属性
,使得iterator的遍历按照放入顺序进行的。
取值方法:
LinkedHashMap重写了Map接口的V get(Object key)方法,该方法分两个步骤:
- 调用父类HashMap的getNode(hash(key), key)方法,获取value;
- 如果accessOrder(访问后重排序)为true(默认为false),那么移动所访问的元素到表尾,并修改head和tail的值。
1.3、 说一说排序算法,稳定性,复杂度
这个东西还是面试前把每个排序算法都看一看比较好
1.4、 说一说GC
堆(新生代和老生代)
是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区(永久代)
。
在堆中进行垃圾回收分为新生代和老生代;将新生代分成了三个独立的区域
(这里的独立区域只是一个相对的概念,并不是说分成三个区域以后就不再互相联合工作了),
分别为:Eden区、From Survivor区以及To Survivor,而Eden区分配的内存较大,其他两个区较小,每次使用Eden和其中一块Survivor。
在进行垃圾回收时,将Eden和Survivor中还存活着的对象进行一次性地复制到另一块Survivor空间上,直到其两个区域中对象被回收完成,
当Survivor空间不够用时,需要依赖其他老年代的内存进行分配担保。当另外一块Survivor中没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老生代,大对象和长期存活的对象也会直接进入老年代。
如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。
新生代中的垃圾回收频率高, ,保存在JVM的方法区(永久代)中的对象一般不会被回收。
其永久代进行垃圾回收的频率就较低,速度也较慢。
永久代的垃圾收集主要回收废弃常量和无用类。
判断一个类是否被回收,则需同时满足的条件:
该类所有的实例和ClassLoader都已经被回收。
该类的对象没有被引用,无法通过反射访问,这里说的是可以回收而不是必然回收。
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;
同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full CG。
虚拟机通过对象年龄计数器来判断存放在哪:
如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。
对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15(MaxTenuringThreshold)时,就将会被晋升到老年代中。
如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
Jdk8开始废弃永久代:
This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
(移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。)
由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen
字符串存在永久代中,容易出现性能问题和内存溢出。
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
永久代会为GC带来不必要的复杂度,而且回收效率偏低。
1.5、 可以保证的实习时长
一般都是半年到一年,(太少的话,刚教会了你,你就可能走了,太多的话也不现实)
还有可能问到什么时候去上班,建议的话,就是下个月,或者两周后,今天面试明天上班,肯定准备的不充分。(问这个的话,大多都是有个项目什么的着急要人,然后面试官用你应付,)
1.6、 职业规划
其实这个问题不只是回答面试官,更是回答自己,为什么做,想怎么做……
说明自己对岗位的理解和从事这份工作的原因。
说明自己愿意为这份工作付出努力。
说明自己长远的目标和规划。
下面以产品经理为例子回答:
互联网行业是一个高速发展的行业,同时也有大量创新和尝试的机会(阐述自己看好行业)。
而产品经理则是互联网企业的核心岗位之一,产品经理负责用户需求分析、竞品分析、产品设计和上下层需求的沟通,需要超强的逻辑思考和分析能力、用户洞察能力和沟通协作能力。(阐述自己对岗位的理解)。
而我毕业于XXX大学,在大学里曾参加XXX产品设计比赛,拿下了XXX的成绩,个人非常擅长思考和分析问题,同时能处理好和团队成员的沟通协作…(阐述自己适合这个工作)。
我认为自己非常适合这个岗位,为此,我也愿意付出努力。
在过去,我曾阅读过XXX本产品书籍,自己设计过3款产品的原型,有一款在自己的努力下成功上线,并通过持续获取用户反馈,收获了XXX万的用户。(表达自己过去的努力)
入职以后,我希望能从助理开始,系统学习产品的基本功,在一年的时间里面,成功掌握主流的产品设计方法论(表达自己愿意付出的努力)。
我知道,优秀的产品经理不仅仅需要掌握产品设计的方法,还需要XXXX。
我会努力培养自己的业务思维,站在全局业务的角度去思考和解决问题,为团队做好表率…(表达自己大致的努力方向)。
每个产品经理都有自己的目标,我也一样。
我希望在我的努力之下,在两年以后,能够独挡一面,负责好一个版块的功能;
在三到五年左右,可以负责好一个产品的规划、设计和优化;
在未来的五到八年,可以做好一个产品的全局规划、团队管理等等…
二面
2.1、 自我介绍。
自我介绍在三到五分钟最好
一两句话概括自己名字学校什么的,主学的什么,对什么有研究,了解什么(切忌:尽量别说“精通”),然后说一下以前做过的项目(具体说一些自己做的有技术的),或者什么别的奖项,然后谈一下自己对公司的了解以及对未来的规划,等等。(百度有很多,随便搜搜)
2.2、 JVM如何加载一个类的过程,双亲委派模型中有哪些方法?
类加载过程:
加载
(通过一个类的全限定名获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构,在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区域数据的访问入口)、
验证
(验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害,如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。
1.文件格式验证:
验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理。
2.元数据验证:
是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范。
3.字节码验证:
主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。
4.符号引用验证:
符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。)、
准备
(准备阶段为(static)变量(不包括类的实例)分配内存并设置类变量的初始化,此初始化并不是赋值static int num =1,这时得num为0,并不是1)、
解析
(解析过程是将常量池内的符号引用替换成直接引用(类或接口的解析、字段解析、方法解析、接口方法解析。))、
初始化
(这里才是赋值阶段)
使用过程:新线程—程序计数器----jvm栈执行(对象引用)-----堆内存(直接引用)----方法区。
卸载靠GC
双亲委派模型中方法:
双亲委派是指如果一个类收到了类加载的请求,不会自己先尝试加载,先找父类加载器去完成。当顶层启动类加载器表示无法加载这个类的时候,子类才会尝试自己去加载。当回到最开的发起者加载器还无法加载时,并不会向下找,而是抛出ClassNotFound异常。
方法:启动(Bootstrap)类加载器,标准扩展(Extension)类加载器,应用程序类加载器(Application ),上下文(Custom)类加载器。意义是防止内存中出现多份同样的字节码 。
1)启动类加载器(Bootstrap ClassLoader):
负责加载JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。
2)扩展类加载器(Extension ClassLoader):
按《深入理解java虚拟机》这本书上所说,该加载器主要是负责加载JAVA_HOME\lib\ext目录中的类库,但是貌似在JDK的安装目录下,没看到该指定的目录。该加载器可以被开发者直接使用。
3)应用程序类加载器(Application ClassLoader):
该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
2.3、 HashMap如何实现的?
HashMap的底层是数组+链表,(很多人应该都知道了)
JDK1.7的是数组+链表
首先是一个数组,然后数组的类型是链表
元素是头插法
JDK1.8的是数组+链表 或者 数组+红黑树
首先是一个数组,然后数组的类型是链表
在链表的元素大于8的时候,会变成红黑树
(当链表长度大于8并且数组长度大于64时,才会转换为红黑树。
如果链表长度大于8,但是数组长度小于64时,还是会进行扩容操作,不会转换为红黑树。因为数组的长度较小,应该尽量避开红黑树。因为红黑树需要进行左旋,右旋,变色操作来保持平衡,
所以当数组长度小于64,使用数组加链表比使用红黑树查询速度要更快、效率要更高。 )
在红黑树的元素小于6的时候会变成链表
(这里需要注意,不是元素小于6的时候一定会变成链表,只有resize的时候才会根据UNTREEIFY_THRESHOLD 进行转换,同样也不是到8的时候就变成红黑树(不是等到扩容的时候) 链表与红黑树的转换详情)
元素进行尾插
2.4、 HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?
使用ConcurrentHashMap(线程安全),
JDK1.7的是分段数组,有Segment锁(继承于ReentrantLock)
加速一小段保证并发
JDK1.8 是和HashMap一样了,数组+链表(或者红黑树)
Synchronized(锁)and CAS(compare and swap)
(JVM在1.6对Synchronize的优化很好)
CAS通俗易懂,比较并替换
(CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做)
(无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的 一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的)
2.5、 HashMap和HashTable 区别,HashTable线程安全吗?
1.1、HashMap和Hashtable的区别
HashTable(线程安全)就是把HashMap套上了一个Synchronized
2.6、 进程间通信有哪几种方式?
管道、消息队列、信号量、共享内存、套接字
无名管道( pipe ):
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
高级管道(popen):
将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
有名管道 (named pipe) :
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
消息队列( message queue ) :
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号量( semophore ) :
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
信号 ( sinal ) :
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
共享内存( shared memory ) :
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
套接字( socket ) :
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
2.7、 JVM分为哪些区,每一个区干吗的?
线程独占 : 栈 , 本地方法栈 ,程序计数器
线程共享 : 堆 , 方法区
程序计数器PC
线程私有的
它可以看做是当前线程所执行的字节码的行号指示器
内存区域中唯一一个没有规定任何OutOfMemoryError的区域
Java虚拟机栈
线程私有的
每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息
如果线程请求的栈深度大于虚拟机所允许的深度,将抛StackOverFlowError异常;
如虚拟机扩展时仍无法申请到足够的内存,就会抛出OutOfMemoryError异常
本地方法栈
与虚拟机栈非常相似,区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用Native方法服务
也会抛出StackOverFlowError和OutOfMemoryError异常
Java堆
线程共享的
Java堆是GC管理的主要区域
在虚拟机启动时创建
存放对象实例,几乎所有的对象实例和数组都在这里分配内存。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
方法区
线程共享的
用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
运行时常量池
是方法区的一部分
用于存放编译器生成的各种字面量和符号引用
相对于Class文件常量池的一个重要特征是,具备动态性
运行时常量池是方法区的一部分,自然受到方法区内存的限制。当常量池无法再申请到内存时会抛出OutOfMemoryError异常
2.8、 JVM如何GC,新生代,老年代,持久代,都存储哪些东西?
2.9、 GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?
可达性分析算法的思想:
从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。
在java中可以作为GC Roots的对象有以下几种:
虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象 不一定会被回收。当一个对象不可达GC Roots时,这个对象并不会马上被回收,而是处于一个死缓的阶段,若要被真正的回收需要经历两次标记。如果对象在可达性分析中没有与GC Roots的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已经被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的队列中,虚拟机会触发一个finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这还是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除“即将回收”集合,等待回收。
2.10、 快速排序,过程,复杂度?
//快速排序
void quick_sort(int s[], int l, int r)
{
if (l < r)
{
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
快速排序,分治递归,对于每一段做如下处理:
从右向左第一个小于x的数放在原来左位置+1得那个地方,相反,从左向右第一个大于x的数放到原来右位置-1,一直到坐位置》=右位置,然后中间位置=原来左面的那个位置的数,在递归调用(l,i-1)和(i+1,r)
2.11、 什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤。
对于每一个结点来说,子树和右子树都是二叉平衡树,左右子树的高度差不能大于1,如果插入删除使得高度差大于1了,就要进行旋转操作
插入:
如果有当前结点就返回false,就插入,如果还是二叉平衡树就返回true,插入的过程中如果不符合条件,就左旋右旋处理
删除:
(1)删除节点没有左子树,这种情况直接将删除节点的父节点指向删除节点的右子树。
(2)删除节点没有右子树,这种情况直接将删除节点的父节点指向删除节点的左子树。
(3)删除节点左右子树都存在,可以采用两种方式,
1:让删除节点左子树的最右侧节点代替当前节点
2:让删除节点右子树的最左侧节点代替当前节点
2.12、 TCP如何保证可靠传输?三次握手过程?
TCP为了提供可靠传输:
(1)首先,采用三次握手来建立TCP连接,四次握手来释放TCP连接,从而保证建立的传输信道是可靠的。
(2)其次,TCP采用了连续ARQ协议(回退N,Go-back-N;超时自动重传)(自动重传请求(Automatic Repeat-reQuest,ARQ))来保证数据传输的正确性,使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制。
(3)最后,TCP使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制,避免网络拥塞。
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手: 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据.
2.13、 TCP和UDP区别?
TCP与UDP区别总结
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4.每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的交互通信。
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
为什么UDP有时比TCP更有优势?
UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。
(1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
(2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。
采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。
2.14、 滑动窗口算法?
大概意思:在一个数组或者其他链表中,确认左端点和右端点,这中间用和或者其他的存起来,一个一个的向右移动右端点,这个过程中可能因不符合条件要把左端点也右移,在这个过程中一直记录最大值或者最小值,(向右移动左端点就是在这个范围的和或者其他记录的数值删去左端点这个值)
2.15、 Linux下如何进行进程调度的?
凉凉…………(这个不会,看也看不懂的那种)
1.先来先服务调度算法
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时,
每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪
队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。
该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。
2、基于优先级调度 (Priority Scheduling)
在优先级调度算法中,每个进程都关联一个优先级,内核将CPU分配给最高优先级的进程。具有相同优先级的进程,按照
先来先服务的原则进行调度。
Aging就是指逐渐提高系统中长时间等待的进程的
优先级。我们可以每15分钟将等待进程的优先级加1。最终
经过一段时间,即便是拥有最低优先级的进程也会变成系统中最高优先级的进程,从而被执行。
优先级调度可以抢占式或者非抢占式的。当一个进程在Ready队列中时,内核将它的优先级与正在CPU上执行的进程的优先级
进行比较。当发现这个新进程的优先级比正在执行的进程高时:对于抢占式内核,新进程会抢占CPU,之前正在执行的进程
转入Ready队列;对于非抢占式内核,新进程只会被放置在Ready队列的头部,不会抢占正在执行的进程。
3、短进程优先(SCBF–Shortest CPU Burst First)
最短CPU运行期优先调度算法(SCBF–Shortest CPU Burst First)
该算法从就绪队列中选出下一个“CPU执行期最短”的进程,为之分配处理机。
最短作业优先调度是优先级调度的特例。在优先级调度中我们根据进程的优先级来进行调度,在最短作业优先调度中我们
根据作业的执行时间长短来调度。
4、轮转法 (Round-Robin Scheduling) (RR)
前几种算法主要用于批处理系统中,不能作为分时系统中的主调度算法,在分时系统中,都采用时间片轮转法。
简单轮转法:系统将所有就绪进程按FIFO规则排队,按一定的时间间隔把处理机分配给队列中的进程。这样,就绪
队列中所有进程均可获得一个时间片的处理机而运行。多级队列方法:将系统中所有进程分成若干类,每类为一级。
RR调度算法转为分时系统设计,它与FCFS很像,但是加入了抢占。具体调度过程是:内核从Ready队列中选取第一个进程,
将CPU资源分配给它,并且设置一个定时器在一个时间片后中断该进程,调度Ready队列中的下一进程。很明显,RR调度
算法是抢占式的,并且在该算法的调度下,没有一个进程能够连续占用CPU超过一个时间片,从而达到了分时的目的。
5、高响应比优先调度算法
(1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业.
(2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务.
(3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高, 从而也可获得处理机.
该算法照顾了短作业,且不会使长作业长期得不到服务
6、抢占式调度算法
- 非抢占式调度算法
为每一个被控对象建立一个实时任务并将它们排列成一轮转队列,调度程序每次选择队列中的第一个任务投入运行.该任务完成后便把它挂在轮转队列的队尾等待下次调度运行. - 非抢占式优先调度算法.
实时任务到达时,把他们安排在就绪队列的对首,等待当前任务自我终止或运行完成后才能被调度执行. - 抢占式调度算法
1)基于时钟中断的抢占式优先权调度算法.
实时任务到达后,如果该任务的优先级别高于当前任务的优先级并不立即抢占当前任务的处理机,而是等到时钟中断到来时,调度程序才剥夺当前任务的执行,将处理机分配给新到的高优先权任务.
2)立即抢占的优先权调度算法.
在这种调度策略中,要求操作系统具有快速响应外部时间中断的能力.一旦出现外部中断,只要当前任务未处于临界区便立即剥夺当前任务的执行,把处理机分配给请求中断的紧迫任务,实时进程调度,实时进程抢占当前。
2.16、 Linux下你常用的命令有哪些?
这个最好是多用用比较好,直接跳过看一下个题
1、显示日期的指令: date
2、显示日历的指令:cal
3、简单好用的计算器:bc
怎么10/100会变成0呢?这是因为bc预设仅输出整数,如果要输出小数点下位数,那么就必须要执行 scale=number ,那个number就是小数点位数,
4、重要的几个热键[Tab],[ctrl]-c, [ctrl]-d
[Tab]按键—具有『命令补全』不『档案补齐』的功能
[Ctrl]-c按键—让当前的程序『停掉』
[Ctrl]-d按键—通常代表着:『键盘输入结束(End Of File, EOF 戒 End OfInput)』的意思;另外,他也可以用来取代exit
5、man
退出用q,
man -f man
6、数据同步写入磁盘: sync
输入sync,那举在内存中尚未被更新的数据,就会被写入硬盘中;所以,这个挃令在系统关机戒重新启劢乀前, 径重要喔!最好多执行几次!
7、惯用的关机指令:shutdown
此外,需要注意的是,时间参数请务必加入指令中,否则shutdown会自动跳到 run-level 1 (就是单人维护的登入情况),这样就伤脑筋了!底下提供几个时间参数的例子吧:
重启,关机: reboot, halt,poweroff
8、切换执行等级: init
Linux共有七种执行等级:
–run level 0 :关机
–run level 3 :纯文本模式
–run level 5 :含有图形接口模式
–run level 6 :重新启动
使用init这个指令来切换各模式:
如果你想要关机的话,除了上述的shutdown -h now以及poweroff之外,你也可以使用如下的指令来关机:
9、改变文件的所属群组:chgrp
10、改变文件拥有者:chown
他还可以顸便直接修改群组的名称
11、改变文件的权限:chmod
权限的设定方法有两种, 分别可以使用数字或者是符号来进行权限的变更。
–数字类型改变档案权限:
–符号类型改变档案权限:
12、查看版本信息等
13、变换目录:cd
14、显示当前所在目录:pwd
15、建立新目录:mkdir
不建议常用-p这个选项,因为担心如果你打错字,那么目录名称就回变得乱七八糟的
16、删除『空』的目录:rmdir
17、档案与目录的显示:ls
18、复制档案或目录:cp
19、移除档案或目录:rm
20、移动档案与目录,或更名:mv
21、取得路径的文件名与目录名:basename,dirname
22、由第一行开始显示档案内容:cat
23、从最后一行开始显示:tac(可以看出 tac 是 cat 的倒着写)
24、显示的时候,顺道输出行号:nl
25、一页一页的显示档案内容:more
26、与 more 类似,但是比 more 更好的是,他可以往前翻页:less
27、只看头几行:head
28、只看尾几行:tail
29、以二进制的放置读取档案内容:od
30、修改档案时间或新建档案:touch
31、档案预设权限:umask
32、配置文件档案隐藏属性:chattr
33、显示档案隐藏属性:lsattr
34、观察文件类型:file
35、寻找【执行挡】:which
36、寻找特定档案:whereis
37、寻找特定档案:locate
38、寻找特定档案:find
39、压缩文件和读取压缩文件:gzip,zcat
40、压缩文件和读取压缩文件:bzip2,bzcat
41、压缩文件和读取压缩文件:tar
2.17、 操作系统什么情况下会死锁?
死锁的4个必要条件
(1) 互斥条件: 一个资源每次只能被一个进程使用。
(2) 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行被剥夺。
(4) 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
竞争不可剥夺资源
在系统中所配置的不可剥夺资源,由于它们的数量不能满足诸进程运行的需要,会使进程在运行过程中,因争夺这些资源而陷于僵局。
竞争临时性资源
上面所说的打印机资源属于可顺序重复使用型资源,称为永久资源。还有一种所谓的临时资源,这是指由一个进程产生,被另一个进程使用,短时间后便无用的资源,故也称为消耗性资源,
2.18、 常用的hash算法有哪些?
加法Hash;位运算Hash;乘法Hash;除法Hash;查表Hash;混合Hash;
2.19、 什么是一致性哈希?
一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织
圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,
0点的左侧是2的32次方-1,把这个圆环叫做Hash环
然后把服务器ip或者主机名字作为关键字Hash,每个服务器都能确定位置,把数据进行相同的Hash算出的位置,顺时针访问的第一个就是对应的服务器
一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
2.20、 如何理解分布式锁?
分布式锁: 甲乙两人购买时剩余量都显示一个,如果同时下单可能会出现问题,这时就需要用到分布式锁
分布式锁是实现有序调度不同的进程,解决不同进程之间相互干扰的问题的技术手段
分布式锁的应具备的条件
在分布式系统环境下,分布式锁在同一个时间仅能被同一个进程访问
高可用的获取/释放锁
高性能的获取/释放锁
具备锁的重入性
具备锁的失效机制,防止死锁
具备非阻塞锁的特性,即使没有获取锁也能直接返回结果
分布式锁的实现有哪些
mechache:
利用mechache的add命令,改命令是原子性的操作,只有在key 不存在的情况下,才能add成功,也就意味着线程拿到了锁
Redis:
和Mechache的实现方法相似,利用redis的setnx命令,此命令同样是原子性的操作,只有在key不存在的情况下,add成功
zookeeper:
利用他的顺序临时节点,来实现分布式锁和等待队列,zookeeper的设计初衷就是为了实现分布式微服务的
使用Redis实现分布式锁的思路
先去redis中使用setnx(商品id,数量) 得到返回结果
这里的数量无所谓,它的作用就是告诉其他服务,我加上了锁
发现redis中有数量,说明已经可以加锁了
发现redis中没有数据,说明已经获得到了锁
解锁: 使用redis的 del商品id
锁超时, 设置exprie 生命周期,如30秒, 到了指定时间,自定解锁
三个致命问题
非原子性操作:setnx,宕机,expire
因为 setnx和expire不是原子性的,要么都成功要么都失败, 一旦出现了上面的情况,就会导致死锁出现
redis提供了原子性的操作 set ( key , value , expire)
误删锁
假如我们的锁的生命事件是30秒,结果我在30s内没操作完,但是锁被释放了
jvm2拿到了锁进行操作
jvm1 操作完成使用del,结果把jvm2 的锁删除了
解决方法, 在删除之前,判断是不是自己的锁
redis提供了原子性的操作 set ( key ,threadId, expire)
超时为完成任务
增加一个守护线程,当快要超时,但是任务还没执行完成,就增加锁的时间
2.21、 数据库中的范式有哪些?
第一范式----数据库中的表(所有字段值)都是不可分割的原子数据项。
第二范式----数据库表中的每一列都和主键相关,而不能只和主键的某一部分相关。也就是说 一个表中只能只能包含一个,不能把多种数据保存在同一个表中。
第三范式----数据库表中每一列数据都和主键直接相关,不能间接相关。
2.22、 数据库中的索引的结构?什么情况下适合建索引?
数据库中索引的结构是一种排序的数据结构。是通过B树和变形的B+树
实现的。
适合建索引: 经常查询,使用,用在表连接的字段(经常更改的字段不适合建索引)
2.23、 Java中的NIO,BIO,AIO分别是什么?
BIO: 同步并阻塞
,服务器实现模式为一个连接一个线程
,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO: 同步非阻塞
,服务器实现模式为一个请求一个线程
,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO: 异步非阻塞
,服务器实现模式为一个有效请求一个线程
,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
2.24、 用什么工具调试程序?JConsole,用过吗?
JConsole在JDK/bin目录下面,对资源消耗和性能进行监控,提供图表和可视化界面,占内存小(具体的一些还是自己多打开看一看就差不多了)
2.25、 现在JVM中有一个线程挂起了,如何用工具查出原因?
通过Javacore
了解线程运行情况:
Javacore,也可以称为“threaddump”或是“javadump”,它是 Java 提供的一种诊断特性,能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻,JVM 中有哪些线程在运行,每个线程执行到哪一个类,哪一个方法。
应用程序如果出现不可恢复的错误或是内存泄露,就会自动触发 Javacore 的生成。而为了性能问题诊断的需要,我们也会主动触发生成 Javacore。
在 AIX、Linux、Solaris 环境中,我们通常使用 kill -3 产生该进程的 Javacore。
2.26、 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
同步是个过程,阻塞是线程的一种状态。多个线程操作共享变量时可能会出现竞争。这时需要同步来防止两个以上的线程同时进入临界区,在这个过程中,后进入临界区的线程将阻塞,等待先进入的线程走出临界区。
线程同步不一定发生阻塞!!!线程同步的时候,需要协调推进速度,互相等待和互相唤醒会发生阻塞。
同样,阻塞也不一定同步。
2.27、 同步和异步有什么区别?
同步交互: 指发送一个请 求,需要等待返回,然后 才能够发送下一个请求,有个等待过程;
异步交互: 指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别: 一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
2.28、 线程池用过吗?
线程池做的工作
主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的优势
线程复用、控制最大并发数、线程管理
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1、corePoolSize(线程池基本大小): 当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小): 线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3、keepAliveTime(线程存活保持时间): 当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、workQueue(任务队列): 用于传输和保存等待执行任务的阻塞队列。
5、threadFactory(线程工厂): 用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
6、handler(线程饱和策略): 当线程池和队列都满了,再加入线程会执行此策略。
线程池为什么需要使用(阻塞)队列?
1、因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。
2、创建线程池的消耗较高。 (线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。)
线程池为什么要使用阻塞队列而不使用非阻塞队列?
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。
当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。
使得在线程不至于一直占用cpu资源。
不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢?
如何配置线程池
CPU密集型任务: 尽量使用较小的线程池,一般为CPU核心数+1。
IO密集型任务: 可以使用稍大的线程池,一般为2*CPU核心数。
混合型任务: 可以将任务分成IO密集型和CPU密集型任务,
2.29、 如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?
饿汉模式
通过定义final型的对象,来让加载类的时候,直接创建对象,只加载一次,实现单例。
懒汉式
通过定义静态对象,加锁去实例化对象。
枚举
通过定义枚举类,来实现单例。
Double-Check
若有两个线程通过了第一个Check循环,进入第二个Check循环是串行化的,只能有一个线程进入,这样当这个线程创建完成后,另外的线程就无法通过第二个循环了,保证了实例的唯一性
,随后的线程也不会通过第一个Check循环,也就不会有同步控制的环节了。但是,这种方法也伴随着一个缺点,它可能会引起空指针的异常。
高效创建线程安全的单例
Volatile+Double-Check
volatile关键字可以防止重排序的发生,在此不对volatile作详细介绍,通过volatile关键字,这种模式可以说是满足懒加载、多线程下单例的唯一性、安全性的。
public final class SingletonObject5 {
private volatile static SingletonObject5 instance ;
private SingletonObject5() {
}
public static SingletonObject5 getInstance() {
if (null == instance) {
synchronized (SingletonObject5.class) {
if (null == instance)
instance = new SingletonObject5();
}
}
return SingletonObject5.instance;
}
}
2.30、 concurrent包下面,都用过什么?
(凉凉夜色为我思念成河……)
1.executor接口,使用executor接口的子接口ExecutorService用来创建线程池
2.Lock接口下的ReentrantLock类,实现同步,比如三个线程循环打印ABCABCABC…
3.atomic包,使用AtomicInteger类的incrementAndGet()方法来实现原子操作,比如a++
4.Callable接口,重写call方法,实现多线程
5.concarrenHashMap,线程安全的HashMap
2.31、 常用的数据库有哪些?redis用过吗?
mysql 、SQL Server、Oracle、Sybase、DB2等
Redis:
1、纯内存操作
2、核心是基于非阻塞的IO多路复用机制
3、单线程反而避免了多线程的频繁上下文切换问题
2.32、 了解hadoop吗?说说hadoop的组件有哪些?说下mapreduce编程模型。
common、Hadoop Distributed File System(HDFS)、MapReduce、YARN
编程模型:
Mapper把复杂的任务分解为若干个小任务,分到存在所需数据结点上进行计算,这些任务可以一起,彼此没有依赖关系
Reducer把Mapper的结果汇总
eg:
一共有100个汉堡,甲吃十个,乙吃是个,丙吃十个,,,这就是Mapper
甲乙丙……放到一起就是Reducer
2.33、 你知道的开源协议有哪些?
• Mozilla Public License:
MPL License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起
者。这种授权维护了商业软件的利益,它要求基于这种软件得修改无偿贡献版权给该软件。这样,围绕该软件得
所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。
• BSD开源协议:
给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或
者专有软件再发布。
• Apache Licence 2.0 :
Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样
鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。
• GPL:
GPL许可证是自由软件的应用最广泛的软件许可证,人们可以修改程式的一个或几个副本或程式的任何部
分,以此形成基於这些程式的衍生作品。必须在修改过的档案中附有明显的说明:您修改了此一档案及任何修改
的日期。 您必须让您发布或出版的作品,包括本程式的全部或一部分,或内含本程式的全部或部分所衍生的作
品,允许第三方在此许可证条款下使用,并且不得因为此项授权行为而收费。
• LGPL:
LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生之GPL类库的的软
件必须采用GPL协议不同。LGPL允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代
码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并发布和销售。
• Public Domain:
公共域授权。将软件授权为公共域,这些软件包没有授权协议,任何人都可以随意使用它
2.34、 你知道的开源软件有哪些?
• JDK
• eclipse
• Tomcat
• Spring
• Hibernate
• MySQL
• MyBatis
• struts
2.35、 你最近在看的书有哪些?
这个尽量说真话,最好不要搜一点简介就说看过什么书,否则被戳穿
很难受,
如果实在没看过可以这么说:
最近没怎么看书,但是相对于书本,我更喜欢在B站学习,比如大学公开课和摄影栏目我就经常逛;知乎我也挺活跃的,关注……等话题,每天保持相当的阅读量,回答受赞也有几百了;我也参加了很多经验分享活动,我觉得从面对面的交流获得的知识,是阅读无法替代的。
2.36、 你有什么问题要问我吗?
严禁:没问题,提薪资,五年计划,某某产品被卖了等无关紧要的问题
准备3-5个问题就行,太多或者太少都不好
问题
1、问工作内容
这份工作比较大的挑战是?
您希望我在短期内解决哪些问题?
对于未来加入这个团队,您对我的期望是什么?
我对这个职位工作的理解是XXX,不知道除了我的理解外,是否还有其他的工作职责?
2、问职位差距
对我此次应聘的表现有什么评价和建议?
如果可以录用,我需要学习哪方面的知识?
接下来的这段空档期,有什么值得注意或者建议学习的吗?
3、问工作潜力
请问该岗位都要经历哪些培训?
这个岗位的晋升路径是什么样子的?
咱们部门近期/未来有什么新动向/新举措?
您对这个岗位三到五年职业规划的建议是什么呢
4、问团队氛围
能带我看一下办公区吗?
您在公司的一天是如何度过的?
可以介绍下一起工作的团队是什么样的吗?
提问的原则
1、探讨式发问
提问不是只问不答,提问后,先抛出一点自己的理解,让面试官看出你对应聘的职位是做过功课的。透过发问,更了解公司的组织文化和工作上实际会遇到的问题。
2、在其位谋其职
提问要展现专业度,切忌太跳脱或者故意装高深,引发尴尬,又给人好高骛远的感觉。记住一点:不提和所聘岗位无关的问题。
3、真诚
提问环节也是考验情商的时候!关注他人感受,掌握好分寸感。要明白提问不是辩论,你不是为了和面试官互相切磋知识、技能,而是真诚、虚心请教。
2.37、 了解哪些设计模式?说说都用过哪些设计模式
设计模式的分类
总体来说是三大类
:
创建型模式 ,五种:工厂方法模式、 抽象工厂模式、单例模式、建造者模式、原型模式
结构性模式 ,共七种: 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式。
其实还有两类:并发型模式和线程池模式
2.38、 如何判断一个单链表是否有环?
快慢指针查询,快指针每次跳两个,慢指针每次跳一个,如果两指针相等时,就证明有环
环的入口:
用两个指针,一个指向快慢指针相交点(这里就是慢指针走,慢指针在走快指针的一半就相当于快指针走的路了,还会到这个点),一个指向单链表的头结点(模拟慢指针从头走,也是走快指针的一半),一起走,当两个指针相等时,就是环的入口。
数学思维:
设从单链表头节点到环入口节点的距离是D,环入口到相交点的距离是X,设slow和fast第一次相遇时fast走了n圈环,slow走的距离为len,那么fast走的距离是2*len,可以得出下面的两个等式:
len = D + X
2 * len = D + X + n * R
两个等式相减可以的到:len = n * R - X
如果还不行的话,自己画个带环的单链表就明白了
2.39、 操作系统如何进行分页调度?
笔者不懂,凉凉……
1.分页的作用: 高效率地利用内存,可以运行比物理内存空间更大的程序;
2.分页机制的原理: 利用两级页表将内存分割成4KB/页的大小,强制内存对齐提高效率;
3.页表结构: PDE与PTE在内存中各个位的主要作用,表项与页之间的对应关系。
2.40、 匿名内部类是什么?如何访问在其外面定义的变量?
匿名内部类:
没有名字,通常用来简化代码,只能用一次(使用的话必须要继承父类或者实现接口)
访问在其外面定义的变量:
必须要final类型的变量(也可以不用final类型,但只能使用不能修改)
三面
3.1、 自我介绍,做过什么项目。
这。就不说了,某度一搜一堆,项目要挑自己做的最有水平的拿出来
3.2、java虚拟机的区域如何划分
程序计数器(独立内存)
当前线程所执行的字节码的行号指示器。
Java虚拟机栈(独立内存)
每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈(独立内存)
本地方法栈则为虚拟机使用到的Native方法(百度说是Java中声明的可调用的C/C++实现的方法)服务。
Java堆(共享内存): 存放对象实例
方法区(共享内存): 存储已被虚拟区加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池: 存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
3.3、 双亲委派模型中,从顶层到底层,都是哪些类加载器,分别加载哪些类?
(1)启动类加载器(Bootstrap ClassLoader)
这个类加载器负责将存放在JAVA_HOME/lib下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
(2)扩展类加载器(Extension ClassLoader)
这个加载器负责加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
(3)应用程序类加载器(Application ClassLoader)
这个加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
3.4、 有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类?
双亲委派,先在父类看能不能加载,如果能则由父加载,否则给子类加载
3.5、 HashMap的结构,get(),put()是如何实现的?
Put:
1、先将key和value封装到Node节点中
2、底层会调用key的hashcode()方法,通过hash函数将hash值转换为数组下标,下标位置上如果没有任何元素,就把该Node添加到该位置上(该下标处)
如果该下标处对应的位置上已经存在元素或链表(多于一个元素变成链表),那么就会拿着新节点的key与链表上的每一个人节点中的key进行equals。
1、 如果所有对比(equals)都返回false,那么这个新节点将会被添加到链表的尾部。(大于8个就会转换成红黑树)
2、 如果其中有一个对比(equals)返回true,那么这个节点上的value将会被新节点的value覆盖。
Get:
1、底层会调用key的hashcode()方法,通过hash函数将hash值转换为数组下标,通过数组下标快速定位到数组的指定位置上,如果这个位置上没有任何元素,那么返回null。
2、如果这个位置上有单向链表(该位置上有元素,或者有红黑树),那么会拿着我们get(key)中的key和单向链表中的每个节点的key进行equals,如果说所有的equals都返回false,那么这个get方法返回false。
3、只要其中有一个节点的key和参数key的equals对比的结果返回true,那么这个节点的value就是我们想要找的value,get方法返回这个value.
3.6、 ConcurrentHashMap的get(),put(),又是如何实现的?ConcurrentHashMap有哪些问题? ConcurrentHashMap的锁是读锁还是写锁?
put 操作一上来就锁定了整个segment
,这当然是为了并发的安全
,修改数据是不能并发进行的,必须得有个判断是否超限的语句以确保容量不足时能够 rehash,
而比较难懂的是这句int index = hash & (tab.length - 1),原来segment里面才是真正的hashtable,即每个segment是一个传统意义上的hashtable, 从两者的结构就可以看出区别,这里就是找出需要的entry在table的哪一个位置,之后得到的entry就是这个链的第一个节点,如果e!=null,说明找到了,这是就要替换节点的值(onlyIfAbsent == false),否则,我们需要new一个entry,它的后继是first,而让tab[index]指向它,什么意思呢?实际上就是将这个新entry 插入到链头,剩下的就非常容易理解了。
get 方法(请注意,这里分析的方法都是针对桶的,因为ConcurrentHashMap的最大改进就是将粒度细化到了桶上
),首先判断了当前桶的数据个数是 否为0,为0自然不可能get到什么,只有返回null,这样做避免了不必要的搜索,也用最小的代价避免出错。然后得到头节点(方法将在下面涉及)之后就 是根据hash和key逐个判断是否是指定的值,如果是并且值非空就说明找到了,直接返回;
程序非常简单,但有一个令人困惑的地方,这句return readValueUnderLock(e)到底是用来干什么的呢?研究它的代码,在锁定之后返回一个值。但这里已经有一句V v = e.value得到了节点的值,这句return readValueUnderLock(e)是否多此一举?
事实上,这里完全是为了并发考虑的,这里当v为空时,可能是一个线程正在改变节点,而之前的 get操作都未进行锁定,根据bernstein条件,读后写或写后读都会引起数据的不一致,所以这里要对这个e重新上锁再读一遍,以保证得到的是正确值,
这里不得不佩服Doug Lea思维的严密性。整个get操作只有很少的情况会锁定,相对于之前的Hashtable,并发是不可避免的啊!
ConcurrentHashmap只能保证自身数据在多线程的环境下不被破坏,而并不能保证业务逻辑的正确性。
ConcurrentHashMap的锁是读锁
3.7、 sleep()和wait()分别是哪个类的方法,有什么区别?synchronized底层如何实现的?用在代码块和方法上有什么区别?
sleep方法是Thread
类的静态方法,调用此方法会让当前线程暂停指定的时间,将执行机会(CPU)让给其他线程,但是不会释放锁,因此休眠时间结束后自动恢复(程序回到就绪状态)。
wait是Object
类的方法,调用对象的wait方法导致线程放弃CPU的执行权,同时也放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify或notifyAll方法才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
wait只能在同步控制方法中或者同步控制块中使用,而sleep可以在任何地方使用。
wait 可以指定时间也可以不指定,指定时间 wait(time) 在 time时间内 有别的线程 notifyAll() 是不会唤醒到它 。sleep 必须指定时间
synchronized代码块是由一对monitorenter/monitorexit
指令实现的, Monitor对象是同步的基本实现单元。
现代的(Oracle) JDK6中, JVM对此进行了大刀阔斧地改进,提供了三种不同的Monitor实现,也就是常说的三种不同的锁:
偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范围比较大,而同步代码块范围要小点
,
一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好。
3.8、 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
一个线程池包括以下四个基本组成部分:
线程管理器 (ThreadPool): 用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务;
工作线程 (PoolWorker): 线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
任务接口 (Task): 每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
任务队列 (TaskQueue): 用于存放没有处理的任务。提供一种缓冲机制;
所包含的方法
//创建线程池
private ThreadPool()
//获得一个默认线程个数的线程池
public static ThreadPool getThreadPool()
//执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable task)
//批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定
public void execute(Runnable[] task)
//销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy()
//返回工作线程的个数
public int getWorkThreadNumber()
//返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成
public int getFinishedTasknumber()
//在保证线程池中所有线程正在执行,并且要执行线程的个数大于某一值时。增加线程池中线程的个数
public void addThread()
//在保证线程池中有很大一部分线程处于空闲状态,并且空闲状态的线程在小于某一值时,减少线程池中线程的个数
public void reduceThread()
3.9、 什么是死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息?
(不懂还是不懂,😔)
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源
,导致这些线程处于等待状态,无法前往执行。
常常需要在隔两分钟后再次收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个 地址上锁,那么肯定是死锁了。
3.10、 查看jvm虚拟机里面堆、线程的信息,你用过什么命令?
-heap : 打印jvm heap的情况,会列出堆的总体使用情况,还有新生代老生代的内存占用情况。
-histo: 打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-histo:live : 同上,但是只答应存活对象的情况
-permstat: 打印permanent generation heap情况
等等
3.11、 垃圾回收算法有哪些?CMS知道吗?如何工作的?
标记-清除算法
从算法的名称上可以看出,这个算法分为两部分,标记和清除。首先标记出所有需要被回收的对象,然后在标记完成后统一回收掉所有被标记的对象。
这个算法简单,但是有两个缺点:一是标记和清除的效率不是很高;二是标记和清除后会产生很多的内存碎片,导致可用的内存空间不连续,当分配大对象的时候,没有足够的空间时不得不提前触发一次垃圾回收。
复制算法
这个算法将可用的内存空间分为大小相等的两块,每次只是用其中的一块,当这一块被用完的时候,就将还存活的对象复制到另一块中,然后把原已使用过的那一块内存空间一次回收掉。这个算法常用于新生代的垃圾回收。
复制算法解决了标记-清除算法的效率问题,以空间换时间,但是当存活对象非常多的时候,复制操作效率将会变低,而且每次只能使用一半的内存空间,利用率不高。
标记-整理算法
这个算法分为三部分:
一是标记出所有需要被回收的对象;
二是把所有存活的对象都向一端移动;三是把所有存活对象边界以外的内存空间都回收掉。
标记-整理算法解决了复制算法多复制效率低、空间利用率低的问题,同时也解决了内存碎片的问题。
分代收集算法
根据对象生存周期的不同将内存空间划分为不同的块,然后对不同的块使用不同的回收算法。一般把Java堆分为新生代和老年代,新生代中对象的存活周期短,只有少量存活的对象,所以可以使用复制算法,而老年代中对象存活时间长,而且对象比较多,所以可以采用标记-清除和标记-整理算法。
CMS工作过程
初始标记 :在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
并发预清理 :
并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。
重新标记 : 这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
并发清理 : 清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
并发重置 : 这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。
3.12、 数据库中什么是事务?事务的隔离级别?事务的四个特性?什么是脏读,幻读,不可重复读?
事务(transaction)是作为一个单元的一组有序的数据库操作。
如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交
数据库事务的隔离级别由低到高分别为
Read uncommitted 、Read committed(大多数数据库) 、Repeatable read(Sql Server , Oracle。Mysql) 、Serializable
事务的四个特性:
1 、原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性
(只有一种结果,不是全写入数据库,就是全都没写入数据库)
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
3 、隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
脏读 是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
幻读 是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
不可重复读 是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
3.13、 数据库索引的结构有哪些? 介绍B+树的结构。
哈希表和有序数组
二叉树和多叉树
B+树
1.根结点至少有两个子女。
2.每个中间节点都至少包含ceil(m / 2)个孩子,最多有m个孩子。
3.每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m。
4.所有的叶子结点都位于同一层。
5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
3.14、 数据库中的分页查询语句怎么写?
select * from table limit (start-1)*limit,limit; --其中start是页码,limit是每页显示的条数。
3.15、 什么是一致性哈希?用来解决什么问题?
一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织
圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,
0点的左侧是2的32次方-1,把这个圆环叫做Hash环
然后把服务器ip或者主机名字作为关键字Hash,每个服务器都能确定位置,把数据进行相同的Hash算出的位置,顺时针访问的第一个就是对应的服务器
一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性
和可扩展性
。
用来解决的问题:
hash( key ) % N,N 为 Redis 的数量,在这里 N = 4 ;
4 台 Redis 不够了,需要再增加 4 台 Redis ;那么这个求余算法就会变成:hash( key ) % 8 ;
一部分%4一部分%8当前大部分缓存的位置都会是错误的,极端情况下,就会造成 缓存雪崩。
(如果节点太少或分布不均匀的时候,容易造成 数据倾斜,也就是大部分数据会集中在某一台服务器上。,一致性 Hash 算法提出了【虚拟节点】解决数据倾斜问题)
3.16、 Redis的存储结构,或者说如何工作的,与mysql的区别?有哪些数据类型?
redis中以key-value
的形式存储,key固定是字符串
,使用字符串对象进行表示,value可以是字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(ZSet)
。
redis和mysql的区别总结:
(1)类型上
从类型上来说,mysql是关系型数据库,redis是缓存数据库
(2)作用上
mysql用于持久化的存储数据到硬盘,功能强大,速度较慢,基于磁盘,读写速度没有Redis快,但是不受空间容量限制,性价比高
redis用于存储使用较为频繁的数据到缓存中,读取速度快,基于内存,读写速度快,也可做持久化,但是内存空间有限,当数据量超过内存空间时,需扩充内存,但内存价格贵
(3)需求上
mysql和redis因为需求的不同,一般都是配合使用。
需要高性能的地方使用Redis,不需要高性能的地方使用MySQL。存储数据在MySQL和Redis之间做同步。
3.17、 项目中用到redis,为什么选用redis,了解其他NoSQL数据库吗?在你的项目中是如何运用redis的?key是什么,value是什么?
(又是凉凉的一道题,😔)
为什么选用redis
高效性: Redis读取的速度是110000次/s,写的速度是81000次/s
原子性: Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
支持多种数据结构: string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
稳定性: 持久化,主从复制(集群)
其他特性: 支持过期时间,支持事务,消息订阅。
其他NoSQL数据库:
memcache介绍
很早出现的NoSql数据库数据都在内存中,一般不持久化支持简单的key-value模式一般是作为缓存数据库辅助持久化的数据库
mongoDB介绍
高性能、开源、模式自由(schema free)的文档型数据库数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能支持二进制数据及大型对象可以根据数据的特点替代RDBMS(关系数据库管理系统) ,成为独立的数据库。或者配合RDBMS,存储特定的数据。
列式存储HBase介绍
HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行随机、实时的读写操作的场景中。HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万列元素的数据表。
如何运用redis
冷热数据区分
虽然 Redis支持持久化,但将所有数据存储在 Redis 中,成本非常昂贵。建议将热数据 (如 QPS超过 5k) 的数据加载到 Redis 中。低频数据可存储在 Mysql、 ElasticSearch中。
业务数据分离
不要将不相关的数据业务都放到一个 Redis中。一方面避免业务相互影响,另一方面避免单实例膨胀,并能在故障时降低影响面,快速恢复。
消息大小限制
由于 Redis 是单线程服务,消息过大会阻塞并拖慢其他操作。保持消息内容在 1KB 以下是个好的习惯。严禁超过 50KB 的单条记录。消息过大还会引起网络带宽的高占用,持久化到磁盘时的 IO 问题。
连接数限制
连接的频繁创建和销毁,会浪费大量的系统资源,极限情况会造成宿主机当机。请确保使用了正确的 Redis 客户端连接池配置。
缓存 Key 设置失效时间
作为缓存使用的 Key,必须要设置失效时间。失效时间并不是越长越好,请根据业务性质进行设置。注意,失效时间的单位有的是秒,有的是毫秒,这个很多同学不注意容易搞错。
缓存不能有中间态
缓存应该仅作缓存用,去掉后业务逻辑不应发生改变,万不可切入到业务里。
缓存的高可用会影响业务;
产生深耦合会发生无法预料的效果;
会对维护行产生肤效果。
扩展方式首选客户端 hash
如果应用太小就别考虑了,如单 redis 集群并不能为你的数据服务,不要着急扩大你的 redis 集群(包括 M/S 和 Cluster),集群越大,在状态同步和持久化方面的性能越差。优先使用客户端 hash 进行集群拆分。如:根据用户 id 分 10 个集群,用户尾号为 0 的落在第一个集群。
操作限制
严禁使用 Keys
Keys 命令效率极低,属于 O(N)操作,会阻塞其他正常命令,在 cluster 上,会是灾难性的操作。严禁使用,DBA 应该 rename 此命令,从根源禁用。
严禁使用 Flush
flush 命令会清空所有数据,属于高危操作。严禁使用,DBA 应该 rename 此命令,从根源禁用,仅 DBA 可操作。
严禁作为消息队列使用
如没有非常特殊的需求,严禁将 Redis 当作消息队列使用。Redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题。如需要消息队列,可使用高吞吐的 Kafka 或者高可靠的 RocketMQ。
严禁不设置范围的批量操作
redis 那么快,慢查询除了网络延迟,就属于这些批量操作函数。大多数线上问题都是由于这些函数引起。
1、[zset] 严禁对 zset 的不设范围操作
ZRANGE、 ZRANGEBYSCORE等多个操作 ZSET 的函数,严禁使用 ZRANGE myzset 0 -1 等这种不设置范围的操作。请指定范围,如 ZRANGE myzset 0 100。如不确定长度,可使用 ZCARD 判断长度
2、[hash] 严禁对大数据量 Key 使用 HGETALL
HGETALL会取出相关 HASH 的所有数据,如果数据条数过大,同样会引起阻塞,请确保业务可控。如不确定长度,可使用 HLEN 先判断长度
3、[key] Redis Cluster 集群的 mget 操作
Redis Cluster 的 MGET 操作,会到各分片取数据聚合,相比传统的 M/S架构,性能会下降很多,请提前压测和评估
4、[其他] 严禁使用 sunion, sinter, sdiff等一些聚合操作
禁用 select 函数
select函数用来切换 database,对于使用方来说,这是很容易发生问题的地方,cluster 模式也不支持多个 database,且没有任何收益,禁用。
禁用事务
redis 本身已经很快了,如无大的必要,建议捕获异常进行回滚,不要使用事务函数,很少有人这么干。
禁用 lua 脚本扩展
lua 脚本虽然能做很多看起来很 cool 的事情,但它就像是 SQL 的存储过程,会引入性能和一些难以维护的问题,禁用。
禁止长时间 monitor
monitor函数可以快速看到当前 redis 正在执行的数据流,但是当心,高峰期长时间阻塞在 monitor 命令上,会严重影响 redis 的性能。此命令不禁止使用,但使用一定要特别特别注意。
Key 规范
Redis 的 Key 一定要规范,这样在遇到问题时,能够进行方便的定位。Redis 属于无 scheme 的 KV 数据库,所以,我们靠约定来建立其 scheme 语义。
redis中以key-value的形式存储,key固定是字符串
,使用字符串对象进行表示,value可以是字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(ZSet)
。
3.18、 归并排序的过程?时间复杂度?空间复杂度?你平常用什么排序?快速排序。说说在那些场景下适用,哪些场景下不适用。
这个就不总结了,大家面试之前要把排序的几大算法认真看看
3.19、 Solr是如何工作的?
solr是基于Lucence开发的企业级搜索引擎技术,而lucence的原理是倒排索引。
倒排索引:
Demo1:A在吃饭
Demo2:B在睡觉
记录就变成了这样
关键词 段落号(出现频率) 出现位置
吃饭 1(1) 3
在 1(1) 2
2(1) 2
A 1(1) 1
睡觉 2(1) 3
B 2(1) 1
整理不易,有描述的不对的地方还望大佬赐教
转载:https://blog.csdn.net/weixin_46285416/article/details/108186216