• 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) {
            }
        }

     

      

      

    多想,多试
  • 相关阅读:
    XML语法
    C/C++对MySQL操作
    HDU 3966 Aragorn's Story
    SPOJ 375 Query on a tree
    SPOJ 913 Query on a tree II
    SPOJ 6779 Can you answer these queries VII
    URAL 1471 Tree
    SPOJ 2798 Query on a tree again!
    POJ 3237 Tree
    SPOJ 4487 Can you answer these queries VI
  • 原文地址:https://www.cnblogs.com/junyi0120/p/6685899.html
Copyright © 2020-2023  润新知