Java笔试面试题整理第四波



转载至:http://blog.csdn.net/shakespeare001/article/details/51274685


作者:山代王(开心阳)

本系列整理Java相关的笔试面试知识点,其他几篇文章如下:

1、HashMap、HashTable、ConcurrentHashMap的区别

    (关于HashMap的分析,在第三篇总结《Java笔试面试题整理第三波》中的hashCode有分析,同样在这篇中有关于Java容器的介绍。HashMap和HashTable都属于Map类集合。)

    HashMap和HashTable都实现了Map接口,里面存放的元素不保证有序,并且不存在相同元素;

区别(线程安全和保存值是否为null方面):
   (1) HashMap和HashTable在功能上基本相同,但HashMap是线程不安全的,HashTable是线程安全的;

HashMap的put源码如下:    
  1. public V put(K key, V value) {
  2. if (table == EMPTY_TABLE) {
  3. inflateTable(threshold);
  4. }
  5. if (key == null)
  6. return putForNullKey(value);    //说明key和value值都是可以为null
  7. int hash = hash(key);
  8. int i = indexFor(hash, table.length);
  9. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  10. Object k;
  11. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  12. V oldValue = e.value;
  13. e.value = value;
  14. e.recordAccess(this);
  15. return oldValue;
  16. }
  17. }
  18. modCount++;
  19. addEntry(hash, key, value, i);
  20. return null;
  21. }
Java笔试面试题整理第四波
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value); //说明key和value值都是可以为null
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
} modCount++;
addEntry(hash, key, value, i);
return null;
}
(2)可以看到,HashMap的key和value都是可以为null的,当get()方法返回null值时,HashMap中可能存在某个key,只不过该key值对应的value为null,也有可能是HashM中不存在该key,所以不能使用get()==null来判断是否存在某个key值,对于HashMap和HashTable,提供了containsKey()方法来判断是否存在某个key。

HashTable的put源码如下:    
  1. public synchronized V put(K key, V value) {
  2. // Make sure the value is not null
  3. if (value == null) {    //当value==null的时候,会抛出异常
  4. throw new NullPointerException();
  5. }
  6. // Makes sure the key is not already in the hashtable.
  7. Entry tab[] = table;
  8. int hash = hash(key);
  9. int index = (hash & 0x7FFFFFFF) % tab.length;
  10. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  11. if ((e.hash == hash) && e.key.equals(key)) {
  12. V old = e.value;
  13. e.value = value;
  14. return old;
  15. }
  16. }
  17. modCount++;
  18. if (count >= threshold) {
  19. // Rehash the table if the threshold is exceeded
  20. rehash();
  21. tab = table;
  22. hash = hash(key);
  23. index = (hash & 0x7FFFFFFF) % tab.length;
  24. }
  25. // Creates the new entry.
  26. Entry<K,V> e = tab[index];
  27. tab[index] = new Entry<>(hash, key, value, e);
  28. count++;
  29. return null;
  30. }
Java笔试面试题整理第四波
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) { //当value==null的时候,会抛出异常
throw new NullPointerException();
} // Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
} modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash(); tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
} // Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
  
(3)HashTable是不允许key和value为null的。HashTable中的方法大部分是同步的,因此HashTable是线程安全的。

拓展:
   (1) 影响HashMap(或HashTable)性能的两个因素:初始容量和load factor;
        HashMap中有如下描述:        When the number of entries in the hash table exceeds the product of the load factor and the current capacity, 
the hash table is <i>rehashed</i> (that is, internal data structures are rebuilt) 
        当我们Hash表中数据记录的大小超过当前容量,Hash表会进行rehash操作,其实就是自动扩容,这种操作一般会比较耗时。所以当我们能够预估Hash表大小时,在初始化的时候就尽量指定初始容量,避免中途Hash表重新扩容操作,如:      
  1. HashMap<String, Integer> map = new HashMap<String, Integer>(20);
Java笔试面试题整理第四波
 HashMap<String, Integer> map = new HashMap<String, Integer>(20);
        
(类似可以指定容量的还有ArrayList、Vector)

   (2)使用选择上,当我们需要保证线程安全,HashTable优先选择。当我们程序本身就是线程安全的,HashMap是优先选择。
        其实HashTable也只是保证在数据结构层面上的同步,对于整个程序还是需要进行多线程并发控制;在JDK后期版本中,对于HashMap,可以通过Collections获得同步的HashMap;如下:
        Map m = Collections.synchronizedMap(new HashMap(…));
        这种方式获得了具有同步能力的HashMap。        

    (3)在JDK1.5以后,出现了ConcurrentHashMap,它可以很好地解决在并发程序中使用HashMap的问题,ConcurrentHashMap和HashTable功能很像,不允许为null的key或value,但它不是通过给方法加synchronized方法进行并发控制的。
     在ConcurrentHashMap中使用分段锁技术Segment,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。效率也比HashTable好的多。

(关于ConcurrentHashMap具体可以参考:

2、TreeMap、HashMap、LinkedHashMap的区别

    关于Map集合,前面几篇都有讲过,可以去回顾一下。而TreeMap、HashMap、LinkedHashMap都是Map的一些具体实现类,其关系图如下:
Java笔试面试题整理第四波
其中,HashMap和HashTable主要区别在线程安全方面和存储null值方面。HashMap前面讨论的已经比较多了,下面说说LinkedHashMap和TreeMap。
(1)LinkedHashMap保存了数据的插入顺序,底层是通过一个双链表的数据结构来维持这个插入顺序的。key和value都可以为null;
(2)TreeMap实现了SortMap接口,它保存的记录是根据键值key排序,默认是按key升序排列。也可以指定排序的Comparator。

HashMap、LinkedHashMap和TreeMap都是线程不安全的,HashTable是线程安全的。提供两种遍历Map的方法如下:
(1)推荐方式:     
  1. Map<String, Integer> map = new HashMap<String, Integer>(20);
  2. for(Map.Entry<String, Integer> entry : map.entrySet()){    //直接遍历出Entry
  3. System.out.println(”key–>”+entry.getKey()+“,value–>”+m.get(entry.getValue()));
  4. }
Java笔试面试题整理第四波
Map<String, Integer> map = new HashMap<String, Integer>(20);
for(Map.Entry<String, Integer> entry : map.entrySet()){ //直接遍历出Entry
System.out.println("key-->"+entry.getKey()+",value-->"+m.get(entry.getValue()));
}
        这种方式相当于首先通过Set<Map.Entry<String,Integer>> set =  map.entrySet();方式拿到Set集合,而Set集合是可以通过foreach的方式遍历的。

(2) 普通方式:     
  1. Map<String, Integer> map = new HashMap<String, Integer>(20);
  2. Iterator<String> keySet = map.keySet().iterator();    //遍历Hash表中的key值集合,通过key获取value
  3. while(keySet .hasNext()){
  4. Object key = keySet .next();
  5. System.out.println(”key–>”+key+“,value–>”+m.get(key));
  6. }
Java笔试面试题整理第四波
Map<String, Integer> map = new HashMap<String, Integer>(20);
Iterator<String> keySet = map.keySet().iterator(); //遍历Hash表中的key值集合,通过key获取value
while(keySet .hasNext()){
Object key = keySet .next();
System.out.println("key-->"+key+",value-->"+m.get(key));
}

3、Collection包结构,与Collections的区别。

Collection的包结构如下:
Java笔试面试题整理第四波
Statck类为Vector的子类。由于Collection类继承Iterable类,所以,所有Collection的实现类都可以通过foreach的方式进行遍历。

Collections是针对集合类的一个帮助类。提供了一系列静态方法实现对各种集合的搜索、排序、线程完全化等操作。 
当于对Array进行类似操作的类——Arrays。 
如,Collections.max(Collection coll); 取coll中最大的元素。 
       Collections.sort(List list); 对list中元素排序 

4、OOM你遇到过哪些情况,SOF你遇到过哪些情况

    OOM:OutOfMemoryError异常,
    即内存溢出,是指程序在申请内存时,没有足够的空间供其使用,出现了Out Of Memory,也就是要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。
    内存溢出分为上溢和下溢,比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。
    
    有时候内存泄露会导致内存溢出,所谓内存泄露(memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,举个例子,就是说系统的篮子(内存)是有限的,而你申请了一个篮子,拿到之后没有归还(忘记还了或是丢了),于是造成一次内存泄漏。在你需要用篮子的时候,又去申请,如此反复,最终系统的篮子无法满足你的需求,最终会由内存泄漏造成内存溢出。

    遇到的OOM:
    (1)Java Heap 溢出
    Java堆用于存储对象实例,我们只要不断的创建对象,而又没有及时回收这些对象(即内存泄漏),就会在对象数量达到最大堆容量限制后产生内存溢出异常。
    (2)方法区溢出
   方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

异常信息:Java.lang.OutOfMemoryError:PermGen space

方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。


SOF:*(堆栈溢出)
    当应用程序递归太深而发生堆栈溢出时,抛出该错误。因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
    栈溢出的原因:
    (1)递归调用
    (2)大量循环或死循环
    (3)全局变量是否过多
    (4)数组、List、Map数据过大

OOM在Android开发中出现比较多:
   场景有: 加载的图片太多或图片过大时、分配特大的数组、内存相应资源过多没有来不及释放等。

解决方法:
    (1)在内存引用上做处理
        软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。
    (2)对图片做边界压缩,配合软引用使用
    (3)显示的调用GC来回收内存,如:
        if(bitmapObject.isRecycled()==false//如果没有回收  
       bitmapObject.recycle();
  (4)优化Dalvik虚拟机的堆内存分配
            》增强程序堆内存的处理效率    
        //在程序onCreate时就可以调用 即可
        privatefinalstaticfloat TARGET_HEAP_UTILIZATION = 0.75f; 
        VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);

            》设置堆内存的大小
            privatefinalstaticintCWJ_HEAP_SIZE = 610241024;
      //设置最小heap内存为6MB大小
      VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);

    (5)用LruCache 和 AsyncTask<>解决
        从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。
  如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。

5、Java面向对象的三个特征与含义,多态的实现方式

Java中两个非常重要的概念:类和对象。类可以看做是一个模板,描述了一类对象的属性和行为;而对象是类的一个具体实现。Java面向对象的三大基本特征:
(1)封装
    属性用来描述同一类事物的特征,行为用来描述同一类事物可做的一些操作。封装就是把属于同一类事物的共性(属性和行为)归到一个类中,只保留有限的接口和方法与外部进行交互,避免了外界对对象内部属性的破坏。Java中使用访问控制符来保护对类、属性、方法的访问。
(2)继承
    子类通过这种方式来接收父类所有的非private的属性和方法(构造方法除外)。这里的接收是直接拥有的意思,即可以直接使用父类字段和方法,因此,继承相当于“扩展”,子类在拥有了父类的属性和特征后,可以专心实现自己特有的功能。
    (构造方法不能被继承,因为在创建子类时,会先去自动“调用”父类的构造方法,如果真的需要子类构造函数特殊的形式,子类直接修改或重载自己的构造函数就好了。)
(3)多态
    多态是程序在运行的过程中,同一种类型在不同的条件下表现不同的结果。比如:
        Animal  a = new Dog();    // 子类对象当做父类对象来使用,运行时,根据对象的实际类型去找子类覆盖之后的方法

多态实现方式:
    (1)设计时多态,通过方法的重载实现多态;
    (2)运行时多态,通过重写父类或接口的方法实现运行时多态;

6、interface与abstract类的区别

    abstract class 只能被继承extends,体现的是一种继承关系,而根据继承的特征,有继承关系的子类和父类应该是一种“is-a”的关系,也即两者在本质上应该是相同的(有共同的属性特征)。
    interface 是用来实现的implements,它并不要求实现者和interface之间在本质上相同,是一种“like-a”的关系,interface只是定义了一系列的约定而已(实现者表示愿意遵守这些约定)。所以一个类可以去实现多个interface(即该类遵守了多种约定)。
    很多情况下interface和abstract都能满足我们要求,在我们选择用abstract火interface的时候,尽量符合上面的要求,即如果两者间本质是一样的,是一种“is-a”的关系,尽量用abstract,当两者之间本质不同只是简单的约定行为的话,可以选择interface。
特点:
(1)abstract类其实和普通类一样,拥有有自己的数据成员和方法,只不过abstract类里面可以定义抽象abstract的方法(声明为abstract的类也可以不定义abstract的方法,直接当做普通类使用,但是这样就失去了抽象类的意义)。
(2)一个类中声明了abstract的方法,该类必须声明为abstract类。
(3)interface中只能定义常量和抽象方法。在接口中,我们定义的变量默认为public static final类型,所以不可以在显示类中修改interface中的变量;定义的方法默认为public abstract,其中abstract可以不明确写出。

7、static class 与non static class的区别

static class–静态内部类,non static class–非静态内部类,即普通内部类

普通内部类:
    内部类可以直接使用外部类的所有变量(包括private、静态变量、普通变量),这也是内部类的主要优点(不用生成外部类对象而直接使用外部类变量)。如下例子:
  1. public class OutClass {
  2. private String mName = “lly”;
  3. static int mAge = 12;
  4. class InnerClass{
  5. String name;
  6. int age;
  7. private void getName(){
  8. name = mName;
  9. age = mAge;
  10. System.out.println(”name=”+name+“,age=”+age);
  11. }
  12. }
  13. public static void main(String[] args) {
  14. //第一种初始化内部类方法
  15. OutClass.InnerClass innerClass = new OutClass().new InnerClass();
  16. innerClass.getName();
  17. //第二种初始化内部类方法
  18. OutClass out = new OutClass();
  19. InnerClass in = out.new InnerClass();
  20. in.getName();
  21. }
  22. }
Java笔试面试题整理第四波
public class OutClass {
private String mName = "lly";
static int mAge = 12; class InnerClass{
String name;
int age;
private void getName(){
name = mName;
age = mAge;
System.out.println("name="+name+",age="+age);
}
} public static void main(String[] args) {
//第一种初始化内部类方法
OutClass.InnerClass innerClass = new OutClass().new InnerClass();
innerClass.getName();
//第二种初始化内部类方法
OutClass out = new OutClass();
InnerClass in = out.new InnerClass();
in.getName();
}
}
输出:name=lly,age=12

可以看到,内部类里面可以直接访问外部类的静态和非静态变量,包括private变量。在内部类中,我们也可以通过外部类.this.变量名的方式访问外部类变量,如:name = OutClass.this.mName; 
内部类的初始化依赖于外部类,只有外部类初始化出来了,内部类才能够初始化。

私有内部类(包括私有静态内部类和私有非静态内部类):
如果一个内部类只希望被外部类中的方法操作,那只要给该内部类加上private修饰,声明为private 的内部类只能在外部类中使用,不能在别的类中new出来。如上private class InnerClass{…},此时只能在OutClass类中使用。

静态内部类:
静态内部类只能访问外部类中的静态变量。如下:
  1. public class OutClass {
  2. private String mName = “lly”;
  3. static int mAge = 12;
  4. static class StaticClass{
  5. String name = ”lly2”;
  6. int age;
  7. private void getName(){
  8. //            name = mName;    //不能引用外部类的非静态成员变量
  9. age = mAge;
  10. System.out.println(”name=”+name+“,age=”+age);
  11. }
  12. }
  13. public static void main(String[] args) {
  14. //第一种初始化静态内部类方法
  15. OutClass.StaticClass staticClass = new OutClass.StaticClass();
  16. staticClass.getName();
  17. //或者直接使用静态内部类初始化
  18. StaticClass staticClass2 = new StaticClass();
  19. staticClass2.getName();
  20. }
  21. }
Java笔试面试题整理第四波
public class OutClass {
private String mName = "lly";
static int mAge = 12; static class StaticClass{
String name = "lly2";
int age;
private void getName(){
// name = mName; //不能引用外部类的非静态成员变量
age = mAge;
System.out.println("name="+name+",age="+age);
}
} public static void main(String[] args) {
//第一种初始化静态内部类方法
OutClass.StaticClass staticClass = new OutClass.StaticClass();
staticClass.getName();
//或者直接使用静态内部类初始化
StaticClass staticClass2 = new StaticClass();
staticClass2.getName();
}
}

输出:name=lly2,age=12


可以看到,静态内部类只能访问外部类中的静态变量,静态内部类的初始化不依赖于外部类,由于是static,类似于方法使用,OutClass.StaticClass是一个整体。

匿名内部类:
匿名内部类主要是针对抽象类和接口的具体实现。在Android的监听事件中用的很多。如:    
  1. textView.setOnClickListener(new View.OnClickListener(){    //OnClickListener为一个接口interface
  2. public void onClick(View v){
  3. }
  4. });
Java笔试面试题整理第四波
textView.setOnClickListener(new View.OnClickListener(){    //OnClickListener为一个接口interface
public void onClick(View v){
...
}
});

对于抽象类:
  1. public abstract class Animal {
  2. public abstract void getColor();
  3. }
  4. public class Dog{
  5. public static void main(String[] args) {
  6. Animal dog = new Animal() {
  7. @Override
  8. public void getColor() {
  9. System.out.println(”黑色”);
  10. }
  11. };
  12. dog.getColor();
  13. }
  14. }
Java笔试面试题整理第四波
public abstract class Animal {
public abstract void getColor();
} public class Dog{
public static void main(String[] args) {
Animal dog = new Animal() {
@Override
public void getColor() {
System.out.println("黑色");
}
};
dog.getColor();
}
}
输出:黑色

对于接口类似,只需要把abstract class 改为interface即可。


上一篇:swagger2入门实例


下一篇:SpringBoot整合swagger2