zoukankan      html  css  js  c++  java
  • 面试Java基础问题汇总 part1

    编译时多态、运行时多态

    c++要更复杂,Java相对而言更容易回答。
    多态按执行过程分为两种情况,编译时多态和运行时多态。

    运行时多态的概念也可以被说成“一个接口,多个方法”。

    方法重载都是编译时多态。根据参数列表(数据类型、个数和次序),Java在编译时能够确定执行重载方法的哪一个。

    方法覆盖会表现出两种不同的多态性,当对象引用本类实例时,为编译时多态,否则(例:父类对象引用子类实例)则为运行时多态。

    在性能要求较高的代码中不提倡运用运行时多态,运行时多态方法较普通方法而言系统开销更大。

    补充:泛型也是多态性的一种体现,是编译时多态。

    equals()

    ==就不介绍了,它永远比较值。基础数据类型比较大小,引用数据类型比较地址值是否相同。

    equals()判断两个对象是否相等:

    1. 类没有覆盖equals方法,则等价于通过"=="比较对象。
    2. 类覆盖了equal是方法。一般会去比较两个对象的内容是否相等。

    Java String equals方法实例

    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    

    即先比较两个对象地址是否相等,如果地址相等,则他们的内容一定相等;如果地址不相等,会比较String的内容是否相等,比较方法就是遍历存储string的value数组(char类型,JDK9变为byte类型)。

    hashCode()

    hashCode()函数的作用是获取散列码,它只在散列表中有用,在其他情况下没用。在散列表中,hashCode() 的作⽤是获取对象的散列码,进⽽确定该对象在散列表中的位置。如果两个对象的hashcode不相等,则它们一定不相等;如果hashcode相等,两个对象也不一定相等(hash collision),再调用equals方法判断两者是否相等。这样可以减少equals()的调用次数,大大提高执行速度。

    下面是String类里面的hashCode()函数。

        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
    
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }
    

    线程基本状态

    Java线程只有这六种状态。

    • NEW
    • RUNNABLE (就绪和运行统称)
    • BLOCKED
    • WAITING
    • TIME_WAITING
    • TERMINATED

    Java IO

    详细请去其他地方看,这里只总结一些规律。

    特点划分
    Reader/Writer结尾字符流
    Stream结尾字节流

    BIONIOAIO (重点、必考)

    再去其他地方看看,面试只懂下面这些肯定不行。  

    BIO(Blocking I/O): 同步阻塞I/O模型,数据的读取写入必须阻塞在一个线程内等待其完成。在连接数不是很高的情况下,还是不错的,每一个连接专注于自己的I/O并且编程模型简单,不用考虑系统的过载,限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接请求。但是,十万级/百万级连接的时候,传统的BIO模型是无能为力的。

    NIO(Non-blocking/New I/O): 同步非阻塞I/O模型,在Java1.4中引入了NIO框架,对应java.nio包,提供了Channel, Selector, Buffer等抽象。它支持面向缓冲的,基于通道的I/O操作方法。NIO提供了与传统BIO模型中的SocketSeverSocket相对应的SocketChannelServerSocketChannel两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式,阻塞模型使用就像传统中的支持一样,简单,但是性能和可靠性都不好;非阻塞模型刚好相反。

    对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式开发。

    AIO(Asynchronous I/O): AIO即NIO2。于Java 7引入,它是异步非阻塞的IO模型。异步IO基于事件和回调机制实现,也就是应用操作之后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知线程进行后续的操作。

    深拷贝 vs 浅拷贝

    对于基本数据类型来讲,都是值传递。

    对引用数据来讲,对于引用值进行传递的拷贝,为浅拷贝;创建新的对象,复制其内容,返回新对象的地址,为深拷贝。

    C++、Python都有这个概念。

    import copy
    nums = [1, 2, 3, 4]
    c1 = copy.copy(nums) # 浅拷贝
    c2 = copy.deepcopy(nums) # 深拷贝
    

    ArrayList扩容

    初始化ArrayList,不加参数的时候,默认生成了一个空数组。

    加入第一个元素的时候,add()方法首先调用ensureCapacityInternal(size + 1),将minCapacity设置为传入参数和默认容量(10),此时minCapacity = 10。

    然后调用ensureExplicitCapacity方法。

     //判断是否需要扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                //调用grow方法进行扩容,调用此方法代表已经开始扩容了
                grow(minCapacity);
        }
    

    加入第一个元素时,minCapacity -elementData.length = 10- 0 = 10 > 0,调用grow()扩容。

        /**
         * 要分配的最大数组大小
         */
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        /**
         * ArrayList扩容的核心方法。
         */
        private void grow(int minCapacity) {
            // oldCapacity为旧容量,newCapacity为新容量
            int oldCapacity = elementData.length;
            //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
            //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
           // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,
           //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    newCapacity(1) - minCapacity(10) < 0,newCapacity被置为10。

    下次容量超过10的时候再次扩容,扩容之后为原来的.大约1.5倍,偶数正好,奇数抹去了小数部分。

    HashMap和HashTable的区别

    HashTable已经基本被淘汰,建议使用CocurrentHashMap。

    1. HashMap是非线程安全的,HashTable是线程安全的。HashTable内部的方法基本都经过synchronized修饰。
    2. HashMap的效率更好(因为不需要考虑线程安全)。
    3. HashMap,null可以作为键,这样的键只有一个,可以有一个或者多个键所对应的值为null。HashTable中如果put进null作键值,会报NullPointerException
    4. 初始容量大小和每次扩充容量大小的不同:(1) 创建时如果不指定容量初始值,HashTable默认初始大小为11,每次扩充变为原来的2n+1。HashMap默认的初始化大小为16,每次扩容变为原来2倍。
      (2) 创建时如果给定了容量的初始值,那么HashTable会直接使用你给的大小,而HashMap会将其扩充为2的幂次方大小(HashMap中的tableSizeFor()方法保证,HashMap总是使用2的幂作为哈希表的大小,这是因为indexFor方法使用(h&(length-1))来决定索引位置,是因为length是2的幂次方时,&运算接近%运算,能够均分,不会冲突。
    5. 底层数据结构:JDK1.8以后HashMap解决哈希冲突时,链表长度大于阀值(默认为8),会将链表转化为红黑树,以减少搜索时间。HashTable没有这个机制。
    hash % length == hash & (length-1)
    

    该等式仅在length是2的幂的时候成立,使用&运算比%运算效率高得多。

    HashMap和HashSet的区别

    HashSet底层基于HashMap实现。HashSet的源码中,除了clone()writeObject()readObject()是HashSet自己不得不实现之外,其他都是直接调用HashMap中的方法。

    HashMapHashSet
    实现了Map接口实现了Set接口
    存储键值对仅存储对象
    put()添加元素add()添加元素
    HashMap使用键(Key)计算hashcode。HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以使用equals()方法来判断对象的相等性。

    HashMap底层实现(重要)

    JDK1.8之前HashMap底层是数组和链表结合在一起(链表散列)。HashMap经过扰动函数处理后得到hash值,然后通过(n-1) & hash判断当前元素存放的位置。如果当前位置存在元素的话,就判断该元素与要存入的元素的hash值以及key是否相同。如果相同的话,直接覆盖,不相同就使用拉链法解决冲突。

    扰动函数,使用key.hashCode()得到h之后再与h>>>16取异或,目的是为了尽可能减少碰撞。

    ConcurrentHashMap和HashTable的区别(重要)

    主要在于实现线程安全的方式上不同。

    底层数据结构:JDK1.7的ConcurrentHashMap底层采用分段的数组+链表。JDK1.8采用的数据结构跟HashMap相同,都是数组+链表/红黑树。HashTable是数组加链表。

    实现线程安全的方式:
    (1) JDK1.7,ConcurrentHashMap使用分段锁对整个桶数组进行了分割分段(segment)每一把锁只锁容器的其中一部分数据,多线程访问容器里不同分段的数据,就不存在锁竞争,提高并发效率。JDK1.8时已经摒弃了segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现。并发控制使用synchronized和CAS来操作。

    CAS:Compare and Swap,即比较再交换。
    CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    (2) HashTable使用synchronized来保证线程安全(同一把锁)。这种方式效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态。

    详见这里

    参考资料

    JavaGuide面试突击版,百度可得最新版。
    编译时多态、运行时多态

  • 相关阅读:
    C# 发送匿名邮件
    上传大文件,Web.config中的配置
    老话题关于文章自动分页
    scrollLeft,scrollWidth,clientWidth,offsetWidth到底指的哪到哪的距离
    转成静态页面,由于ie网址或路径原因,Atlas失效。
    让图片自适应大小的方法
    textoverflow 全兼容
    ISAPI_rewrite中文手册
    ISAPI_Rewrite集
    无限级下拉列表框控件
  • 原文地址:https://www.cnblogs.com/wanghongze95/p/13842422.html
Copyright © 2011-2022 走看看