1. Java 特性 (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内置模板引擎,自身就是模板语言.而Java Web需要使用JSP容器如Tomcat或第三方模板引擎.
2. 正则表达式及其用途
在编写处理字符串的程序时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些的工具。正则表达式就是记录文本规则的代码。计算机处理的信息大多都是字符串而非数字,正则表达式是进行字符串匹配和处理的最为强大的工具。
3. 比较一下java与Javasciprt。
JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun Microsystems公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言。JavaScript的前身是LiveScript;而Java的前身是Oak语言。
下面对两种语言间的异同作如下比较:
- 基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。
- 解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)
- 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
- 代码格式不一样。
4. java中跳出多重循环
最外层循环加一个标记A,然后break A;就可以跳出循环。
5. int 与 Integer 有什么区别
数据类型:
Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
如:
class AutoUnboxingTest {
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
}
}
6. java编写程序时如何输出遇到的某种编码字符,如iso8859-123这种类型的。
Public String translate (String str) {
String tempStr = “”;
try {
tempStr = new String(str.getBytes(“ISO-8859-1″), “GBK”); // 国标编码
tempStr = tempStr.trim();
}
catch (Exception e) {
System.err.println(e.getMessage());
}
return tempStr;
}
7. String与StringBuffer的区别。
JAVA 平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。
8. 请说明String是最基本的数据类型吗?
基本的数据类型只包含byte、int、char、long、float、double、boolean和short。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类。
9. 什么是值传递和引用传递?
值传递是对于基本类型而言的,传递的是改变了的一个副本,改变副本不影响原变量。
引用传递一般是对于对象型变量而言的,传递的是该对象的地址的一个副本,并不是对象本身。所以引用对象进行操作会同时改变原对象。 一般都认为java内的传递都是值传递。
9. 为什么会出现4.0-3.6=0.40000001这种现象?
原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
- Lamda表达式的优缺点。
优点: 简洁,非常容易并行计算,可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
11. 为什么重写equals还要重写hashcode?
HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的。HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上。重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对。
12. 请你介绍一下map的分类和常见的情况
Java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
Map 有四个子类,其中最常用的是hashmap,访问速度也是最快的,支持线程同步,具有最快的访问速度。
Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。
Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为NULL,允许多条记录的值为NULL。
HashMap不支持线程同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致性。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。
Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。
在遍历的时候会比HashMap慢TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。
13. 请你介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?
修饰静态方法以及同步代码块,锁的是类,修饰成员变量,线程获取的是当前调用该方法的对象实例的对象锁。
volatile关键字来保证有序性和可见性。
原子性: 即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死。
**可见性:**可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性: 即程序执行的顺序按照代码的先后顺序执行。
它的修饰对象有几种:
修饰一个类,其作用的范围是synchronized后面括号括起来的部分, 作用的对象是这个类的所有对象。
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法, 作用的对象是调用这个方法的对象;
修改一个静态的方法,其作用的范围是整个静态方法, 作用的对象是这个类的所有对象;
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象;
14. 链表和数组的区别,跳表的原理和插入过程
答: 不同:链表是链式的存储结构;数组是顺序的存储结构。
链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。
链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难;
数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。
相同:两种结构均可实现数据的顺序存储,构造出来的模型呈线性结构。
**跳跃表:**同样支持对数据进行高效的查找,插入和删除数据操作也比较简单,最重要的就是实现比较平衡二叉树真是轻量几个数量级。缺点就是存在一定数据冗余。
14. 工厂模式的优缺点
建立一个工厂类用来创建对象,其他类需要使用这些方法直接继承此工厂类即可利用其中的方法。
优点:
(1)工厂模式为了解耦:可以将对象的创建和使用分离开,如果不进行分离,不但违反了设计模式的开闭原则,需要使用另外一个子类的话,需要区修改源代码。
(2)工厂模式可以降低代码的重复。
(3)因为工厂管理了对象的创建逻辑,使用者并不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。
(4)可以通过参数设置,返回不同的构造函数,不需要修改使用类的地方。如果一个类有多个构造方法(构造的重写),我们也可以将它抽出来,放到工厂中,一个构造方法对应一个工厂方法并命名一个友好的名字,这样我们就不再只是根据参数的不同来判断,而是可以根据工厂的方法名来直观判断将要创建的对象的特点。这对于使用者来说,体验比较好。
15. ArrayList与LinkedList的区别?
ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合。
对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止。
新增和删除元素,一般LinkedList的速度要优于ArrayList。因为ArrayList在新增和删除元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改指针即可。
LinkedList集合不支持 高效的随机随机访问**(RandomAccess)**
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
16. HashMap的底层原理?
HashMap是以key-value存储的一种数据结构,平常使用很多,它在JDK1.7和JDK1.8中的实现各不相同,在JDK1.7中它的底层实现是数组加链表使用 Entry 类存储 Key 和 Value,,而在1.8之后底层实现则是数组加链表加红黑树使用 Node 类存储 Key 和 Value。Node树的源码。
每一个节点会保存自身的hash值,key和value值。
因为数组的大小是有限的,而现实中的数据量是非常大的,为了解决所遇到的这种情况,JDK1.8做出改进-引入红黑树。其原则如下:当链表的长度大于8的时候,会将链表转换成红黑树,但是转换之前会查看table数组的长度是否大于64,如果数组的长度小于 64,那么 HashMap 会优先选择对数组进行扩容 resize,而不是把链表转换成红黑树。
当有新的节点插入链表时是如何插入的?(1.7和1.8)
1.7 : 头插法 多线程的情况下,容易造成循环链表。
1.8 : 尾插法
HashMap线程不安全的原因,对应于jdk1.7与1.8.
1)JDK1.7不安全是因为采用了头插法,所以在多线程情况下可能会出现环形链表
2)JDK1.8线程不安全是因为put方法没有上锁,所以在多线程情况下可能出现某个线程插入的数据被覆盖。
17. 有哪些线程安全的集合类,讲一讲原理(HashTable,ConcurrentHashMap,CopyOnWriteArrayList)。
线程安全集合类可以分为三大类:
(1). 遗留的线程安全集合如 Hashtable , Vector(直接用synchronized修饰方法,效率低)
(2). 使用 Collections 装饰的线程安全集合(装饰器模式,使用synchronized修饰方法),如:
Collections.synchronizedCollection
Collections.synchronizedList
Collections.synchronizedMap
Collections.synchronizedSet
Collections.synchronizedNavigableMap
Collections.synchronizedNavigableSet
Collections.synchronizedSortedMap
Collections.synchronizedSortedSet
(3) . java.util.concurrent.*下的线程安全类,主要包含三类关键词: Blocking、CopyOnWrite、Concurrent。
Blocking 大部分实现基于锁,并提供用来阻塞的方法
CopyOnWrite 之类容器修改开销相对较重 (保护性拷贝)
Concurrent 类型的容器
内部很多操作使用 cas 优化,一般可以提供较高吞吐量弱一致性遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍 历,这时内容是旧的求大小弱一致性,size 操作未必是 100% 准确
读取弱一致性
对于非安全容器来讲,遍历时如果发生了修改,使用 fail-fast 机制也就是让遍历立刻失败,抛出 ConcurrentModificationException,不再继续遍历
- ConcurrentHashMap: 读的时候不加锁,写入数据时加入lock锁,实现了读写分离,它非常适合读操作频繁,写操作很少的情况。
- Hashtable 和Hashmap一样,它也是一个散列表,存储的内容时键值对(Key-Value)的映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、Java.io.Serializable接口。
Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。
Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。
线程安全是因为synchronized修饰。 - CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet。本文会对CopyOnWriteArrayList的实现原理及源码进行分析。
很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
优缺点分析
了解了CopyOnWriteArrayList的实现原理,分析它的优缺点及使用场景就很容易了。
优点:
读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了
缺点:
缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。
18.JVM的内存模型
JVM的空间分为:方法区(加载类的类定义数据,常量,静态变量,字符,字符串等),堆内存(存放实例对象),堆栈(存放创建的方法以栈帧的形式等),程序计数器(程序运行过程的记录器),本地方法栈 其中 方法区和堆是线程共享的,其他的都是非线程共享的。
19. 哪些可以作为GcRoot
GcRoot的种类:
(1)虚拟机栈:栈帧中的本地变量表引用的对象。
(2)native 方法引用的对象
(3)方法区中的静态变量和常量引用的对象。
20. 常见的垃圾回收器
(1) Serial收集器(新生代)
Serial 即串行的意思,也就是说它以串行的方式执行,它是单线程的收集器,只会使用一个线程进行垃圾收集工作,GC 线程工作时,其它所有线程都将停止工作。
使用复制算法收集新生代垃圾。
它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率,所以,它是 Client 场景下的默认新生代收集器。
显式的使用该垃圾收集器作为新生代垃圾收集器的方式:-XX:+UseSerialGC
(2)ParNew收集器(新生代)
就是 Serial 收集器的多线程版本,但要注意一点,ParNew 在单核环境下是不如 Serial 的,在多核的条件下才有优势。
使用复制算法收集新生代垃圾。
Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用
(3)Parallel Scavenge 收集器(新生代)
同样是多线程的收集器,其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是提高吞吐量(吞吐量 = 运行用户程序的时间 / (运行用户程序的时间 + 垃圾收集的时间))。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
使用复制算法收集新生代垃圾。
(4)Serial Old 收集器(老年代)
Serial 收集器的老年代版本,Client 场景下默认的老年代垃圾收集器。
使用标记-整理算法收集老年代垃圾
(5)Parallel Old 收集器(老年代)
Parallel Scavenge 收集器的老年代版本。
在注重吞吐量的场景下,可以采用 Parallel Scavenge + Parallel Old 的组合。
使用标记-整理算法收集老年代垃圾。
(6)CMS 收集器(老年代)
CMS(Concurrent Mark Sweep),收集器几乎占据着 JVM 老年代收集器的半壁江山,它划时代的意义就在于垃圾回收线程几乎能做到与用户线程同时工作。使用标记-清除算法收集老年代垃圾。
21. CMS垃圾收集器的回收过程
主要有四个步骤:
- 初始标记: 仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(Stop-the-world)
- 并发标记: 进行GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(Stop-the-world)
- 并发清除:
清理垃圾,不需要停顿
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
但 CMS 收集器也有如下缺点:
吞吐量低
无法处理浮动垃圾
标记 - 清除算法带来的内存空间碎片问题
22. volatile关键字的作用
volatile保证变量的可见性和有序性,主内存与线程内存中的值保持一致性。 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。要完全理解此关键字,要深刻理解MESI缓存协议。volatile可见性是通过汇编加上Lock前缀指令,触发底层的MESI缓存一致性协议来实现的。当然这个协议有很多种,不过最常用的就是MESI。MESI表示四种状态,如下所示:
假如说当前有一个cpu去主内存拿到一个变量x的值初始为1,放到自己的工作内存中。此时它的状态就是独享状态E,然后此时另外一个cpu也拿到了这个x的值,放到自己的工作内存中。此时之前那个cpu会不断地监听内存总线,发现这个x有多个cpu在获取,那么这个时候这两个cpu所获得的x的值的状态就都是共享状态S。然后第一个cpu将自己工作内存中x的值带入到自己的ALU计算单元去进行计算,返回来x的值变为2,接着会告诉给内存总线,将此时自己的x的状态置为修改状态M。而另一个cpu此时也会去不断的监听内存总线,发现这个x已经有别的cpu将其置为了修改状态,所以自己内部的x的状态会被置为无效状态I,等待第一个cpu将修改后的值刷回到主内存后,重新去获取新的值。这个谁先改变x的值可能是同一时刻进行修改的,此时cpu就会通过底层硬件在同一个指令周期内进行裁决,裁决是谁进行修改的,就置为修改状态,而另一个就置为无效状态,被丢弃或者是被覆盖(有争论)。
此不能保证原子性,即此线程是不安全的。