1.Java常见异常

java.lang.NullPointerException(空指针异常)

java.lang.ClassNotFoundException(指定的类不存在)

java.lang.NumberFormatException(字符串转化为数组异常)

java.lang.IndexOutOfBoundsException(数组下标越界)

java.lang.IllegalArgumentException(方法的参数错误)

java.lang.IllegalAccessException(没有访问权限)

java.lang.ArithmetricException(数学运算异常)

java.lang.ClassCastException(数据类型转换异常)

java.lang.FileNotFoundException(文件未找到异常,IOException)

java.lang.ArrayStoreException(数组存储异常)

2.Java基本类型和引用类型的区别

基本数据类型:java中一共分为8种基本数据类型:byte,short,int,long,float,double,char,boolean,,其中byte,short,int,long是整形,float,double是浮点型,char是字符型,boolean是布尔型。

引用类型:java为每种基本类型都提供了对应的封装类型,分别为:Byte,Short,Integer,Long,Float,Double,Character,Boolean。引用类型是一种对象类型,它的值是指向内存空间的引用,就是地址。

区别:

(1)默认值,整形的默认值为0,浮点型默认值0.0,boolean默认值false,char默认值为null。对应的包装类型默认值都为null。

(2)内存分配,基本数据类型的变量是存储在栈内存中,而引用类型变量存储在栈内存中,保存的是实际对象在堆内存中的地址,实际对象中保存这内容。

(3)自动装箱、自动拆箱:java从jdk1.5开始引入自动装箱和拆箱,使得基本数据类型与引用数据类型之间的转换变得简单。

3.“==”、equals的区别

“==”运算符用来比较两个变量的值是否相等,也就是说,该运算符用于比较变量对应的内存中所存储的数值是否相等,要比较两个基本类型的数据或两个引用变量是否相等,只能用“==”。

Object类中定义的equals(Object)方法是直接使用“==”运算符比较的两个对象,所以在没有覆盖equals(Object)方法的情况下,两个符号一样,比较的是引用。但是equals方法被覆盖后,比较的就不是引用而是比较数据的内容。例如String类的equals方法适用于比较两个独立对象的内容是否相同,即堆中的内容是否相同。

4.Java集合类框架的基本接口有哪些?

集合类接口指定了一组叫元素的对象,集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序,有的集合允许重复的键,有的不允许。

Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:

Collection:代表一组对象,每一个对象都是它的子元素

Set:不包含重复元素的Collection

List:有顺序的collection,并且可以包含重复元素

Map:可以把key映射到value的对象,键不能重复

5.BIO NIO AIO

IO的方式通常分为几种,同步阻塞的BIO,同步非阻塞NIO,异步非阻塞AIO,。

BIO是一个连接一个线程,NIO是一个请求一个线程,AIO是一个有效请求一个线程。

(1)BIO:(两边都阻塞)在jdk1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一对线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求。如果有,客户端线程会等待请求结束后才继续执行。

BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,jdk1.4以前的唯一选择,但程序直观简单易理解。

(2)NIO:NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上线,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。

BIO和NIO一个比较重要的不同:是我们使用BIO的是往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。

NIO方式适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中,变成比较负责。jdk1.4开始支持。、

(3)AIO:与NIO不同,当进行读写操作时,只需要直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序。对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。

AIO方法适用于连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程复杂,jdk7开始支持。

6.什么是同步、异步、阻塞、非阻塞

同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情。而当IO操作已经完成的时候会得到IO完成的通知。

阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。

7.Java的四个基本特性

(1)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

(2)继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父亲,得到继承信息的类被称为子类,集成让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

(3)封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自制、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可以隐藏的东西,指向外界提供简单的编程接口。

(4)多态:多态是指允许不同子类型的对象对统一消息作出不同的响应。

8.多态的各种问题

(1)方法重载(Overload)实现的是编译时的多态(也称为前绑定)

(2)方法重写(Override)实现的是运行时的多态(也成为后绑定)。运行时的多态是面向对象最精髓的东西。

重载和重写,如何确定调用哪个参数:

(1)重载:重载发生在一个类里,同名的方法如果有不同的参数列表(类型、个数或者两者都不同)则视为重载

(2)重写:重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常,根据不同的子类对象确定调用的是哪个方法。

多态:

继承,重写,父类引用指向子类对象

举一个例子:在物流管理系统中,有两种用户,订购客户和卖房用户,两个客户都可以登录系统,他们有相同的方法Login,但登录之后他们会进入到不同的页面,也就是在登录的时候会有不同的操作,两种客户都继承了父类的Login方法,但对于不同的对象拥有不同的操作。

9.面向对象和面向过程的区别

(1)面向过程就像是一个细心地管家,事无巨细都要考虑到。面向对象就像是个家用电器,你只需要知道它的功能,不需要知道它的工作原理。

(2)面向过程是一种以“事件”为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按照顺序调用。面向对象是以“对象”为中心的编程思想。

(3)举个例子:汽车发动,汽车到站。这对于面向过程来说是两个事件,汽车启动是一个事件,汽车到站是另一个事件,面向过程编程的过程中我们关心的是事件,而不是汽车本身。针对上述两个事件形成两个函数之后依次调用。然而这对于面向对象来说,我们关心的是汽车这类对象,两个时间知识这类对象所具有的行为,而且对于这两个行为的顺序没有强制要求。

10.面向对象开发的六个基本原则,在项目中用过哪些原则

(1)单一职责:一个类只做它该做的事情(高内聚)

(2)开放封闭:软件实体应当对扩展开放,对修改关闭

(3)里氏替换:任何时候都可以用子类型替换掉父类型

(4)依赖倒置:面向接口编程

(5)合成聚合复用:优先使用聚合或合成关系复用代码

(6)接口隔离:接口要小而专,不能大而全

迪米特法则:又叫最少知识原则,一个对象应当对其他对象又尽可能少的了解。

项目中:单一职责,开放封闭,合成聚合复用(String),接口隔离。

11.static 和 final 的区别和用途

static:

(1)修饰变量:静态变量随着类加载时被完成初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。

(2)修饰方法:在类加载的时候就存在,不依赖任何实例,static方法必须实现,不能用abstract修饰

(3)修饰代码块:在类加载完之后就会执行代码块中的内容

(4)父类静态代码块-->子类静态代码块-->父类非静态代码块-->父类构造方法-->子类非静态代码块-->子类构造方法

final:

(1)修饰变量:①编译期常量:类加载的过程完成初始化,编译后带入到任何算式中,只能是基本类型。②运行时常量,基本数据类型或引用数据类型,引用不可变,但引用对象内容可变。

(2)修饰方法:不能被继承,不能被子类重写

(3)修饰类:不能被继承

(4)修饰形参:final形参不可变

12.HashMap和HashTable的区别,HashMap中的key可以使任何对象或数据类型吗?HashTable是安全的吗

HashMap和HashTable的区别:

(1)HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap,这个区别就像是ArrayList和Vector一样

(2)HashTable不允许null值(key和value都不可以),HashMap允许null(k和v都可以)

(3)两者的遍历方式大同小异,HashTable仅仅比HashMap多一个elements方法,HashTable和HashMap都能通过values()方法返回一个Collection,然后进行遍历处理。两者也都可以通过entrySet()方法返回一个Set,然后进行遍历处理。

(4)HashTable使用Enumeration,HashMap使用Iterator

(5)哈希值的使用不同,HashTable直接使用对象的HashCode。而HashMap重新计算Hash值,而且用于替代求模

(6)HashTable中hash数组默认大小为11,增加的方式是old*2+1,HashMap中hash数组的默认大小为16,而且一定为2的倍数

(7)HashTable基于Dictionary类,而HashMap基于AbstractMap类

HashMap中的key可以是任何对象或数据类型吗?

(1)可以为null,但不能是可变对象。如果是可变对象的话,对象中的属性改变,则对象HashCode也进行相应的改变,导致下次无法查找到已经存在Map的数据

(2)如果可变对象在HashMap中被作为key,那就要小心在改变对象状态的时候,不要改变它的哈希值了,我们只需要保证成员变量的改变能保证该对象的哈希值不变即可、

HashTable是线程安全的,其实是在对应的方法上添加了synchronized关键字进行修饰。由于在执行此方法的时候需要获得对象锁,则执行起来比较慢。所以现在如果为了保证线程安全的话,则使用ConcurrentHashMap。

13.HashMap和ConcurrentHashMap的区别,ConcurrentHashMap线程安全吗?如何保证的线程安全

区别

(1)HashMap是非线程安全的,ConcurrentHashMap是线程安全的

(2)ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment , 然后再在这个片段上面进行插入,而且这里还需要获取segment锁。

(3)ConcurrentHashMap让锁的粒度更精细一点,并发性能更好。

(jdk1.8之后,底层变为数组+链表/红黑树,segment的分段锁变为锁链表头结点或是红黑树的根节点)

ConcurrentHashMap线程安全吗?ConcurrentHashMap如何保证线程安全?

(1)HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,县城建就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConCurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其它段的数据也能被其他线程访问。

(2)get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才回加锁重读。get方法里将要使用的共享变量都定义成volatile,如用于统计当前segment大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写,(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。

(3)put方法首先定位到segment,然后在segment进行插入操作,插入操作需要经历两个步骤,第一步判断是否需要对segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。

线程安全

14.别人知道源码怎么实现的,故意构造相同的hash字符串进行攻击,怎么处理?jdk7如何处理HashCode攻击?

处理构造相同hash的字符串进行攻击:

(1)当客户端提交一个请求并附带参数的时候,web应用服务器会把我们的参数转化成一个HashMap存储,这个HashMap的逻辑结构如下:key1-->value1;

(2)但是物理存储结构是不同的,key值会被转化成HashCode,这个HashCode又会被转化成数组的下标,0-->value1;

(3)不同的String就会产生相同的HashCode而导致碰撞,碰撞之后的物理存储结构可能如下:0-->value1-->value2;

(4)限制post和get参数个数,越少越好;限制post数据包的大小;WAF

JDK7如何处理HashCode攻击:

HashMap会动态的使用一个专门的TreeMap实现来替换掉它

15.String,StringBuffer、StringBuilder以及对String不变性的理解

String,StringBuffer ,StringBuilder

(1)都是final类,都不允许被继承

(2)String长度是不可变的,StringBuffer和StringBuilder长度可变

(3)StringBuffer线程安全,StringBuilder不是线程安全的,但是它们两个中的所有方法都是相同的,StringBuffer在StringBuilder的方法之上添加了synchronized修饰,保证线程安全。

(4)StringBuilder比StringBuffer拥有更好的性能

(5)如果一个String类的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量,此时String的速度比另两个的性能好得多。(“a” + “b” 在内存中是“ab”存在的)

String不变性的理解:

(1)String类是被final修饰的,不能被继承

(2)在用+号连接字符串的时候回自动创建新的字符串

(3)String s = new String(“hello world”);可能创建两个对象也可能创建一个对象。如果静态区中有“hello world”字符串常量,则仅仅在堆中创建一个对象。如果静态区中没有“hello world”对象,则堆上和静态区都需要创建对象。

(4)在java中,通过使用+来串连字符串的时候,实际上底层会转成通过StringBuffer实例的append()方法来实现。

16.有重写Object的hashcode和toString吗?如果重写equals不重写hashcode会出现什么问题

String重写了Object类的hashcode方法和toString方法,当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相对相等的两个对象必须有相同的hashCode,在存储散列集合时(如set类),如果原对象equasl(新对象),但没有对hashCode重写,即两个对象拥有不同的hashCode,则在集合中将会存储两个值相同的对象,从而导致混淆。因此在重写equals方法时,通常必须重写hashCode方法。

object1.equals(object2)时为true,object1.hashCode() ==  object2.hashCode()为true

object1.hashCode() == object2.hashCode()为false时,object1.equals(object2)必定为fasle

object1.hashCode() == object2.hashCode()为true时,object1.equals(object2)不一定为true

17.什么是序列化?怎么序列化?什么时候序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,将数据分解成字节流,以便存储在文件中或在网络上传输。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着使用ObjectOutputStream对象的writeObect(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化是打开字节流并重构对象,对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。

什么时候使用序列化:

(1)对象序列化可以实现分布式对象,主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。

(2)java对象序列化不仅保留一个对象的数据,而是递归保存对象引用的每个对象的数据,可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

18.ArrayList , LinkedList , HashMap的底层实现?

ArrayList底层的实现就是一个数组(固定大小),当数组长度不够用的时候就会重新开辟一个新的数组,然后将原来的数据拷贝到新的数组中。(在调用add之前,回去ensureCapacity方法,然后维护了一个modCount域,会进行++操作,然后会去把当前集合的长度记录下来去和最小容量做一个对比,如果大的话,就会去重新计算这个容量然后将旧的element通过Arrays类的copyOf方法覆盖 原来已有的数据)

LinkedList底层是一个链表,是由java实现的一个双向链表,其节点如下:

class Node
{
    private Node privious;//指向前一个节点
    private Object value;//当前节点的value值
    private Node next;//指向下一个节点的值,类似于指针
}

然后实现其增删改查,和链表中的操作完全相同,而且是插入有序的。

HashMap底层是一个数组+链表的实现,基本原理:顶一个LinkedList的数组,然后然后将数据存储到这个链表数组内,意思是数组中的元素类型是LinkedList,数组的每个节点都可以是一个数组。

在插入元素的时候,首先根据key值来计算hash值h,然后计算h%数组长度得到一个是数字,然后把该对象插入到对应的数组元素内,

HashSet的底层时间是通过Map来实现的,Set中不允许有重复的元素,类似于集合,在HashSet实现的时候,通过Map来实现,每次往Set里添加数据,都会将数据设置为Map的key,Map的值设置一个默认值,因为Map的key不能重复,所以每次添加到Set内的数据也不能重复。