JavaSE部分
1、Java基础
为什么重写equals还要重写hashcode
java编程里有关约定:如果两个对象根据equals方法比较是相等的,那么调用这两个对象的任意一个hashcode方法都必须产生相同的结果。
为了正常的使用集合类,比如在HashSet中加对象的时候,会首先用到对象的hashCode值,如果不相等就认为肯定不是一个对象,从而可以省去equals的调用开销,如果两个对象 equals 相等,但是 hashCode 不相等,会导致在 HashSet 中认为是两个不相等的对象,两个对象都会被加入到 HashSet,可能会导致程序异常。
说一下map的分类和常见的情况
HashMap:最常用的Map,根据键的hashcode值来存储数据,根据键可以直接获得他的值(因为相同的键hashcode值相同,在地址为hashcode值的地方存储的就是值,所以根据键可以直接获得值),具有很快的访问速度,遍历时,取得数据的顺序完全是随机的,HashMap最多只允许一条记录的键为null,允许多条记录的值为null,HashMap不支持线程同步,即任意时刻可以有多个线程同时写HashMap,这样对导致数据不一致,如果需要同步,可以使用synchronziedMap的方法使得HashMap具有同步的能力或者使用concurrentHashMap
HashTable:与HashMap类似,不同的是,它不允许记录的键或值为空,支持线程同步,即任意时刻只能有一个线程写HashTable,因此也导致HashTable在写入时比较慢!
LinkedHasMap:是HahsMap的一个子类,但它保持了记录的插入顺序,遍历时先得到的肯定是先插入的,也可以在构造时带参数,按照应用次数排序,在遍历时会比HahsMap慢,不过有个例外,当HashMap的容量很大,实际数据少时,遍历起来会比LinkedHashMap慢(因为它是链啊),因为HashMap的遍历速度和它容量有关,LinkedHashMap遍历速度只与数据多少有关
TreeMap:实现了sortMap接口,能够把保存的记录按照键排序(默认升序),也可以指定排序比较器,遍历时得到的数据是排过序的
Object若不重写hashCode()的话,hashCode()如何计算出来的?
Object的hashcode方法是本地方法,也就是用c语言或者c++实现的,是通过该对象的内存地址进行hash计算得到的。
==比较的是什么?
“==”判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型(它们的变量存储的是值本身,而引用类型变量存储的是引用);equals是Object类的方法,Object对它的实现是比较内存地址,我们可以重写这个方法来自定义“相等”这个概念。比如类库中的String、Date等类就对这个方法进行了重写。
综上,对于枚举类型和原始数据类型的相等性比较,应该使用”==”;对于引用类型的相等性比较,应该使用equals方法。
若对一个类不重写,它的equals()方法是如何比较的?
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
java8新特性
接口的默认方法实现与静态方法、Lambda表达式、函数式接口、方法与构造函数引用、新的日期与时间API、流式处理等重要特性。
说说Lamda表达式的优缺点。
优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
一个十进制的数在内存中是怎么存的?
以二进制补码形式存储,最高位是符号位,正数的补码是它的原码,负数的补码是它的反码加1,在求反码时符号位不变,符号位为1,其他位取反
为啥有时会出现4.0-3.6=0.40000001这种现象?
2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
Java支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型:
整数值型:byte,short,int,long,
字符型:char
浮点类型:float,double
布尔型:boolean
整数默认int型,小数默认是double型。Float和long类型的必须加后缀。
首先知道String是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。引用类型包括类、接口、数组等。String类还是final修饰的。
而包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。
什么是值传递和引用传递?
1、值传递
在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。
2、引用传递
引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。
数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
1、存储内容比较:
Array 数组可以包含基本类型和对象类型,
ArrayList 却只能包含对象类型。
Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
2、空间大小比较:
Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。
ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
3.方法上的比较:
ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
适用场景:
如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。
你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?
大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
其函数表示是:对于函数f(n),g(n),如果存在一个常数c,使得f(n)<=c*g(n),则f(n)=O(g(n));
大O描述当数据结构中的元素增加时,算法的规模和性能在最坏情景下有多好。
大O还可以描述其它行为,比如内存消耗。因为集合类实际上是数据结构,因此我们一般使用大O符号基于时间,内存,性能选择最好的实现。大O符号可以对大量数据性能给予一个很好的说明。
String是最基本的数据类型吗?
不是,是一个final修饰的java类
int 和 Integer 有什么区别
1、Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0
String 和StringBuffer的区别
String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象
StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)
StringBuffer: 可以看作线程安全版的StringBuilder
我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?
1 | public String translate (String str) { |
Java中的四种引用及其应用场景是什么?
强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用
软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收
虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。
&和&&的区别?
Java中&&和&都是表示与的逻辑运算符,都表示逻辑运输符and,当两边的表达式都为true的时候,整个运算结果才为true,否则为false。
&&的短路功能,当第一个表达式的值为false的时候,则不再计算第二个表达式;&则两个表达式都执行。
&可以用作位运算符,当&两边的表达式不是Boolean类型的时候,&表示按位操作
在Java中,如何跳出当前的多重嵌套循环?
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。
1 | ok: |
或者让外层的循环条件表达式的结果可以受到里层循环体代码的控制,例如,要在二维数组中查找到某个数字。
1 | int arr[][] = {{1,2,3},{4,5,6,7},{9}}; |
你能比较一下Java和JavaSciprt吗?
1)基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言。因而它本身提供了非常丰富的内部对象供设计人员使用;
2)解释和编译:Java 的源代码在执行之前,必须经过编译;JavaScript 是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行;
3)强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量声明,采用其弱类型。即变量在使用前不需作声明,而是解释器在运行时检查其数据类型;
4)代码格式不一样。
简述正则表达式及其用途。
在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。计算机处理的信息更多的时候不是数值而是字符串,正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支持。
Java中是如何支持正则表达式操作的?
ava中的String类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中可以用Pattern类表示正则表达式对象,它提供了丰富的API进行各种正则表达式操作。
1 | 面试题: - 如果要从字符串中截取第一个英文左括号之前的字符串,例如:北京市(朝阳区)(西城区)(海淀区),截取结果为:北京市,那么正则表达式怎么写? |
请你说说Java和PHP的区别?
PHP暂时还不支持像Java那样JIT运行时编译热点代码,但是PHP具有opcache机制,能够把脚本对应的opcode缓存在内存,PHP7中还支持配置opcache.file_cache导出opcode到文件.第三方的Facebook HHVM也支持JIT.另外PHP官方基于LLVM围绕opcache机制构建的Zend JIT分支也正在开发测试中.在php-src/Zend/bench.php测试显示,PHP JIT分支速度是PHP 5.4的10倍.
PHP的库函数用C实现,而Java核心运行时类库(jdk/jre/lib/rt.jar,大于60MB)用Java编写(jdk/src.zip), 所以Java应用运行的时候,用户编写的代码以及引用的类库和框架都要在JVM上解释执行. Java的HotSpot机制,直到有方法被执行10000次才会触发JIT编译, 在此之前运行在解释模式下,以避免出现JIT编译花费的时间比方法解释执行消耗的时间还要多的情况.
PHP内置模板引擎,自身就是模板语言.而Java Web需要使用JSP容器如Tomcat或第三方模板引擎.
PHP也可以运行在多线程模式下,比如Apache的event MPM和Facebook的HHVM都是多线程架构.不管是多进程还是多线程的PHP Web运行模式,都不需要PHP开发者关心和控制,也就是说PHP开发者不需要写代码参与进程和线程的管理,这些都由PHP-FPM/HHVM/Apache实现.PHP-FPM进程管理和并发实现并不需要PHP开发者关心,而Java多线程编程需要Java开发者编码参与.PHP一个worker进程崩溃,master进程会自动新建一个新的worker进程,并不会导致PHP服务崩溃.而Java多线程编程稍有不慎(比如没有捕获异常)就会导致JVM崩溃退出.对于PHP-FPM和Apache MOD_PHP来说,服务进程常驻内存,但一次请求释放一次资源,这种内存释放非常彻底. PHP基于引用计数的GC甚至都还没发挥作用程序就已经结束了。
2、关键字
介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?
在synchronized里面,包含有三种常见的锁状态:
对于普通的同步方法:
锁是当前的对象
对于静态函数的同步方法:
锁是指引用当前类的class对象
对于同步方法块的内容:
锁是指Synchonized括号里配置的对象
介绍一下volatile?
volatile作为java中的关键词之一,用以声明变量的值可能随时会被别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排 volatile具有可见性、有序性,不具备原子性。 注意,volatile不具备原子性,这是volatile与java中的synchronized、java.util.concurrent.locks.Lock最大的功能差异,这一点在面试中也是非常容易问到的点
锁有了解嘛,说一下Synchronized和lock
Lock是一个接口,而synchronized是关键字。
synchronized会自动释放锁,而Lock必须手动释放锁。
Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
Lock能提高多个线程读操作的效率。
synchronized能锁住类、方法和代码块,而Lock是块范围内的
讲一讲Java里面的final关键字怎么用的?
(1)修饰类:表示该类不能被继承;
(2)修饰方法:表示方法不能被重写;
(3)修饰变量:表示变量只能赋值一次且赋值以后值不能被修改(常量)
3、面向对象
wait方法底层原理
object中的方法,可以暂停线程,期间会释放对象锁,不像sleep方法,线程休眠期依然持有锁,wait方法的线程,必须调用notify或notifyAll方法唤醒线程!
Java有哪些特性,举个多态的例子。
继承、封装、多态。多态的主要特征就是父类引用指向子类对象,生活中的例子:Animal animal = new Dog();
String为啥不可变?
string是final修饰,不可变,同时string底层是字符串数组也是final修饰,这样做首先是安全,比如hashset中用string做为键,不会出现string变化,导致违反唯一键。另外节约内存。
类和对象的区别
1,类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。就好像“Person(人)”这个类,它虽然可以包含很多个体,但它本身不存在于现实世界上。
2,对象是类的一个具体。它是一个实实在在存在的东西。
3,类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。
4,对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。
请列举你所知道的Object类的方法。
getClass():用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写;
equals():用于比较两个对象的地址是否相同,即两个引用是否指向同一个对象;
clone():用于创建并返回当前对象的一份拷贝;
toString():返回类的名字@实例的哈希码的16进制字符串;
notify():唤醒等待队列中的其中一个线程;
notifyAll():唤醒线程等待队列中的所有线程;
wait(long timeout):让一个线程等待一段时间。
重载和重写的区别?相同参数不同返回值能重载吗?
重载:同名不同参,参数的类型和个数没有具体的限制,一般构造方法使用的比较多,他展现的是编译时的多态性
重写:是对父类的方法重新进行定义在继承父类的方法的时候可以通过重写保证和定义特定于自己的行为,它展现的是运行时的多态性
返回值类型作为函数运行之后的一个状态,他是保持方法的调用者与被调用者进行通信的关键,并不能作为某个方法的标识,所以通过返回类型并不能区分重载的方法,应该根据所要区分的方法的方法名是否相同并且方法中所带的参数去区分
”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
java中也不可以覆盖private的方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的,当然也不能覆盖。
String能继承吗?
不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
StringBuffer和StringBuilder有什么区别,底层实现上呢?
StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,
只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,
而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全
而StringBuffer则每次都需要判断锁,效率相对更低
类加载机制,双亲委派模型,好处是什么?
类加载,JVM第一次使用到这个类时需要对,这个类的信息进行加载。一个类只会加载一次,之后这个类的信息放在堆空间,静态属性放在方法区。
JVM类加载器从上到下一共分为三类
- 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
- 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
- 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
当一个加载器不管是应用程序类加载器还是我们自定义的类加载器在进行类加载的时候它首先不会自己去加载,它首先会把加载任务委派给自己的父类加载器,比如现在有个类需要我们的自定义类加载器来加载,其实它首先会把它交给应用程序类加载器,应用程序类加载器又会把任务交给扩展类加载器,一直往上提交,直到启动类加载器。启动类加载器如果在自己的扫描范围内能找到类,它就会去加载,如果它找不到,它就会交给它的下一级子加载器去加载,以此类推,这就是双亲委派模型。
为什么jdk里要提出双亲委派模型?
可以保证我们的类有一个合适的优先级,例如Object类,它是我们系统中所有类的根类,采用双亲委派模型以后,不管是哪个类加载器来加载Object类,哪怕这个加载器是自定义类加载器,通过双亲委派模型,最终都是由启动类加载器去加载的,这样就可以保证Object这个类在程序的各个类加载器环境中都是同一个类。在虚拟机里觉得一个类是不是唯一有两个因素,第一个就是这个类本身,第二个就是加载这个类的类加载器,如果同一个类由不同的类加载器去加载,在虚拟机看来,这两个类是不同的类。
静态变量存在哪?
静态变量在方法区的静态存储区,但方法区既可以在堆上又可以位于栈(not java栈)上,static 变量保存在 Class 实例的尾部。
Class 对象存在堆中。
讲讲什么是泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,
操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
解释extends 和super 泛型限定符-上界不存下界不取
extends 指定上界限,只能传入本类和子类
super 指定下界限,只能传入本类和父类
是否可以在static环境中访问非static变量?
这个要从java的内存机制去分析,首先当你New 一个对象的时候,并不是先在堆中为对象开辟内存空间,而是先将类中的静态方法(带有static修饰的静态函数)的代码加载到一个叫做方法区的地方,然后再在堆内存中创建对象。所以说静态方法会随着类的加载而被加载。当你new一个对象时,该对象存在于对内存中,this关键字一般指该对象,但是如果没有new对象,而是通过类名调用该类的静态方法也可以。
程序最终都是在内存中执行,变量只有在内存中占有一席之地时才会被访问,类的静态成员(变态和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问,非静态成员(变量和方法)属于类的对象,所以只有在类的对象禅师(创建实例)的时候才会分配内存,然后通过类的对象去访问。
在一个类的静态成员中去访问非静态成员之所以会出错是因为在类的非静态成员不存在的时候静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
那类是什么时候被加载呢?在需要调用的时候被加载
谈谈如何通过反射创建对象?
方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()
方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”);
Java支持多继承么?
java不支持多继承,只支持单继承(即一个类只能有一个父类)。但是java接口支持多继承,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)
接口和抽象类的区别是什么?
一 接口和抽象类的相似性
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
二 接口和抽象类的区别
- (不能为普通方法提供方法体)接口里只能包含抽象方法,静态方法和默认方法(加default),不能为普通方法提供方法实现,抽象类则完全可以包含普通方法,接口中的普通方法默认为抽象方法。
- (public static final 赋值)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的,并且必须赋值,否则通不过编译。
- (是否有构造器)接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
- (不能包含初始化块)接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
- (继承一个抽象类、多个接口)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
Comparable和Comparator接口是干什么的?列出它们的区别。
Comparable接口位于 java.lang包下,Comparator接口位于java.util包下。
Comparable: 内部比较器,一个类如果想要使用 Collections.sort(list) 方法进行排序,则需要实现该接口
Comparator: 外部比较器用于对那些没有实现Comparable接口或者对已经实现的Comparable中的排序规则不满意进行排序.无需改变类的结构,更加灵活。(策略模式)
面向对象的特征有哪些方面
(1)继承:就是保留父类的属性,开扩新的东西。通过子类可以实现继承,子类继承父类的所有状态和行为,同时添加自身的状态和行为。
(2)封装:就是类的私有化。将代码及处理数据绑定在一起的一种编程机制,该机制保证程序和数据不受外部干扰。
(3)多态:是允许将父对象设置成为和一个和多个它的子对象相等的技术。包括重载和重写。重载为编译时多态,重写是运行时多态。
final, finally, finalize的区别。
final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。
finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。
finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated
Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
方法的重写Override和重载Overload是Java多态性的不同表现。重写Override是父类与子类之间多态性的一种表现。重载Overload是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,那么我们说该方法被重写了。子类的对象使用这个方法时,将调用子类中的定义。对子类而言,父类中的定义如同被“屏蔽”了一样。关于重载,如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,也就是参数签名不同,这种情况出现方法的重载。重载的方法是可以改变返回值的类型。
每个重载的方法都必须有一个独一无二的参数类型列表。甚至参数顺序不同也足以区分两个方法,在区分重载方法时候,以类名和方法的形参作为标准,不以方法的返回值来区分。
Static Nested Class 和 Inner Class的不同
Nested Class 一般是C++的说法,Inner Class 一般是JAVA的说法。
Nested class分为静态Static nested class 的和非静态的 inner class,
静态的Static nested class是不可以直接调用它的外部类enclosing class的,但是可以通过外部类的引用来调用,就像你在一个类中写了main方法一样。
非静态类inner class 可以自由的引用外部类的属性和方法,但是它与一个实例绑定在了一起,不可以定义静态的属性、方法 。
Inner Class(内部类)定义在类中的类。
Nested Class(嵌套类)是静态(static)内部类。1. 要创建嵌套类的对象,并不需要其外围类的对象。 2. 不能从嵌套类的对象中访问非静态的外围类对象。
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
传递的是值,只不过这个值是引用的地址值,地址值指向对象,所以改变的对象的内容,地址并没有改变
Java的接口和C++的虚类的相同和不同处。
C++虚类相当于java中的抽象类,与接口的不同处是:
1.一个子类只能继承一个抽象类(虚类),但能实现多个接口
2.一个抽象类可以有构造方法,接口没有构造方法
3.一个抽象类中的方法不一定是抽象方法,即其中的方法可以有实现(有方法体),接口中的方法都是抽象方法,不能有方法体,只有方法声明
4.一个抽象类可以是public、private、protected、default,接口只有public
5.一个抽象类中的方法可以是public、private、protected、default,接口中的方法只能是public和default修饰,实际上都是public的abstract方法
相同之处是:
都不能实例化。
JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?
throws是获取异常
throw是抛出异常
try是将会发生异常的语句括起来,从而进行异常的处理,
catch是如果有异常就会执行他里面的语句,
而finally不论是否有异常都会进行执行的语句。
内部类可以引用他包含类的成员吗?有没有什么限制?
当内部类为静态内部类时他只能调用外部类的静态方法。如果内部类为非静态内部类时则调用无限制。
主要是编译时就会加载静态类,而非静态类在运行时才会加载。所以如果静态类部类无法调用非静态外部类
两个对象值相同(x.equals(y) == true),但却可有不同的hash code说法是否正确?
如果此对象重写了equals方法,那么可能出现这两个对象的equals相同,而hashcode不同。因此可以说它是对的。 但是,如果此对象继承Object,没有重写equals方法,那么就使用Object的equals方法,Object对象的equals方法默认是用==实现的,那么如果equals相同,hashcode一定相同。
如何通过反射获取和设置对象私有字段的值?
getDeclaredField方法
并且要设置访问权限为true ,setAccessible(true)
谈一下面向对象的”六原则一法则”。
单一职责原则,里氏替换原则,依赖倒置原则,开闭原则,接口隔离原则,合成聚合复用原则和迪米特法则
单一职责原则:一个类只做它该做的事情。(单一职责原则想表达的就是”高内聚”,写代码最终极的原则只有六个字”高内聚、低耦合”)
开闭原则:软件实体应当对扩展开放,对修改关闭。(在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱)
依赖倒转原则:面向接口编程。(该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代)
里氏替换原则:任何时候都可以用子类型替换掉父类型。(子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。)
接口隔离原则:接口要小而专,绝不能大而全。(臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。)
合成聚合复用原则:优先使用聚合或合成关系复用代码。(通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。)
迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。(迪米特法则简单的说就是如何做到”低耦合”,门面模式和调停者模式就是对迪米特法则的践行。Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作。迪米特法则用通俗的话来将就是不要和陌生人打交道,如果真的需要,找一个自己的朋友,让他替你和陌生人打交道。)
请问Query接口的list方法和iterate方法有什么区别?
对于Query接口的list()方法与iterate()方法来说,都可以实现获取查询的对象,但是list()方法返回的每个对象都是完整的(对象中的每个属性都被表中的字段填充上了),而iterator()方法所返回的对象中仅包含了主键值(标识符),只有当你对iterator中的对象进行操作时,Hibernate才会向数据库再次发送SQL语句来获取该对象的属性值。
Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造方法。
hashCode()和equals()方法有什么联系?
前提: 谈到hashCode就不得不说equals方法,二者均是Object类里的方法。由于Object类是所有类的基类,所以一切类里都可以重写这两个方法。
原则 1 : 如果 x.equals(y) 返回 “true”,那么 x 和 y 的 hashCode() 必须相等 ;
原则 2 : 如果 x.equals(y) 返回 “false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等 ;
原则 3 : 如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 一定返回 “false” ;
原则 4 : 一般来讲,equals 这个方法是给用户调用的,而 hashcode 方法一般用户不会去调用 ;
原则 5 : 当一个对象类型作为集合对象的元素时,那么这个对象应该拥有自己的equals()和hashCode()设计,而且要遵守前面所说的几个原则。
4、集合
Map和ConcurrentHashMap的区别?
首先Map是接口,一般而言concurrentHashMap是线程安全的,具体实现
在1.7采取的segment分段锁,有点类似于16个线程安全的hashtable组合成了一个concurrenthashmap,不同分段操作不需要上锁,同一个分段才需要上锁,读不上锁,写上锁。锁的粒度更加精细。而1.8采取的AQS和CAS来实现【用了不少volatile】。
hashMap内部具体如何实现的?
HashMap的底层是基于数组+链接的一个复合数据结构,非同步的 允许null键值 继承于map接口来实现,通过put和get方法来进行数据的操作.数组被分为一个个的bucket.哈希值决定了键值对在数组中的位置.具有相同哈希值的键值对会组成链表,当链表长度超过阀值(8)的时候回触发树化,链表转换成红黑树.
如果hashMap的key是一个自定义的类,怎么办?
没问题,但是最好要重写hashcode方法,否则可能会出现对象是equals的,但放入hashset时却作为不同对象的问题。
ArrayList和LinkedList的区别,如果一直在list的尾部添加元素,用哪个效率高?
ArrayList 底层数据结构是一中线性的数据结构 ArrayList 可以理解为动态数组,它的容量能动态增长,该容量是指用来存储列表的数组的大小,随着向ArrayList中不断添加元素,其容量也自动增长, ArrayList 容许包括null在内所有的元素 ArrayList 是List接口的非同步实现 ArrayList 是有序 LinkedList 基于链表的list接口的非同步实现 LinkedList 是容许包括null在内的所有元素 LinkedList 是有序的 ArrayList 访问任意位置,效率高 LinkedList 两端数据操作效率高
HashMap底层,负载因子,为啥是2^n?
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法; 这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1), hash%length==hash&(length-1)的前提是length是2的n次方; 为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1; 例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了; 例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞; 其实就是按位“与”的时候,每一位都能 &1 ,也就是和1111……1111111进行与运算
ConcurrentHashMap锁加在了哪些地方?
1.7中不同的Segment,ConcurrentHashMap将数据分段,在读写的时候只加到相应的数据段上,这样在多线程的时候,可以读写其他段的数据,提高效率
1.8 中取消了segments字段,直接采用transient volatile HashEntry<k,v>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
TreeMap底层,红黑树原理?
TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点
ArrayList是否会越界?
会的,底层是数组实现,是数组就一定会有越界的问题存在
什么是TreeMap?
TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口,能按自然顺序或自定义顺序遍历
ConcurrentHashMap的原理是什么?
底层采用分段的数组+链表实现,线程安全
通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
https://www.cnblogs.com/heyonggang/p/9112731.html
https://www.cnblogs.com/banjinbaijiu/p/9147434.html
Java集合类框架的基本接口有哪些?
总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;
为什么集合类没有实现Cloneable和Serializable接口?
Cloneable.接口是用于浅克隆,而Serializable接口是用于深克隆,标识性接口,之所以用到克隆,有时需要把对象信息保存到本地磁盘,防止在传输时出现乱序,而那些容器没有这个必要,只是用来存储数据
什么是迭代器?
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
Iterator和ListIterator的区别是什么?
- iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);
- ListIterator有add()方法,可以向List中添加对象,而Iterator不能
- ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
- 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常
https://blog.csdn.net/qq_31780525/article/details/77431970
HashMap和Hashtable有什么区别?
线程安全性不同
https://blog.csdn.net/wangxing233/article/details/79452946
ArrayList,Vector,LinkedList的存储性能和特性是什么?
ArrayList 和Vector他们底层的实现都是一样的,都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。
LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。
Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。
Collection 和 Collections的区别。
java.util.Collection 是一个集合框架的父接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
List、Set、Map是否继承自Collection接口?
List,Set是,Map不是
List、Map、Set三个接口存取元素时,各有什么特点?
Set里面不允许有重复的元素,
存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。
取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。
List表示有先后顺序的集合,
存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。
取元素:方法1:Iterator接口取得所有,逐一遍历各个元素
方法2:调用get(index i)来明确说明取第几个。
Map是双列的集合,存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。
取元素:用get(Object key)方法根据key获得相应的value。
也可以获得所有的key的集合,还可以获得所有的value的集合,
还可以获得key和value组合成的Map.Entry对象的集合。
5、线程
多线程中的i++线程安全吗?为什么?
i++和++i都是i=i+1的意思,但是过程有些许区别:
i++:先赋值再自加。(例如:i=1;a=1+i++;结果为a=1+1=2,语句执行完后i再进行自加为2)
++i:先自加再赋值。(例如:i=1;a=1+++i;结果为a=1+(1+1)=3,i先自加为2再进行运算)
但是在单独使用时没有区别:如for(int i=0;i<10;i++){ }和for(int i=0;i<10;++i) { }没有区别。
i++和++i的线程安全分为两种情况:
1、如果i是局部变量(在方法里定义的),那么是线程安全的。因为局部变量是线程私有的,别的线程访问不到,其实也可以说没有线程安不安全之说,因为别的线程对他造不成影响。
2、如果i是全局变量(类的成员变量),那么是线程不安全的。因为如果是全局变量的话,同一进程中的不同线程都有可能访问到。
如果有大量线程同时执行i++操作,i变量的副本拷贝到每个线程的线程栈,当同时有两个线程栈以上的线程读取线程变量,假如此时是1的话,那么同时执行i++操作,再写入到全局变量,最后两个线程执行完,i会等于3而不会是2,所以,出现不安全性。
如何线程安全的实现一个计数器?
Java 提供了一组atomic class来帮助我们简化同步处理。基本工作原理是使用了同步synchronized的方法实现了对一个long, integer, 对象的增、减、赋值(更新)操作.
多线程同步的方法
synchronized关键字、wait和notify、重入锁、阻塞队列等
https://blog.csdn.net/scgyus/article/details/79499650
介绍一下生产者消费者模式?
生产者消费者模式:通过一个容器来解决生产者和消费者的强耦合关系,生产者生成数据无需等待消费者索取,消费者无需直接索要数据。两者并不进行任何通讯,而是通过容器来进行操作
作用:解耦、支持并发、支持忙闲不均。
线程,进程,然后线程创建有很大开销,怎么优化?
线程池。
线程池运行流程,参数,策略
线程池的工作流程:当一个任务通过execute(Runnable)方法欲添加到线程池时:
- 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
- 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
- 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过
handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 - 当线程池中的线程数量大于
corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
举个例子来说明一下线程池中的工作流程:
假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 413 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 1720 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。
7个参数:
- corePoolSize: 线程池维护线程的最少数量(也叫核心线程池数量)
- maximumPoolSize:线程池维护线程的最大数量
- keepAliveTime: 线程池维护线程所允许的空闲时间
- unit: 线程池维护线程所允许的空闲时间的单位
- workQueue: 线程池所使用的缓冲队列
- threadFactory:线程创建的工厂
- handler: 线程池对拒绝任务的处理策略
任务拒绝策略:当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
- 自定义策略,如果在使用过程中,Java对我们提供给我们的四种策略都不符合我们的要求,那我们可以自定义策略。
讲一下AQS吧。
AQS是一个并发包的基础组件,用来实现各种锁,各种同步组件。它包含了state变量,加锁线程,等待队列并发中的核心组件。
AQS的实现依赖内部的同步队列(FIFO双向队列),如果当前线程获取同步状态失败,AQS会将该线程以及等待状态等信息构造成一个Node,将其加入同步队列的尾部,同时阻塞当前线程,当同步状态释放时,唤醒队列的头节点。
https://www.cnblogs.com/waterystone/p/4920797.html
创建线程的方法,哪个更好,为什么?
需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法; 实现Runnalbe接口,重载Runnalbe接口中的run()方法。 实现Runnalbe接口更好,使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.
Java中有几种方式启动一个线程?
1、继承Thread类,新建一个当前类对象,并且运行其start()方法
2、实现Runnable接口,然后新建当前类对象,接着新建Thread对象时把当前类对象传进去,最后运行Thread对象的start()方法
3、实现Callable接口,新建当前类对象,在新建FutureTask类对象时传入当前类对象,接着新建Thread类对象时传入FutureTask类对象,最后运行Thread对象的start()方法
Java中有几种线程池?
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
自定义线程池:通过修改五大核心参数来控制
线程池有什么好处?
1、线程池的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
2、控制线程池的并发数
3、线程池可以对线程进行管理
线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
如何理解Java多线程回调方法?
所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。
下面看一个实际例子来理解:
本示例设置一个提问者,一个回答者,而回答者需要回答提问者一个很深奥的问题时,这时需要很多时间去查找,提问者又开始做其他的事情,
等回答者找到答案后,再把答案告诉提问者。
概括的解释下线程的几种可用状态。
新建状态(New):
当用new操作符创建一个线程时, 例如new Thread®,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
运行状态(Running)
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
阻塞状态(Blocked)
线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
…
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
5.死亡状态(Dead)
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false
同步方法和同步代码块的区别是什么?
语法不同。 同步块需要注明锁定对象,同步方法默认锁定this。 在静态方法中,都是默认锁定类对象。 在考虑性能方面,最好使用同步块来减少锁定范围提高并发效率。
在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
在 java 虚拟机中, 每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联, 为了实现监视器的互斥功能, 每个对象都关联着一把锁.
一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码
另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案
sleep() 和 wait() 有什么区别?
相同点:都可让线程处于冻结状态.
不同点:
1.wait()可以设置线程冻结的时间,也可以不设置冻结的时间,而sleep()必须设置冻结的时间.
2.wait()释放cpu资源,同时也释放了锁,而sleep()释放cpu资源,但不释放锁.
同步和异步有何异同,在什么情况下分别使用他们?举例说明。
同步异步:指的是需不需要等待返回结果;
同步:需要不断轮询数据是否准备好了,或者一直在等待数据准备好
异步:发送一个请求就立即返回,然后去干别的事情,当数据准备号了会通知进行相关处理。(同步的实时性比较号,异步的并发性能比较号)
阻塞和非阻塞:是指需不需要阻塞线程
阻塞:当前线程不执行别的事情,一直再等待
非阻塞:当前线程可以干别事情,间隔一段时间检查一下上次的数据有没有准备好;
设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。使用内部类实现线程,对j增减的时候没有考虑顺序问题。
1 | 链接:https://www.nowcoder.com/questionTerminal/8db05d0b47044b3f9605860451d63d25 |
启动一个线程是用run()还是start()?
start();
请说出你所知道的线程同步的方法
1 同步方法 2 同步块 3 wait 和 notify 4 volatile 5 Lock : ReentrantLock 6局部变量比如ThreadLocal 7 blockqueue
stop()和suspend()方法为何不推荐使用?
stop会导致不安全,为啥呢,如果在同步块执行一半时,stop来了,后面还没执行完呢,锁没了,线程退出了,别的线程又可以操作你的数据了,所以就是线程不安全了。 suspend会导致死锁,因为挂起后,是不释放锁的,别人也就阻塞着,如果没人唤醒,那就一直死锁。
线程的sleep()方法和yield()方法有什么区别?
1.sleep()方法给其他线程机会不考虑线程的优先级别,而yield()方法只会给相同运行级别或更高运行级别的线程运行
2.线程执行sleep()方法就会进入阻塞状态,执行yield()方法会转入就绪状态
3.sleep()方法声明抛出InterruptException,而yield()没有声明任何异常
4.sleep()方法比yield方法具有更好的移植性
当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
请说出与线程同步以及线程调度相关的方法。
-wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
-sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
-notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
-notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
什么是线程池(thread pool)?
线程池就是用来存放已经创建过的线程的容器,有任务时直接从线程池里获取,可以节省时间。
如何保证线程安全?
线程安全:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如何保证呢:
1、使用线程安全的类;
2、使用synchronized同步代码块,或者用Lock锁;
由于线程安全问题,使用synchronized同步代码块 原理:当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
3、多线程并发情况下,线程共享的变量改为方法局部级变量
如何处理项目的高并发、大数据
1.HTML静态化
2.文件服务器
3.负载均衡
4.反向代理
5.动静分离
6.数据库sql优化
7.缓存
8.数据库读写分离
9.数据库活跃数据分离
10.批量读取和延迟修改
11.数据库集群和库表散列
6、锁
讲一下非公平锁和公平锁在reetrantlock里的实现。
非公平锁: 当线程争夺锁的过程中,会先进行一次CAS尝试获取锁,若失败,则进入acquire(1)函数,进行一次tryAcquire再次尝试获取锁,若再次失败,那么就通过addWaiter将当前线程封装成node结点加入到Sync队列,这时候该线程只能乖乖等前面的线程执行完再轮到自己了。
公平锁: 当线程在获取锁的时候,会先判断Sync队列中是否有在等待获取资源的线程。若没有,则尝试获取锁,若有,那么就那么就通过addWaiter将当前线程封装成node结点加入到Sync队列中
讲一下synchronized,可重入怎么实现。
每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。
对象锁(synchronized method{})和类锁(static sychronized method{})的区别
对象锁也叫实例锁,对应synchronized关键字,当多个线程访问多个实例时,它们互不干扰,每个对象都拥有自己的锁,如果是单例模式下,那么就是变成和类锁一样的功能。对象锁防止在同一个时刻多个线程访问同一个对象的synchronized块。如果不是同一个对象就没有这样子的限制。
类锁对应的关键字是static sychronized,是一个全局锁,无论多少个对象否共享同一个锁(也可以锁定在该类的class上或者是classloader对象上),同样是保障同一个时刻多个线程同时访问同一个synchronized块,当一个线程在访问时,其他的线程等待。
什么是死锁(deadlock)?
死锁 :是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
原因:
(1) 因为系统资源不足。
(2) 资源分配不当等。
(3) 进程运行推进顺序不合适。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(3) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
其中最简单的方法就是线程都是以同样的顺序加锁和释放锁,也就是破坏了第四个条件。
如何确保N个线程可以访问N个资源同时又不导致死锁?
四个条件是死锁的必要条件,只要破坏其中任意一个条件,就可以避免死锁,其中最简单的就是破环循环等待条件。按同一顺序访问对象,加载锁,释放锁。
请你简述synchronized和java.util.concurrent.locks.Lock的异同?
相同点:两者都是用来实现对某个资源的同步。
两者区别如下:
(1) 用法不一样。synchronized可以用于修饰方法,也可以用在代码块中。Lock需要指定起始和终点位置,一般放在try-finally结构中,try开始执行lock方法,finally中执行unlock方法。synchronized是托管给JVM执行的,Lock是通过代码执行的。
(2) 性能不一样。在资源竞争不激烈情况下,synchronized的性能比Lock好,而在资源竞争激烈时,synchronized的性能下降很快,而Lock基本保持不变。
锁机制不一样。synchronized获得锁和释放锁都是在块结构中,获取多个锁时必须以相反顺序释放,并且自动释放锁。Lock需要开发人员手动释放锁,并且放在finally中。
7、JDK
Java中的LongAdder和AtomicLong的区别
JDK1.8引入了LongAdder类。CAS机制就是,在一个死循环内,不断尝试修改目标值,直到修改成功。如果竞争不激烈,那么修改成功的概率就很高,否则,修改失败的的概率就很高,在大量修改失败时,这些原子操作就会进行多次循环尝试,因此性能就会受到影响。 结合ConcurrentHashMap的实现思想,应该可以想到对一种传统AtomicInteger等原子类的改进思路。虽然CAS操作没有锁,但是像减少粒度这种分离热点的思想依然可以使用。将AtomicInteger的内部核心数据value分离成一个数组,每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。热点数据value被分离成多个单元cell,每个cell独自维护内部的值,当前对象的实际值由所有的cell累计合成,这样热点就进行了有效的分离,提高了并行度。
JDK和JRE的区别是什么?
1.JDK
JDK是Java Development Kit的缩写,是Java的开发工具包,主要包含了各种类库和工具,当然也包含了另外一个JRE.。那么为什么要包含另外一个JRE呢?而且<JDK安装目录>/JRE/bin目录下,包含有server一个文件夹~包含一个jvm.dll,这说明JDK提供了一个虚拟机。
另外,JDK的bin目录下有各种Java程序需要用到的命令,与JRE的bin目录最明显的区别就是JDK文件下才有javac,这一点很好理解,因为JRE只是一个运行环境而已,与开发无关。正因为如此,具备开发功能的JDK所包含的JRE下才会同时有server的JVM,而仅仅作为运行环境的JRE下,只需要server的jvm.dll就够了。
注意:JDK所提供的运行环境和工具度需要进行环境变量的配置以后,才能使用,最主要的配置就是把<JDK安装目录>/bin目录设置为Path环境变量值的一部分。
2.JRE
JRE是Java Runtime Environment的缩写,是Java程序的运行环境。既然是运行,当然要包含JVM,也就是所谓的Java虚拟机,还有所以的Java类库的class文件,都在lib目录下,并且都打包成了jar。
至于在Windows上的虚拟机是哪个文件呢?就是<JRE安装目录>/bin/server中的jvm.dll。
另外,安装JRE的时候安装程序会自动把JRE的java.exe添加到了系统变量中。系统变量Path的最前面有%SystemRoot%system32;%SystemRoot%;这样的配置,那样到Windows/system32目录下main去看看,会发现一个java.exe文件。这样就无需配置环境变量,也可以运行Java程序了。
3.JDK与JRE的区别
JDK是Java的开发工具,它不仅提供了Java程序运行所需的JRE,还提供了一系列的编译,运行等工具,如javac,java,javaw等。JRE只是Java程序的运行环境,它最核心的内容就是JVM(Java虚拟机)及核心类库。
4.Tomcat和JDK是什么关系
tomcat是java的web项目运行容器之一;
jdk是java运行环境。也就是说java没有jdk肯定是没法编译运行的。
java运行必须依赖于jdk环境,但是不一定要用tomcat容器,如WebLogic、WebSphere等都是可以的。
8、反射
反射的实现与作用
它允许程序在运行时进行自我检查,同时也允许对其内部成员进行操作。反射机制提供的功能主要有:得到一个对象所属的类;获取一个类的所有成员变量和方法;在运行时创建对象;在运行时调用对象的方法
https://blog.csdn.net/SongYuxinIT/article/details/81872066
9、JVM
JVM回收算法和回收器,CMS采用哪种回收算法,怎么解决内存碎片问题?
标记清除算法、复制算法、标记整理
CMS采用标记清除
https://www.cnblogs.com/aspirant/p/8662690.html
类加载过程
类加载过程分为:加载——验证——准备——解析——初始化
加载:又分为三个阶段:
(1)通过一个类的全限定名来获取定义此类的二进制字节流;
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
JVM分区
Java堆,虚拟机栈,本地方法栈,方法区,程序计数器
eden区,survial区?
新生代有一个较大的Eden区和两个较小的Survivor区组成,绝大多数新创建的对象都是在Eden区分配的,其中大多数对象很快消亡。Eden是一块连续的内存,所以分配内存的速度很快。
首先,Eden满时,进行一次minor gc ,将存活 的对象复制到 To Survivor(以下简称To),清除Eden消亡的对象。当Eden再次满时,进行minor gc,To中能够晋升的移动到老年代,存活的对象复制到From。
清空Eden和To,如此切换(默认15),将存活的对象迁移到老年代
JAVA虚拟机的作用?
解释运行字节码程序消除平台相关性。 jvm将java字节码解释为具体平台的具体指令。一般的高级语言如要在不同的平台上运行,至少需要编译成不同的目标代码。而引入JVM后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
GC中如何判断对象需要被回收?
gc用到垃圾回收机制算法,判断是否是垃圾,从而进行回收。 引用可达法法,程序运行从开始,每次引用对象,都将对引用的对象进行连接起来,到最后形成一张网,没有在这张网上的对象则被认为是垃圾对象。 还有引用计数法,对于对象的引用,每引用一次计数器加一,引用失败,计数器减一,当计数器一段时间为0,则可以被认为是垃圾。
JAVA虚拟机中,哪些可作为ROOT对象?
Java程序是怎么运行的?
1)加载类定义进入方法区
2)初始化类定义中的静态成员变量 & 常量
3)执行入口类的main方法
在后续程序的执行过程会创建一些对象,并调用一些方法。
每调用一个方法,都会进行压栈,如果是Java方法,则压虚拟机栈;如果是native方法,则压本地方法栈。
在方法中也会创建一些对象,每创建一个对象,就会在堆中占据一块内存。
在方法中也会调用对象的方法。
在方法中也会调用类的静态成员变量 or 常量。
所以在进行垃圾回收的时候,可以从几个地方开始下手,然后一路往前走,最终没有触及的对象,都将被回收调。
这些root对象有哪些呢?
1)虚拟机栈中引用的对象
2)本地方法栈中引用的对象
3)方法区静态变量引用的对象
4)方法去常量引用的对象
JVM内存模型是什么?
https://blog.csdn.net/u011972171/article/details/80398771
jvm是如何实现线程?
.使用内核线程实现
内核线程(Kernel-Level Thread, KLT)就是直接由操作系统内核支持的线程。
用户态和内核态切换消耗内核资源
2 使用用户线程实现
3 用户线程加轻量级进程混合实现
java虚拟机的多线程是通过线程轮流切换分配处理执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条程序中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。
简单点说,对于单核处理器,是通过快速切换线程执行指令来达到多线程的,因为单核处理器同时只能处理一条指令,只是这种切换速度很快,我们根本不会感知到。
jvm最大内存限制多少
这个如果不使用-xx:Xmx -xx:Xms -xx:Permsize -xx:MaxPermsize参数进行设置的话,应该和不同版本的jdk的jvm最大内存限制相关吧。
1 | 公司 JVM版本 最大内存(兆)client 最大内存(兆)server |
什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?
一、什么是java虚拟机?
java虚拟机是执行字节码文件(.class)的虚拟机进程。
java源程序(.java)被编译器编译成字节码文件(.class)。然后字节码文件,将由java虚拟机,解释成机器码(不同平台的机器码不同)。利用机器码操作硬件和操作系统
二、为什么java被称为平台无关的编程语言?
因为不同的平台装有不同的JVM,它们能够将相同的.class文件,解释成不同平台所需要的机器码。正是因为有JVM的存在,java被称为平台无关的编程语言
描述一下JVM加载class文件的原理机制?
委托机制,可见性机制,单一性机制 父类静态代码块,子类静态代码块,父类构造代码块和构造方法,子类构造代码块和构造方法。 启动类加载器,扩展类加载器,应用程序类加载器。双亲委派模型
10、GC
java中内存泄露是啥,什么时候出现内存泄露?
java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
1.集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收。而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等即有静态引用或final一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。
2.单例模式。不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露
minor gc如果运行的很频繁,可能是什么原因引起的,minor gc如果运行的很慢,可能是什么原因引起的?
minor gc运行的很频繁可能是什么原因引起的?
1、 产生了太多朝生夕灭的对象导致需要频繁minor gc
2、 新生代空间设置的比较小
minor gc运行的很慢有可能是什么原因引起的?
1、 新生代空间设置过大。
2、 对象引用链较长,进行可达性分析时间较长。
3、 新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。
4、 内存分配担保失败,由minor gc转化为full gc
5、 采用的垃圾收集器效率较低,比如新生代使用serial收集器
阐述GC算法
标记清除算法:首先先标记,然后统一把标记的对象依次清除,缺点是CPU消耗大,极易出现内存碎片,所以一般用于老年代。
复制算法:把内存区域分成俩块,每次只使用其中一块,然后把还存活的对象放在另一块中,清空原先的块,这样的话不会出现内存碎片。新生代常用的。
复制整理:指针碰撞,将使用过的对象移动到内存的一段,不用的放在另一端。
分代收集:根据不同代的区别,使用符合不同代的算法。
简单来说minorGC发生在新生代,频繁而且需要开销小,所以采取复制算法。
老年代:对象相较于新生代gc不频繁且对象少,采取标记清除或者标记整理算法。
GC是什么? 为什么要有GC?
Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代 码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。
电脑的内存大小的不变的,当我们使用对象的时候,如使用New关键字的时候,就会在内存中生产一个对象,但是我们在使用JAVA开发的时候,当一个对象使用完毕之后我们并没有手动的释放那个对象所占用的内存,就这样在使用程序的过程中,对象越来越多,当内存存放不了这么多对象的时候,电脑就会崩溃了,JAVA为了解决这个问题就推出了这个自动清除无用对象的功能,或者叫机制,这就是GC
垃圾回收的优点和原理。并考虑2种回收机制
由于有个垃圾回收机制,java中的对象不再有“作用域”的概念,只有对象的引用才有作用域。垃圾回收可以有效的防止内存泄漏,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收,java语言没有提供释放已分配内存的显示操作方法。
回收机制有分代复制垃圾回收和标记垃圾回收、增量垃圾回收
java中会存在内存泄漏吗,请简单描述。
java中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露
内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露
https://blog.csdn.net/coodlong/article/details/50836613
垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?(垃圾回收)
垃圾回收器是一个级别很低的线程,它通过不定时监测程序使用的内存中被占用的动态分配的内存内的对象是否还存在它的引用来判断是否该回收那个内存单元,如果不存在则回收,否则相反,并不是只要监测到就会回收的,因为垃圾回收器线程的低级别,所以当另一个级别比它高的线程跟他同时竞争运行时间时,前者优先运行,我们通过Thread或者继承Runnable的线程都级别都比它高,所以你无法知道垃圾回收器何时回收,System.gc()只是建议垃圾回收器进行回收处理,调用它并不能保证它回立即回收,
11、IO和NIO、AIO
IO的基本常识
1.同步
用户进程触发IO操作并等待或者轮询的去查看IO操作是否完成
2.异步
用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程继续处理
3.阻塞
当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务
4.非阻塞
当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
BIO,NIO,AIO可以简述如下:
BIO是同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO是同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO是异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高
NIO方式适用于连接数目多且连接比较短的架构,可充分利用服务器资源
AIO方式使用于连接数目多且连接比较长的架构,充分调用OS参与并发操作
怎么打印日志?
使用log4j和slf4j实现日志打印
运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。
error和exception有什么区别?
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked)。
2.表示一个由程序员导致的错误。
3.应该在应用程序级被处理。
Error:
1.总是不可控制的(unchecked)。
2.经常用来用于表示系统错误或低层资源的错误。
3.如何可能的话,应该在系统级被捕捉。
https://blog.csdn.net/min996358312/article/details/65729617
给我一个你最常见到的runtime exception
1,当试图将对象强制转换为不是实例的子类时,抛出该异常(ClassCastException)
1 | Object x = new Integer(0); |
2,一个整数“除以零”时,抛出ArithmeticException异常。
1 | int a=5/0; |
3, 当应用程序试图在需要对象的地方使用 null 时,抛出NullPointerException异常
1 | String s=null; |
1 | "hello".indexOf(-1); |
5,如果应用程序试图创建大小为负的数组,则抛出NegativeArraySizeException异常。
1 | String[] ss=new String[-1]; |
Java中的异常处理机制的简单原理和应用。
java异常处理机制可以从两个方面来描述,当一个java程序违反了java语义的时候,JVM虚拟机就会抛出一个异常,比如说当遇到的null的时候,会抛出一个nullpointExcepiton,当遇到下标越界的时候就会抛出indexoutofbroundsException,除此之外,程序员还可以自定义异常,去拓展这种语义的检查,并在合适的时机,通过throw关键字抛出异常。其中,try{}是监控的代码语句块,catch{}是处理异常,finally{}语句块无论是否发生异常都会执行
java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.
什么是java序列化,如何实现java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化(将对象转换成二进制)。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
https://blog.csdn.net/qq_35868412/article/details/86978141
运行时异常与受检异常有什么区别?
受检查异常表示程序可以处理的异常,如果抛出异常的方法本身不能处理它,那么方法调用者应该去处理它,从而使程序恢复运行,不至于终止程序。例如,喷墨打印机在打印文件时,如果纸用完或者墨水用完,就会暂停打印,等待用户添加打印纸或更换墨盒,如果用户添加了打印纸或更换了墨盒,就能继续打印。
运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现了错误操作,建议终止程序,因此Java编译器不检查这种异常。
算法与数据结构
1、哈希
hashset存的数是有序的吗?
hashset继承的是set接口,set是无序集合
Object作为HashMap的key的话,对Object有什么要求吗?
Hashmap不允许有重复的key,所以要重写它的hashcode和equal方法,以便确认key是否重复
一致性哈希算法
在日常工作中,经常有这样的情况,我们需要做hash,散列开数据到不同的区或节点。目标要的结果是要均匀散列,避免某个节点积累大量的数据,出现倾斜情况。
比如目前有N台机器,过来的数据key,需要做散列key%N,分发到对应的节点上。
一致性哈希算法原理:
为了解决hash倾斜难题,一致性算法是这样的,节点和节点形成一个环。比如
A->B->C->A,这样一个环。数字hash后落在环上,而不是落到某个node。比如落在a~b node之间,通过顺时针转,这个数字归b节点管。
但是如果节点很少,同样容易出现倾斜,负载不均衡问题。所以一致性哈希算法,引入了虚拟节点,在整个环上,均衡增加若干个节点。比如a1,a2,b1,b2,c1,c2,a1和a2都是属于A节点的。
通过让闭环上的节点增加,来平衡各个节点散列的值。
什么是hashmap?
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。 HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
Java中的HashMap的工作原理是什么?
hashmap是一个key-value键值对的数据结构,从结构上来讲在jdk1.8之前是用数组加链表的方式实现,jdk1.8加了红黑树,hashmap数组的默认初始长度是16,hashmap数组只允许一个key为null,允许多个value为null
hashmap的内部实现,hashmap是使用数组+链表+红黑树的形式实现的,其中数组是一个一个Node[]数组,我们叫他hash桶数组,它上面存放的是key-value键值对的节点。HashMap是用hash表来存储的,在hashmap里为解决hash冲突,使用链地址法,简单来说就是数组加链表的形式来解决,当数据被hash后,得到数组下标,把数据放在对应下表的链表中。
然后再说一下hashmap的方法实现
put方法,put方法的第一步,就是计算出要put元素在hash桶数组中的索引位置,得到索引位置需要三步,去put元素key的hashcode值,高位运算,取模运算,高位运算就是用第一步得到的值h,用h的高16位和低16位进行异或操作,第三步为了使hash桶数组元素分布更均匀,采用取模运算,取模运算就是用第二步得到的值和hash桶数组长度-1的值取与。这样得到的结果和传统取模运算结果一致,而且效率比取模运算高
jdk1.8中put方法的具体步骤,先判断hashmap是否为空,为空的话扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束
get方法就是计算出要获取元素的hash值,去对应位置取即可。
扩容机制,hashmap的扩容中主要进行两部,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,在jdk1.8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
3.hashmap大小为什么是2的幂次方
在计算插入元素在hash桶数组的索引时第三步,为了使元素分布的更加均匀,用取模操作,但是传统取模操作效率低,然后优化成h&(length-1),设置成2幂次方,是因为2的幂次方-1后的值每一位上都是1,然后与第二步计算出的h值与的时候,最终的结果只和key的hashcode值本身有关,这样不会造成空间浪费并且分布均匀,如果不是2的幂次方
如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。
hashCode()和equals()方法的重要性体现在什么地方?
比如Java中HashMap使用hashcode()和equals()来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确使用这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认定为是相等的。而且,这两个方法也会用来发现重复元素。所以,这两个的实现对HashMap的精确性和正确性是至关重要的。
2、树
说一下B+树和B-树?
m 阶的 B+ 树和 B- 树主要区别有三:
( 1 )有 n 棵子树的结点中含有 n ( B- 树中 n-1 )个关键字;
( 2 ) B+ 树叶子结点包含了全部关键字信息,及指向含关键字记录的指针,且叶子结点本身依关键字大小自小到大顺序链接;
( 3 ) B+ 树的非终端结点可以看成是索引部分,结点中只含其子树(根结点)中最大(或最小)关键字。 B+ 树的查找既可以顺序查找,也可以随机查找, B- 只能顺序查找。
怎么求一个二叉树的深度?手撕代码?
1.后序遍历,最长栈长即为树的深度
2.递归,最后比较即可
3.遍历求层,层次即为深度
算法题:二叉树层序遍历,进一步提问:要求每层打印出一个换行符
只需要在普通的BST基础上增加一个判断当前queue中数量的size即可,每个size就代表当前层的节点数量
https://www.jianshu.com/p/582d42dc8129
二叉树任意两个节点之间路径的最大长度
https://blog.csdn.net/patkritLee/article/details/52162806
https://blog.csdn.net/liuyi1207164339/article/details/50917503
如何实现二叉树的深度?
1 | static class TreeNode { |
如何打印二叉树每层的节点?
1 | /** |
TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象必须实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)
3、遍历
编程题:写一个函数,找到一个文件夹下所有文件,包括子文件夹
https://blog.csdn.net/qq_38977097/article/details/88853568
1 | import java.io.File; |
二叉树 Z 字型遍历
1 | import java.util.ArrayList; |
4、链表
反转单链表
1 | /* |
随机链表的复制
1 | /* |
链表-奇数位升序偶数位降序-让链表变成升序
https://www.cnblogs.com/DarrenChan/p/8764608.html
bucket如果用链表存储,它的缺点是什么?
不支持随机访问,查找的时间复杂度是O(n)
如何判断链表检测环
https://blog.csdn.net/yangruxi/article/details/80333000
1 | public static boolean isLoop(Node head) { |
5、数组
寻找一数组中前K个最大的数
https://blog.csdn.net/zhou15755387780/article/details/81318105
1 | package com.Test; |
求一个数组中连续子向量的最大和
1 | public class Solution { |
找出数组中和为S的一对组合,找出一组就行
1 | public class NumComberAll { |
一个数组,除一个元素外其它都是两两相等,求那个元素?
1 | int singleNumber(int A[], int n) { |
算法题:将一个二维数组顺时针旋转90度,说一下思路。
https://blog.csdn.net/peach90/article/details/40422097
6、排序
排序算法知道哪些,时间复杂度是多少,解释一下快排?
直接插入排序,选择排序,冒泡排序:O(n*n) ,快速排序,归并排序,堆排序:O(nlog2n), 希尔排序:O(n√n) ,基数排序:O(d(r+n)), 快速排序:每次选择一个枢纽值,比它大的放在右边,比它小的放在左边,每一趟排序都会使一个数放到最终的位置。
如何得到一个数据流中的中位数?
1 | private int count = 0; |
堆排序的原理是什么?
堆是一个完全二叉树,如果按照层序遍历结果存储为数组,下标为i且根节点i=0,则满足Key[i]>=Key[2i+1]&&Key[i]>=key[2i+2]的称为大根堆,即根结点大于子结点,堆顶为最大值。 堆排序第一步建堆,即将输入序列看作是层序遍历结果,然后按顺序写成完全二叉树的形式。第二步调整堆,即从最后一个非叶结点开始调整,它的数组下标为最后一个数的下标-1之后除以2,保证这个结点比子结点大。然后下标减一继续调整,交换结点之后的孩子结点有可能不满足堆的性质,继续调整直到下标为0,这里有递归和非递归两种方法。 堆排序就是根据前边建好的堆先取出根结点和最后一个结点交换,然后对前边len-1个结点进行堆调整,再取出根结点和倒数第二个结点交换,对前边len-2个结点堆调整,以此类推直到所有结点都取出。 heapAdjust函数可看做每次都从当前结点走到叶子节点,数高度为log(n+1)向上取整,所以复杂度可视为O(logn),简单起见,不必考虑到底是从根节点到达叶结点还是从中间某节点到达叶结点。建堆时调用heapAdjust函数n/2次,排序时调用heapAdjust函数n-1次,得到三种情况下的复杂度都是O(logn)* (n/2)+O(logn)*(n-1),化简为O(nlogn)。空间复杂度O(1)。是不稳定的排序
归并排序的原理是什么?
归并排序是一种递归算法,不断将列表拆分为一半,如果列表为空或有一个项,则按定义进行排序。如果列表有多个项,我们分割列表,并递归调用两个半部分的合并排序。一旦对两半排序完成,获取两个较小的排序列表并将它们组合成单个排序的新列表的过程
如何用java写一个冒泡排序?
https://www.jianshu.com/p/f31de0e89f7e
1 | public static void bubbleSort(int[] arr) { |
7、堆与栈
heap和stack有什么区别。
要点:堆:顺序随意 栈:后进先出(Last-In/First-Out)
1.堆栈空间分配
①栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
②堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
2.堆栈缓存方式
①栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
②堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
3.堆栈数据结构区别
①堆(数据结构):堆可以被看成是一棵完全二叉树树,如:堆排序。
②栈(数据结构):一种先进后出的数据结构。
解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。
堆区:专门用来保存对象的实例(new 创建的对象和数组),实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在Stack中)
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身.
3.一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。
栈区:对象实例在Heap 中分配好以后,需要在Stack中保存一个4字节的Heap内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例。
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
4.由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.
静态区/方法区:
1.方法区又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
3.全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
8、队列
什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先级堆的无界队列。它的元素是按照自然顺序排序的。在创建元素的时候,我们给它一个一个负责排序的比较器。PriorityQueue不允许null值,因为
它们没有自然排序,或者说没有任何相关联的比较器。最后PriorityQueue不是线程安全的,出对和入队的时间复杂度都是O(log(n))
9、高级算法
题目:Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.Could you do both operations in O(1) time complexity?
1 | class LFUCache { |
id全局唯一且自增,如何实现?
id type primary key auto_increament
如何设计算法压缩一段URL?
常用的url压缩算法是短地址映射法。具体步骤是:
① 将长网址用md5算法生成32位签名串,分为4段,,每段8个字符;
② 对这4段循环处理,取每段的8个字符, 将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理;
③ 将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串;
④ 这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址。
为什么要设计后缀表达式,有什么好处?
https://blog.csdn.net/xiazdong/article/details/7272693
后缀表达式的特点就是计算机运算非常方便,需要用到栈;计算机处理过程只需要顺序读入,如果遇到数字,则放入栈中,如果是运算符,则将两个栈中数字取出进行运算;
LRU算法的实现原理?
https://blog.csdn.net/elricboa/article/details/78847305
LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
设计模式
1、结构型模式
java中有哪些代理模式?
https://blog.csdn.net/gdutxiaoxu/article/details/81394050
如何实现动态代理
https://blog.csdn.net/HEYUTAO007/article/details/49738887
jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。
IO流熟悉吗,用的什么设计模式?
https://blog.csdn.net/yjw123456/article/details/80094801
即装饰模式和适配器模式。
2、创建型模式
介绍一下单例模式?懒汉式的单例模式如何实现单例?
https://blog.csdn.net/mbh12333/article/details/82258455
3、行为型模式
介绍一下策略模式?
https://blog.csdn.net/qq_22314145/article/details/82664481
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
一般情况下我们是将一种行为写成一个类方法,比如计算器类中有加、减、乘、除四种方法,而策略模式则是将每一种算法都写成一个类,然后动态的选择使用哪一个算法
设计模式了解哪些,手写一下观察者模式?
https://blog.csdn.net/liuguangxu1988/article/details/82055853
4、模式汇总
说说你所熟悉或听说过的j2ee中的几种常用模式?及对设计模式的一些看法
j2ee常用的设计模式?说明工厂模式。
Java中的23种设计模式:
Factory(工厂模式), Builder(建造模式), Factory Method(工厂方法模式),
Prototype(原始模型模式),Singleton(单例模式), Facade(门面模式),
Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),
Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),
Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),
Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),
Observer(观察者模式), State(状态模式), Strategy(策略模式),
Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)
工厂模式:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
开发中都用到了那些设计模式?用在什么场合?
每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。
singleton:单例,用来减少垃圾对象和缓存用
factory:工厂模式,用来解耦(呵呵,其实模式都是用来解耦的)
facade和decorator:封装接口
command:命令模式,传递
Iterator:用来遍历对象
Observer:用来监听状态变化(现在习惯用listener机制替代)
templete:模板模式,用来处理相同的操作步骤
strategy:策略模式,策略选择
proxy:用来附加功能,属性或隐蔽。
bridge也很实用,用来解耦工厂与产品搭配之类的选择
场景题
如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?
美团首页每天会从10000个商家里面推荐50个商家置顶,每个商家有一个权值,你如何来推荐?第二天怎么更新推荐的商家?可以借鉴下stackoverflow,视频网站等等的推荐算法。
微信抢红包问题 悲观锁,乐观锁,存储过程放在mysql数据库中。
1000个任务,分给10个人做,你怎么分配,先在纸上写个最简单的版本,然后优化。
全局队列,把1000任务放在一个队列里面,然后每个人都是取,完成任务。分为10个队列,每个人分别到自己对应的队列中去取务。
保证发送消息的有序性,消息处理的有序性。
https://blog.csdn.net/fengqiangdu/article/details/96139151
如何把一个文件快速下发到100w个服务器
http://m.nowcoder.com/discuss/76829?type=0&pos=18
给每个组分配不同的IP段,怎么设计一种结构使的快速得知IP是哪个组的?
10亿个数,找出最大的10个。
建议一个大小为10的小根堆。
有几台机器存储着几亿淘宝搜索日志,你只有一台2g的电脑,怎么选出搜索热度最高的十个搜索关键词?
分布式集群中如何保证线程安全?
https://www.jianshu.com/p/8c9e98a6e936
给个淘宝场景,怎么设计一消息队列?
https://www.sohu.com/a/204619554_730031
10万个数,输出从小到大?
先划分成多个小文件,送进内存排序,然后再采用多路归并排序。
有十万个单词,找出重复次数最高十个?
1 | import java.util.*; |
25匹马,5个跑道,最少赛马多少次可以找出跑的最快的前三匹马
https://blog.csdn.net/wtwzd002/article/details/70154526
待补充。。。
参考链接
https://blog.csdn.net/u014543872/article/details/90312139
https://cloud.tencent.com/developer/article/1362755
https://blog.csdn.net/qq_39382769/article/details/88792554
https://blog.csdn.net/qq_41701956/article/details/84378302
https://www.cnblogs.com/yewsky/articles/1864934.html
https://blog.csdn.net/qq_38977097/article/details/88826939
https://blog.csdn.net/Norte_L/article/details/80250057
https://www.cnblogs.com/zhaideyou/p/5929977.html
https://www.cnblogs.com/zk753159/p/4966571.html
https://blog.csdn.net/qq_42322624/article/details/80470170
https://yq.aliyun.com/articles/635007
https://blog.csdn.net/natian306/article/details/18504111
https://blog.csdn.net/longfulong/article/details/78700239
https://blog.csdn.net/s10461/article/details/53941091
https://blog.csdn.net/snail_xinl/article/details/53427572
https://yq.aliyun.com/articles/635005
https://blog.csdn.net/qq_34602647/article/details/80560741
https://blog.csdn.net/lsqingfeng/article/details/80342620
https://www.cnblogs.com/heartstage/p/3365688.html
https://blog.csdn.net/Onty_dr/article/details/84889097
https://blog.csdn.net/qq_37113604/article/details/81353626
https://blog.csdn.net/cyywxy/article/details/81151104
https://blog.csdn.net/cyywxy/article/details/81151104
https://www.cnblogs.com/tjudzj/p/4459443.html
https://www.cnblogs.com/jiangyi-uestc/p/5682699.html
https://www.cnblogs.com/cielosun/p/6684775.html
https://www.cnblogs.com/dadonggg/p/7799344.html
https://blog.csdn.net/qq_42090683/article/details/83505979
https://blog.csdn.net/gaoyong_stone/article/details/79540242