从网上找了些面试题,自己手工总结了理解了一下,如有理解错误,还请指正。
java基础
4. 描述一下ArrayList和LinkedList各自实现和区别
ArrayList是线性表 ,随机取数快,数据删除插入较慢,占用内存小
LinkedList是链表 插入删除快,查找遍历较慢占用内存稍大
5. Java中的队列都有哪些,有什么区别。
队列是实现生产者消费者的重要工具,java中的队列一般氛围阻塞的和非阻塞的,或者是根据需要有其他多重的实现方式:
ArrayBlockingQueue——带边界的阻塞式队列
ConcurrentLinkedDeque / ConcurrentLinkedQueue——无边界的链表队列(CAS)
DelayQueue——元素带有延迟的队列
LinkedBlockingDeque / LinkedBlockingQueue——链表队列(带锁),可设定是否带边界
LinkedTransferQueue——可将元素`transfer`进行w/o存储
PriorityBlockingQueue——并发PriorityQueue
SynchronousQueue——使用Queue接口进行Exchanger
Queue在设计之处就考虑了多线程的安全性,代表性的Queue的两种实现,表示实现线程安全的两套方案:•BlockingQueue,•ConcurrentLinkedQueue
前者是阻塞的同步的,后者是并发的。
这里提一下多线程安全问题的含义,之前有些搞混了。线程安全的类 ,指的是类内共享的全局变量的访问必须保证是不受多线程形式影响的。如果由于多线程的访问(比如修改、遍历、查看)而使这些变量结构被破坏或者针对这些变量操作的原子性被破坏,则这个类就不是线程安全的。实现线程安全又有两种方式,一种是同步的,将多线程的请求排个队,A线程进来了关上厕所门,其他线程在外面等待。A线程执行结束,B线程进入同样关上门。这样的操作保证了多线程下共享变量不会被修改,但是效率比较差,如果线程太多会导致严重的锁争用。还有一种方式是并发的,多个线程可以同时进入一段代码,如何保证线程安全呢?就是通过各个线程之间对共享变量的修改和操作都是可见的,你改了什么我必须第一时间知道,然后针对性地做一些操作。这样极大地提高了处理速度,只是算法的实现会非常的复杂。另外,类自身实现线程安全,只是代表类自身的api保持原子性,但是外部使用多个api的情况下,还是要手动进行加锁。concurrent包下的集合类实现,都是采用的并发方式,而Collections工具类包装的集合都是使用同步锁方式。其中CurrentHashMap应该是使用了分段锁和volatile。java并发:同步容器&并发容器 - 烟雨暗千家 - 博客园
LinkedList实现了Queue接口,也可以作为队列使用,但是不可以访问非Queue接口的方法,以免发生错乱。
BlocingQueue:ArrayBlockingQueue,LinkedBlockingQueue(入队和出队两把锁),DelayQueue,PriorityBlockingQueue(公平锁),SynchronousQueue。
ReentrantLock 只锁部分代码块
ConcurrentLinkedQueue:使用volatile关键字使共享变量可见,具体细节颇为复杂。
【Java多线程总结之聊一聊Queue - I'm Sure - ITeye技术网站 】【java中线程队列BlockingQueue的用法-shwenwen-ITPUB博客 】
这个坑很深,目前只能研究到这里了……
6. 反射中,Class.forName和classloader的区别。
Class.forName()方法底层调用Class.forName(className,true,classloader);表示在加载类的时候,执行初始化步骤;
ClassLoader.loadClass()方法底层调用ClassLoader.loadClass(className,false);表示不执行初始化步骤;
初始化步骤会初始化静态变量和静态代码块,如果此处没有初始化,那就需要在newInstance时,初始化了。根据不同的需要,调用相应的方法。
可能会引申到JVM装载类的过程:编译解释java文件为二进制class,装载class(通过类的完全限定名和类加载器实例),链接(对二进制字节码校验,初始化静态变量,找到父类接口和类,确定调用的方法和类都是存在的且具备权限),初始化(静态代码块其他)。
7. Java7、Java8的新特性
java7的更新多数是语法糖:
try包含资源代码块,自动释放;多个catch合并;更强的类型推测,new的时候不再需要指定类型,直接<>即可;二进制字面量支持;通过[],{}初始化集合。
java8
Lambda表达式与函数接口,接口支持默认方法和静态方法,可使用类似于C++的方法引用 class::method,重复注解,更强的类型推测,可将参数名称保存在字节码中,Optional 支持对null的处理,Stream 类似于mapReduce的集合处理方式,api可以直接连缀调用,Date/Time API 的更新,在兼容旧api分基础上提供了许多新的api,传闻java 9 将进一步支持货币api,base64直接支持,支持运行javascript以及js和java的相互调用(java8就有了?),jvm的permGen移除,类依赖分析器jdeps。
8. Java数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高。
数组 不能动态扩容,占内存小,添加删除操作稍慢,查询快。
链表 可以动态扩容,占内存大,添加删除操作快,查询慢。
9. Java内存泄露的问题调查定位:jmap,jstack的使用等等。
没用过不好说
10. string、stringbuilder、stringbuffer区别
String 是final的字符串,不可变。StringBuffer是线程安全的可变String,StringBuilder是简化的可变字符串,不是线程安全的。
11. hashtable和hashmap的区别
上面有所提及:
1. HashTable不支持用null作为key,而HashMap支持
2. HashTable是线程安全的map集合,HashMap不是线程安全的。
3. HashTable因为支持线程安全,所以没有HashMap的快速失败机制,但是HashTable不能保证多个api复合调用时保证安全。
4. Map想要支持多线程安全可以通过Collections工具类获取同步的Map,或者通过concurrent包中的concurrentHashMap返回并发的Map,而且可以与有序的TreeMap转化,很多时候,即便是多线程环境下也并不一定需要HashTable。
13 .异常的结构,运行时异常和非运行时异常,各举个例子。
单独再说
14. String 类的常用方法
length() , subString , indexOf , lastIndexOf , charAt , equals ,toUpperCase , equalsIgnoreCase , trim , toString , toLowerCase , replace , match(用于正则) , startsWith , compareTo. format(允许类似于C语言的输出字符串格式控制)
15. Java 的引用类型有哪几种
String , Object ,基本类型的包装类型:Integer,Long , Boolean , Short , Float,Double,Byte,Character,Void。Enum枚举类型
16. 抽象类和接口的区别
从语法上来看:
参数 | 抽象类 | 接口 |
默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
从本质上来看:
抽象类是对子类的一个抽象,抽象类提供了子类的某些方法约束,作为公共的大家都必须遵守的约束,有利于直接复用代码。不能确定的子类行为可以采用抽象方法定义,交给具体子类实现。抽象类更多的是在具体代码编写时的一种设计。
接口是定义一组约束规则,作为彻底的抽象层存在。它只要求实现者实现所有的方法,而不关心具体的实现方式,它只为子类指定功能,但是不存在对子类实现方式的要求,也就是说没有公共的已经实现的方法。更多的是用于软件架构设计中,对不同模块的抽象。
java8中支持了接口的默认方法,具体后面再研究。
18. java的基础类型和字节大小
java中基本数据类型(不是引用类型)有8种
byte | Byte | 1 | -128 | 127 |
char | Character | 2 | unicode-0 | unicode 2^16-1 |
short | Short | 2 | -2^15 | 2^15-1 |
int | Integer | 4 | -2^31 | 2^31-1 |
float | Float | 4 | ||
double | Double | 8 | ||
long | Long | 8 | ||
boolean | Boolean | - | true | false |
19. Hashtable,HashMap,ConcurrentHashMap底层实现原理与线程安全问题。
HashMap没有做多线程的控制,底层实现原理之前有提到过。HashTable是使用了重用锁,在操作时锁住了一段代码块,是同步的。ConcurrentHashMap,内部使用了volatile关键字,使得共享变量在多线程之间可见和同步,编程上考虑的比较多算法比较复杂。具体的上面的文章中有提到过。
20. Hash冲突怎么办?哪些解决散列冲突的方法?
hash冲突应该是hash算法的问题,也有可能是hashCode算法的问题。这个//todo
21. HashMap冲突很厉害,最差性能,你会怎么解决?从O(n)提升到log(n)。
首先哈希表是通过哈希函数计算元素的哈希值,然后将次哈希值作为要存放的数组地址的下标,以便达到用数组存放元素,同时能够实现较快的查找删除操作。
这里哈希函数就非常重要,如果哈希函数计算的不同元素的哈希值容易重复,就会导致哈希冲突(不同元素映射到同一下标),哈希表不得不额外地解决这些冲突。
好的哈希函数会尽量避免冲突,能均匀的分布。以及计算简单。
解决哈希冲突的办法主要有下面几种:
开放地址法:如果发现哈希值下标已经被占用,就向下查找下一个空元素存入,问题是非常容易产生集聚现象,使得冲突加剧。其中也有一些变种办法,例如不直接向下查找下一个元素,而是生成不同的查找步数,这个可以根据随机数或者二次哈希产生。
再哈希法:冲突后再次执行另一个哈希算法,得到新的地址。
链地址法:如果哈希冲突了,就直接将冲突的元素接在原占有的元素后面,形成一个链表。
而HashMap就是采用这种办法做的。HashMap的性能,也是与其hash函数有关,当存放的key是用户自定义的对象而不是String的时候,需要重写这个对象的hashCode方法,这个hashCode方法的效率就会影响到HashMap的哈希函数的效率,如果HashCode不能做到计算简单随机分布,hash函数就会容易冲突。
HashMap冲突很严重的话,还可以将HashMap转化为平衡树实现的TreeMap,后者的性能是logn.
23. rehash
看起来应该是HashMap的再哈希算法,主要用于当HashMap的容量超过负载因子(导致元素容易集聚成链表),HashMap就会扩容,所有的元素的哈希值就要重新计算,rehash大概就是用于此处的。rehash比较消耗性能,因此应该避免HashMap频繁扩容。
24. hashCode() 与 equals() 生成算法、方法怎么重写。
hashCode
hashCode用于生成标记一个对象的最好是唯一的散列码,它将被用在HashMap等散列表中,哈希函数会根据该散列码计算数组下标从而存放起来。
因此如果散列码是一样的话,在HashMap中认为是同一个对象,如果不一样,它就会认为这个一个新的对象。
大家说在重写equals时重写hashCode方法,是为了避免对象在用在哈希表的键时造成的问题。
设想重写了equals方法,两个不同的对象被认为是相等的,我们把相等的对象存到map的key中,然后再根据这个相等的对象取value的时候,就会出错。因为HashMap根据hashCode计算出来的两个对象的值不同,HashMap认为是不一样的。所以就找不到当初存放的对象。
所以hashCode需要重写,并且保证两个值相同的对象,它的hashCode一定是相同的。(如果固定地返回某个常量值,会导致系统会将所有的这种类型的对象都视为相同。)
重写hashCode方法是一项技术活,不过一些简单的重写方案还是有的。下面的代码从《effective java》中摘录
/**此处的变量都是int类型*/ @Override public int hashCode(){ int result = 17; result = 31*reuslt + areaCode; result = 31*result + prefix; result = 31*result + lineNumber; return result; }
equals
/** * equals方法需要满足自反性,对称性,传递性,一致性和非空性 * equasl方法写完了要进行相关的测试 * 过程是: * 检查被比较的对象是不是自己,如果是返回true * 检查被比较的对象是不是null,如果是返回false * 检查被比较的对象是不是和自己是同一个类型的,如果不是返回false * 将被比较对象强制转换成自己的类型,然后逐一比较值。 * 对于float,double,数组等格式的域,需要使用特殊的比较方法,而不能用==。 * */ @Override public boolean equals(Object obj){ if(this == obj)return true; if(obj == null)return false; if(obj.getClass() != this.getClass())return false;//!(obj instanceof Te1) Te1 t = (Te1)obj; if(!t.name.equals(name))return false; if(!(t.id==id))return false; if(Double.compare(salary,t.salary)!=0)return false; if(!Arrays.equals(strs,t.strs))return false; return true; }