java面试题(基础+非基础)[不定期更新]

前两天看到知乎中一个专栏,某大神的面试题,大概综合了各个大公司的常见提问,

写的蛮好的,这里转一下,配一些自己的理解,希望对大家面试有帮助吧。

原文链接:https://zhuanlan.zhihu.com/p/25725929

*********************************************************************************************************************

part1,

1,Java基础

java内存模型
多态(重载重写)
object方法
类访问权限
sleep、notify、wait 联系、区别
String、stringbuffer、stringbuilder 联系、区别、源码
Volatile 原理、源码、与syn区别
线程间通信方式
线程的各种状态
等等等等

Q:java内存模型(JMM):

A:

1,JMM 是 控制线程间通信的控制机制,

2,线程之间的共享变量(主要堆内存)存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。(本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。)

3,线程A与线程B之间如要通信的话,①A 把更新的本地内存A中的共享变量,刷新到共享内存。②,B读取共享内存中的变量(A已经更新了的)。

基本这样,面试回答就OK吧。

参考:http://www.infoq.com/cn/articles/java-memory-model-1

Q 2:多态(重载和重写)

A:重载(Overloading)和重写(@Overrider)是多态的两种体现,

重载是同一个类 内部,比如 构造函数,方法的重载 是方法名相同,参数列表不同,对返回值类型,不做要求。

重写是父子类,(接口的实现也叫重写),要求方法名相同,参数列表相同,返回值类型相同,访问修饰符不小于父类,抛出异常不大于父类,要求加@Overrider进行校验。

Q3:Object的方法,

A:简答回答,常见的 toString(),hashCode(),equals(); +notify(),notifyAll(),wait();+clone(),getClass(),finalize();

  其中有native方法,

  protected方法[finaloze(),clone(),],等

Q4:类访问权限:

 

思考:

1,为什么要设定一些类,方法为 protected的。

2,protected方法,在其他子类的子类中调用情况?

Q5,sleep,wait,notify的区别

sleep:让当前线程休息毫秒数,不丢失线程所有权。

wait:Objecte方法,当前线程等待,知道其他线程调用此对象的Notify方法;

Q6:

  String ,字符串常量,是不可变的对象。

  String str ="abcd";   //line1

  str = str+1;    //line2 这里 line2创建了一个新的str 

      System.out.println(s);// abcd1  ,

  底层 String,采用char[] 数组保存,在相加时,使用的也是StringBuilder的append。

StringBuffer --JDK1.0的方法,线程安全的,【public  synchronized StringBuffer append(* *)】

StringBuilder--JDK1.5的方法,线程不安全的,

三者在执行速度方面的比较:StringBuilder >  StringBuffer  >  String

******************************

PS: 

使用 javap -c 查看即:
public class StringDemo{
        public static void main(String[] args){
                String a = "hello";
                a  = a+"world";
                System.out.println(a);
        }

}


public class StringDemo { public StringDemo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String world 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_1 27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: return }

Q7,Volidate原理,源码和Sync区别;

   

  volidate 是程度较轻的synchronized,与sync相比,validate变量所需的编码较少,运行开销也相对较少.

 首先明白:锁的目的:提供 “互斥”和“可见性”的保证。 

   互斥性:一次只允许一个线程 持有特定的锁,因此可以使用该特性实现对“共享数据”的协调方位协议,这样一次就可以只有一个线程访问该数据。

   可见性:确保释放锁之前,对共享变量做出的更改,对后面获得该锁的另一个线程是可见的。

volidate变量具有 synchronized的可见性特征,但不具备原子特性,运行多个线程同时访问volidate变量。

(所以类似 i++的操作,单纯使用volidate不够)

原理:

非 volidate情况下可能发生的状况:

由于 java内存模型JMM(Q1)中,线程默认会读取,加载 主内存的共享变量后生成副本变量,而后的操作修改副本变量,并在退出之前写入 主内存中。

由于这些操作并非原子性的,eg:在线程read,load之后,主内存中的变量发生了修改,但是线程工作内存中由于已经加载导致不会变化,然后操作的结果,就和预期不一致。

volidate情况下:

volidate 关键字的共享内存,在每次的 read,load时,都会去主内存中去读取,这样保证了可见性。

 严格遵循 volatile 的使用条件 —— 即"变量真正独立于其他变量和自己以前的值时",这时可以使用volidate替代synchronized。

Q8,线程间的通信方式

 1,使用全局变量,结合上面知道,最好全局变量设定为volidate;

public class MySignal{

  protected volidate boolean hasDataToProcess = false;

  public synchronized boolean hasDataToProcess(){
    return this.hasDataToProcess;
  }

  public synchronized void setHasDataToProcess(boolean hasData){
    this.hasDataToProcess = hasData;
  }

}

 2,while轮询的方式方式,

3,wait/notify机制

参考链接:http://www.cnblogs.com/hapjin/p/5492619.html,

4, 通过管道流
    使用使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

5,消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小        受限等缺

Q9,线程的各种状态.

一般来说就是6种状态(JVM):

【倾向线程三种状态的描述:就绪状态,运行状态,和阻塞状态】

New状态是指线程刚创建, 尚未启动-----指Thread类的状态

Runnable: ,就绪和运行状态   ,一般。.start()之后进入。

BLOCKED,阻塞状态,抢到锁才可以执行,目前锁被别人占着,被动等待,抢到锁,会进入“就绪状态”

Waitting: 阻塞状态,持有锁,但是出于无限期的主动等待的中,等到有人唤醒他,会进入“就绪状态”。

TimeWaitting: 阻塞状态,持有锁,但是出于有限期的主动等待中,[eg:被你主动sleep N秒],等有人唤醒他,或者时间到,会进入“就绪状态”。

Terminated,终止状态-----指Thread类的状态.

*********************************************************************************************************************

part2,

集合框架

List

    ArrayList
    LinkedList
    Vector

三者区别,联系,源码

Set

    HashSet
    LinkedHashSet
    TreeSet

基于什么实现,内部数据结构,适用场景,源码

Map

    HashMap
    weakHashMao
    LinkedHashMap
    TreeMap

HashMap与hashtable的区别

内部实现原理、源码、适用场景

Q10,List集合部分,List集合有序(index),相对简单(熟悉)

ArrayList<E>:底层是数组,查询速度快,插入速度慢[要调用System.ArrayCopy 复制数组,生成新的数组],非线程安全,PS:ArrayList集合容量不足时,每次增长【int newCapacity = oldCapacity + (oldCapacity >> 1);

LinkList<E> :底层是“双向链表”结构,Node节点,插入速度快。非线程安全

Vector(其子类 java.util.Stack,栈,先进后出,类似Queue),底层也是数组(Object[] elementData),线程安全的【eg: public synchronized boolean add(E e)】

Q11,Set集合部分,Set集合无序,

相比List接口,Set接口少了很多和顺序有关的方法

1,E get(int index);

2,E set(int index, E element);

3,void add(int index, E element);

4,E remove(int index);

5,int indexOf(Object o);

6,int lastIndexOf(Object o);

7,ListIterator<E> listIterator();

8,List<E> subList(int fromIndex, int toIndex);

HashSet<E>:底层使用HashMap集合,将 E 集合元素保存在Map的Key中,Map的value设定为默认 静态的虚拟值Object。

 public HashSet() {
        map = new HashMap<>();
    }

所有对HashSet的操作,转为对HashMap的操作,eg

public int size() {
        return map.size();
    }

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

   public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

  public void clear() {
        map.clear();
    }

LinkHashSet,继承HashSet, 完全基于LinkHashMap来实现。

-父类HashSet:------------------------------------------------------------
  HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }


-子类LinkHashSet:--------------------------------------------------------
   public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }

    public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }

    public LinkedHashSet() {
        super(16, .75f, true);
    }

TreeSet<E>,JDK1.6之后,添加了实现NavigableSet<E>,(这个接口继承自SortedSet<E>),底层基于TreeMap来实现。

总结:Set集合就是和Map集合的一系列关联,HashSet,LinkHashSet 允许存在一个 null值。

PS, TreeSet集合不允许null值传入,(所有元素必须实现Comparable<>,否则运行报错 类型转换错误,而null无法实现),不允许重复值.

使用:TreeSet,不打乱顺序去重,有序且不允许重复的数据。

   LinkedHashSet,有序的HashSet,按照存入的顺序,在不允许重复数据,切按照顺序输出时使用。

Q12,Map集合部分,

  HashMap<K,V>

  WeakHashMap<K,V>

  LinkHashMap<K,V>

  TreeMap<K,V>

  HashTable<K,V>

HashMap<K,V>:采用哈希表【散列表】结构,O(1)的时间结构,快读的插入和读取。 底层 数组(Array)+链表(LinkList)(+红黑树,jdk1.8),

**********************

我们把学生姓名首字母按照A1,B2,C3计算,之后加和作为 ID,插入 数组中。

eg:李秋梅:lqm 12+17+13=42 取表中第42条记录即可。

但是遇到,李璐,李丽这样的,这个名称计算结果相等的状况,就出现“冲突现象”,不同的人得到了相同的Hash地址。

**********************

同样,Hash算法中,后加入的对象,在计算其Hash算法时,也可能存在计算出的值,已经存在了(但不是同一个对象),这即是 Hash冲突,HashMap解决重复的方法是“链地址法”

【Hash算法返回值是一个int,最多不过40亿,但是实际对象的数量,远不止这些。so,Hash相等,但对象不一定相等】

1,HashMap 运行 一个 null 作为Key和Value,是线程非安全的。

2,HashMap扩容的两个关键参数(initialCapacity,loadFactor) 初始容量和负载因子,默认HashMapTable大小为16,负载因子为0.75,

为什么增大负载因子可以减少Hash表所占用的内存空间,但会增加查询数据的时间开销。
为什么减少负载因子可以提高查询数据的性能,但会减低Hash表所占用的内存空间。

如果负载因子是默认的0.75,HashMap(16)的时候,占16个内存空间,实际上只用到了12个,超过12个就扩容。
如果负载因子是1的话,HashMap(16)的时候,占16个内存空间,实际上会填满16个以后才会扩容。

所以说,增大负载因子,你让 当前Hash表空间被充分利用,但是会产生更多的Hash碰撞,导致查询变慢。

3,

---------------JDK1.7的get,和JDK1.8有部分区别--------------

 public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

4,HashMap的扰乱算法, 默认的Hash值在 容积小的时候,非常容易碰撞,

/**
JDK1.7-----------
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

------------------JDK1.8----------------
/**
     * Computes key.hashCode() and spreads (XORs) higher bits of hash
     * to lower.  Because the table uses power-of-two masking, sets of
     * hashes that vary only in bits above the current mask will
     * always collide. (Among known examples are sets of Float keys
     * holding consecutive whole numbers in small tables.)  So we
     * apply a transform that spreads the impact of higher bits
     * downward. There is a tradeoff between speed, utility, and
     * quality of bit-spreading. Because many common sets of hashes
     * are already reasonably distributed (so don't benefit from
     * spreading), and because we use trees to handle large sets of
     * collisions in bins, we just XOR some shifted bits in the
     * cheapest possible way to reduce systematic lossage, as well as
     * to incorporate impact of the highest bits that would otherwise
     * never be used in index calculations because of table bounds.
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

目的就是让 Hash值,尽量分部均匀。

5,可以看下 静态内部类 entry,【JDK1.8改为Node】

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;  //成为链表
        final int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

 

  

  

多想,多试
原文地址:https://www.cnblogs.com/junyi0120/p/6685899.html