Java面试题

来源:互联网 发布:查看域名真实ip 编辑:程序博客网 时间:2024/06/09 13:34

被alibaba从C++调剂到Java岗位,仍在面试中,然而对Java并不熟悉,基础很差,故整理而学习之。
HR结束,等offer中,求不挂

      • 线程安全
      • String StringBuffer StringBuilder
      • Vector ArrayList LinkedList
      • HashTableHashMapTreeMap
        • 1 Map概括
        • 2 HashMap与HashTable对比
        • 3 HashMap与WeakHashMap对比
      • 面向对象特征
      • Java访问修饰符
      • equals和
      • 重载Overload和重写Override
      • 抽象类abstract class和接口interface

1 线程安全

  • 线程安全

    当多个线程类并发操作某类的某个方法,在该方法的内部来修改这个类的某个成员变量的值,不会出错,我们就说,这个方法是线程安全的。

    某类的某个方法是否线程安全的关键是:

    1. 该方法是否修改该类的成员变量;
    2. 是否给该方法加锁(是否使用synchronized)
  • 线程不安全

    当多个线程类并发操作某类的某个方法,在该方法内部来修改这个类的某个成员变量的值,很容易发生错误,故我们说,这个方法是线程不安全的。如果要把这个方法变成线程安全的,则用synchronized关键词来修饰这个方法即可。

    TIPS:用synchronized关键字修饰方法,会导致加锁,虽然可能使该方法线程安全,但是会极大的降低该方法的执行效率,所以慎用该关键词。

2 String StringBuffer StringBuilder

  • String是字符串常量,后两者是字符串变量。String不可变是因为在JDK中String类倍声明为一个final类。
  • StringBuffer是线程安全的,StringBuilder线程不安全。线程安全会带来额外的内存开销,所以StringBuffer效率低于StringBuilder

3 Vector ArrayList LinkedList

  • Vector和ArrayList都是以类似数组的形式存储的,LinkedList则是以链表的形式进行存储的

  • Vector线程同步(安全),ArrayList和LinkedList线程不同步(不安全)

  • ArrayList扩容容器大小的50%,Vector为100%。ArrayList更省空间?

  • ArrayList 是线性表(数组)
    get() 直接读取第几个下标,复杂度 O(1)
    add(E) 添加元素,直接在后面添加,复杂度O(1)
    add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
    remove() 删除元素,后面的元素需要逐个移动,复杂度O(n)

    LinkedList 是链表的操作
    get() 获取第几个元素,依次遍历,复杂度O(n)
    add(E) 添加到末尾,复杂度O(1)
    add(index, E) 添加第几个元素后,需要先查找到第几个元素,直接指针指向操作,复杂度O(n)
    remove() 删除元素,直接指针指向操作,复杂度O(1)

4 HashTable,HashMap,TreeMap

img

4.1 Map概括

Map是“键值对”映射的抽象接口

AbstractMap实现了Map中大部分函数接口,减少了“Map的实现类”的重复编码

SortedMap有序的”键值对“映射接口

NavigationMap继承自SortedMap,支持导航函数的接口

HashMap是基于拉链法实现的散列表,一般用于单线程程序中。

HashTable是基于拉链法实现的散列表,一般用于多线程。

TreeMap是有序(默认升序)的散列表,通过红黑树实现,一般用于单线程中存储有序的映射

WeakHashMap基于拉链法实现的散列表,一般用于单线程,相比HashMap,WeakHashMap中的键是”弱“键,当弱键被GC回收时,对应的键值也会从WeakHashMap中删除,而HashMap中的键是强键。

4.2 HashMap与HashTable对比

  • 相同点

    • 都是存储键值对(key-value)的散列表,而且都是采用采用拉链法实现的。
    • 添加:首先根据key计算出哈希值,再计算出数组索引,然后根据索引找到Entry(单向链表),再遍历该单向链表,将key和链表中的每一个节点的key进行对比。若存在,则更新value,若不存在,将该节点插入到Entry链表的表头位置
    • 删除:首先根据key计算哈希值,再计算出数组索引,然后根据索引找到Entry遍历,找到节点删除。
  • 不同点

    • 继承和实现方式不同

    • HashMap继承于AbstractMap,实现了Map、CloneMap、Serializable接口

    • HashTable继承于Dictionary,实现了Map、Cloneable、Serializable接口

    • 两者定义代码

      public class HashMap<K,V>  extends AbstractMap<K,V>  implements Map<K,V>, Cloneable, Serializable {}public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable {}
    • 线程安全性

    • HashMap的函数是非同步的,不是线程安全的。

    • HashTable的几乎所有函数都是同步的,是线程安全的。

    • 对null值的处理

    • HashMap的key和value都可以是null,当HashMap的key为null,HashMap会将其固定插入到table[0]—HashMap散列表的第一个位置。table[0]处只能容纳一个key为null的值,若多个key为null的值插入,则保留最后插入的value。

    • HashTable的key和value都不可以是null,会抛出NullPointerException

    • 支持的遍历种类

    • HashMap只支持Iterator(迭代器)遍历

    • Hash Table支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历

    • 通过Iterator迭代器便利时的遍历顺序

    • HashMap**从前向后**遍历数组;对数组具体某一项对应的链表,则从表头开始向后遍历。

    • HashTable**从后向前**遍历数组;对数组具体某一项对应的链表,则从表头开始向后遍历。

    • 容量的初始值和增加方式

    • HashMap默认初始大小为16,默认加载因子是0.75。增加时,每次将容量变为原始容量*2(保证为2^n)

      TIPS:为了提高效率,HashMap的容量是2的幂,即使指定初始容量不是2的幂,内部也会将其转换为2的幂。

      int capacity = 1;while (capacity < initialCapacity)    capacity <<= 1;//是不是非常interesting
    • HashTable默认初始大小为11,默认加载因子是0.75。增加时,每次扩容为原始容量*2+1(保证近似质数,分散效果好)

    • 添加key-value时采用的hash函数不同

    • HashMap采用自定义的哈希算法

       static int hash(int h) {        h ^= (h >>> 20) ^ (h >>> 12);        return h ^ (h >>> 7) ^ (h >>> 4);    }  
    • Hash Table采用key的hashCode()方法,没有自定义的哈希函数

      int hash = key.hashCode();    int index = (hash & 0x7FFFFFFF) % tab.length; 
    • 部分API不同

    • HashMap不支持contains(Object value)方法,没有重写toString()

    • HashTable支持contains(Object value)方法,重写toString()

4.3 HashMap与WeakHashMap对比

  • HashMap实现了Cloneable和Serializable接口,而WeakHashMap没有。

    HashMap实现Cloneable,意味着它能通过clone()克隆自己。
    HashMap实现Serializable,意味着它支持序列化,能通过序列化去传输。

  • HashMap的“键”是“强引用(StrongReference)”,而WeakHashMap的键是“弱引用(WeakReference)”。

    WeakReference的“弱键”能实现WeakReference对“键值对”的动态回收。当“弱键”不再被使用到时,GC会回收它,WeakReference也会将“弱键”对应的键值对删除。

    (01)新建WeakHashMap,将“键值对”添加到WeakHashMap中。

    将“键值对”添加到WeakHashMap中时,添加的键都是弱键。
    实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。


    (02)当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到queue队列中。

    例如,当我们在将“弱键”key添加到WeakHashMap之后;后来将key设为null。这时,便没有外部外部对象再引用该了key。

    接着,当Java虚拟机的GC回收内存时,会回收key的相关内存;同时,将key添加到queue队列中。

    (03)当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的“弱键”;同步它们,就是删除table中被GC回收的“弱键”对应的键值对。

    例如,当我们“读取WeakHashMap中的元素或获取WeakReference的大小时”,它会先同步table和queue,目的是“删除table中被GC回收的‘弱键’对应的键值对”。删除的方法就是逐个比较“table中元素的‘键’和queue中的‘键’”,若它们相当,则删除“table中的该键值对”。

5 面向对象特征

  • 抽象

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

  • 封装

    通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。

  • 继承

    继承是从已有类得到继承信息创建新类的过程。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

  • 多态

    多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性

    方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。

6 Java访问修饰符

修饰符 当前类 同包 子类 其他包 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × ×

7 equals()和”==”

  • ==:既可用于基础类型,也可用于对象类型。对于基础类型,判断两者值是否相等。对于对象类型,判断两个对象的地址是否相等,即判断两个对象是不是同一个对象。

  • equals(),只能用于对象类型,equals()定义再JDK的Object.java中,与==并无区别。因此,通常会重写equals方法,比如String类。

    public boolean equals(Object obj) {  return (this == obj);}

    下面根据是否覆盖equals方法分为两类

    • 覆盖equals方法,一般,我们都覆盖equals方法来比较两个对象的内容,若内容相等,则返回true。比如String类。
    • 未覆盖equals方法,等价于使用==比较两个对象。
    String a = "test";String b = "test";String c = new String("test");System.out.println((a==b) + " " + a.equals(b));System.out.println((a==c) + " " + a.equals(c));//输出://true true//false true//a和b指向同意地址,c在堆上新建一个String对象,和a、b不同,但是内容相同,都是test

8 重载(Overload)和重写(Override)

重载和重写都是实现多态的方式,前者实现的是编译时的多态,后者是运行时的多态。

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

    TIPS:不能根据返回类型区分重载:容易产生二义性

    int f(){}char f(){}//采用无返回值调用,无法判断调用的哪一个f()函数f();
  • 重写发生在父类和子类中,重写要求重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型。重写方法不能使用比被重写的方法更严格的访问权限。

9 抽象类(abstract class)和接口(interface)

  • 抽象类可以有构造方法,接口不能有构造方法。

    TIPS:虽然抽象类有构造方法,但是不能被实例化

  • 抽象类中可以有普通成员变量,接口中没有普通成员变量。

  • 抽象类和接口中都可以有静态成员变量。抽象类中的静态成员变量可以是任意类型,但接口中的变量只能是public static final,并且默认为public static final。

  • 抽象类可以包含非抽象的普通方法,接口中的方法必须是抽象的。

  • 抽象类中的抽象方法访问类型可以使public,protected和的default。接口中只能是public,且默认为public abstract

  • 抽象类可以包含静态方法,而接口中不能包含静态方法。

  • 一个类可以实现多个接口,但是只能继承一个抽象类

待更新

0 0
原创粉丝点击