前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >重磅!!面试季--最新面试题总结出厂,附题解,后期持续分享!

重磅!!面试季--最新面试题总结出厂,附题解,后期持续分享!

作者头像
好好学java
发布2019-05-06 11:20:36
3380
发布2019-05-06 11:20:36
举报

一、情话部分

小姐姐: 为什么有很多人在感情中付出很多,却得不到想要的结果?

你答: 我听过一个这样的故事:讲的是蚯蚓一家人,有一天,蚯蚓爸爸特别无聊,就把自己切成了俩段愉快的打羽毛球去了,蚯蚓妈妈见状,把自己切成了四段,打麻将去了,蚯蚓哥哥接近狂热,把自己切成很多段,结果死掉了,因为他想踢足球。蚯蚓哥哥的死震惊了整个蚯蚓界,各蚯蚓专家呼吁大家要谨慎使用自己的能力。蚯蚓哥哥的死同时对蚯蚓一家造成了不可磨灭的伤害,蚯蚓弟弟为了弥补家庭的残缺,决定把自己切成俩段。第二天蚯蚓弟弟也死掉了。你知道为什么吗?

小姐姐:嗯?不知道(如果小姐姐知道,你就夸她聪明咯)

你:因为蚯蚓弟弟是竖着切的。

这个故事告诉我们,有时候呀,我们总是在应该动脑的时候,却动了情!!!

二、闯关阶段

自我介绍:( 您好(人多就说大家好),很荣幸有机会参加此次面试,希望我今天能有好的表现,现在请允许我介绍一下自己:我叫变坏,今年18岁,毕业于XX大学软件工程专业(或者说是XX大学软件工程专业的应届生),在大学期间专业课学习了java这门编程语言,自己在网上也学习了一些相关的技术,在校期间自己也曾和同学使用java开发过一些项目,在学校也曾考取过相关的证书,获得过一些比赛的奖,大学期间还担任过课代表,由于毕业将近,本人决定踏上社会道路,因此在XX平台看到贵公司的招聘,在此之前也曾在网上了解过贵公司(不要去问公司业务,网上都可以查的),巴拉巴拉吹一吹。从以上简单的自我介绍里,我希望公司能给我一个展示自己能力的机会)

1 多线程的几种实现方式

继承Thread类,实现Runnable接口,实现Callable接口,线程池

下面是我的csdn博客的一篇文章,详细说明了: https://blog.csdn.net/sihai12345/article/details/80256322

2 线程join()方法

Thread中,join()方法的作用是调用线程等待该线程完成后,才能继续用下运行。

public static void main(String[] args) throws InterruptedException
    {
        System.out.println("main start");

        Thread t1 = new Thread(new Worker("thread-1"));
        t1.start();
        t1.join();
        System.out.println("main end");
    }

在上面的例子中,main线程要等到t1线程运行结束后,才会输出“main end”。如果不加t1.join(),main线程和t1线程是并行的。而加上t1.join(),程序就变成是顺序执行了。

我们在用到join()的时候,通常都是main线程等到其他多个线程执行完毕后再继续执行。其他多个线程之间并不需要互相等待。

下面这段代码并没有实现让其他线程并发执行,线程是顺序执行的。

public static void main(String[] args) throws InterruptedException
    {
        System.out.println("main start");

        Thread t1 = new Thread(new Worker("thread-1"));
        Thread t2 = new Thread(new Worker("thread-2"));
        t1.start();
        //等待t1结束,这时候t2线程并未启动
        t1.join();

        //t1结束后,启动t2线程
        t2.start();
        //等待t2结束
        t2.join();

        System.out.println("main end");
    }

为了让t1、t2线程并行,我们可以稍微改一下代码,下面给出完整的代码:

public class JoinTest
{

    public static void main(String[] args) throws InterruptedException
    {
        System.out.println("main start");

        Thread t1 = new Thread(new Worker("thread-1"));
        Thread t2 = new Thread(new Worker("thread-2"));

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("main end");
    }
}

class Worker implements Runnable
{

    private String name;

    public Worker(String name)
    {
        this.name = name;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 10; i++)
        {
            try
            {
                Thread.sleep(1l);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println(name);
        }
    }

}

3 ArrayList的remove操作

ArrayList有俩个remove()重载方法,一个参数是int类型,另一个参数是Object类型,remove(1)是删除索引为1的元素,remove(new Integer(1))是删除元素1,底层是用equals进行比较的。

先看迭代器的 next 方法
public E next() {
    // 这个方法主要是检查光标是否越界的
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}  
/**
* 在对一个集合对象进行跌代操作的同时,并不限制对集合对象的元素进行操作
* 这些操作包括一些可能引起跌代错误的add()或remove()等危险操作。
* 在AbstractList中,使用了一个简单的机制来规避这些风险。 
* 这就是modCount和expectedModCount的作用所在
*/
final void checkForComodification() {
    if (modCount != expectedModCount)
         throw new ConcurrentModificationException();
}

我们可以看到,list 每次获取下一个对象前都要去检查一下光标是否越界。在 ArrayList 的所有涉及结构变化的方法中都增加 modCount 的值,包括:add()、remove()、addAll()、removeRange() 及 clear() 方法。这些方法每调用一次,modCount 的值就加 1。而变量 expectedModCount 在迭代开始时便会被赋值成 modCount 的值。所以在循环遍历中,改变结构变化的方法,例如 add()、remove() 都会是 modCount 增长 1 ,而 expectedModCount 却不会变化。

注意,以上讲的涉及到结构变化的方法是 ArrayList 的方法,不是其内部类 Itr 的方法。

来看一下 ArrayList 的 remove 方法
public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}  

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
        } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
        }
    return false;
}

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

从上面源码中我们不难发现,ArrayList 中两个 remove() 方法都对 modCount 进行了自增,那么我们在用迭代器迭代的时候,若是删除 末尾 的元素,则会造成 modCount 和 expectedModCount 的不一致导致异常抛出。

为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常?

我们来看一下 ArrayList 中的内部类 Itr 。我们在调用迭代器的 Next() 方法之前会先调用 hasNext() 方法。

public boolean hasNext() {
    return cursor != size;
}

从代码上我们可以看出判断条件是当 cursor != size 的时候,才会进行下一次循环,而 cursor 参数是我们迭代循环的下标,在我们删除倒数第二个元素后,此时 list 的大小减了 1,再进入下一次循环后会出现 cursor == size ,也就是 hasNext() 便会返回 false 终止了循环。实际上 modCount 的数值也增加了 1,只不过循环没发执行到那里,所以异常也就不会被抛出来了。

for 下标遍历删除

从源码上我们可以看出,在利用 for 下标进行遍历的时候,并不会触发 checkForComodification() 方法,所以此时只要要删除的位置比列表大小小时都不会出错。

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
} 

在 ArrayList 源码介绍中,作者是推荐使用 for ( int i; i < size; i++) 方式去遍历,而不是 foreach 或者迭代,这个主要是因为 list 接口实现了 RandomAccess 接口。 实现这个接口的集合是随机无序的,所以遍历的时候一般使用上述的 for,记住一点就可以了所有实现了 RandomAccess 接口的集合都是用一般 for 就可以了(可以通过 api 查看那些集合实现了 RandomAccess)。

Iterator 迭代遍历删除

这里我们将的 Iterator 遍历删除调用的方法不是 ArrayList 的 remove 方法,而是其内部类的 remove 方法

我们看源码不难发现,在 Itr 类中,属性 expectedModCount 在调用外部的 remove() 方法后再次被赋值,此时 expectedModCount 是等于 modCount 的。

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    // 这里检查时候还没有进行删除操作
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        // 先进行了 remove 操作后 再重新对 expectedModCount 进行赋值
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

所以在使用 Iterator 进行遍历删除时不会出现 ConcurrentModificationException 异常。

  • 原文 https://blog.csdn.net/qq_35108975/article/details/82971437

4 HashMap为啥不安全

resize死循环,fail-fast(快速失败)

具体查看 https://www.jianshu.com/p/e2f75c8cce01

5 HashMap1.7和1.8区别

1.7数组+链表,头插入,1.8数组+链表+红黑树,尾插入。resize方法、hash计算方式、扩容后的位置计算方式等

具体详解 https://blog.csdn.net/qq_30683329/article/details/80454518

6 HashMap如何扩容及Put方法

具体详情 https://blog.csdn.net/fendianli6830/article/details/81102709

7 TreeMap

默认按照Key的字典序升序排列,底层红黑树+compareTo()方法,大致就是和根节点比较,小于根节点往左子树继续去比较,大于根节点往右子树去比较咯等等

明哥文章 https://www.cnblogs.com/chenssy/p/3746600.html

8 concurrentHashMap底层原理

推荐文章 https://blog.csdn.net/sihai12345/article/details/79383766

9 如何确保一个集合不被修改

使用Collections.unmodifiableCollection(Collection c)方法创建只读集合

10 Iterator和ListIterator有什么区别

前者只能遍历不能修改,后者可以修改元素并且可以逆向遍历、定位当前索引位置,但后者只能用于List及其子类型

11 快速失败和安全失败

fail-fast:遍历时对集合进行增删改会抛出Concurrent Modification Exception异常,一般的java.util包下的集合用的就是快速失败。安全失败就是采用复制方式,修改原集合,虽然不会报错,但是也没办法访问修改后的元素。一般在java.util.concurrent包下集合用的就是安全失败

具体查看:ArrayList源码详解

12 如果clone单例模式的对象会怎样

不行,这里必须要实现Cloneable接口,所以需要单例的类不能去实现Clonable接口。反射应该可以去获取私有的构造方法从而破坏单例

13 hibernate和mybatis区别

相同方面:ORM、都支持jdbc事务 不同点:sql方面、缓存方面

具体查看:hibernate和mybatis区别

14 mysql联合索引和聚集索引

联合索引就是多列组成的索引,聚集索引CLUSTERED,聚集索引的顺序与数据真实的物理存储顺序一致,特别快,主键!=聚集索引

15 行锁和表锁

表锁:不会出现死锁,发生锁冲突几率高,并发低。 行锁:会出现死锁,发生锁冲突几率低,并发高

16 b树索引和Hash索引区别

大量不同数据查找,hash索引比B树索引效率高,hash索引不支持联合索引的最左匹配规则,hash索引不支持排序,hash索引不支持模糊查找

17 软连接硬链接

软连接:新建一个文件来指向别的文件,原文件删除则不可用,可跨文件系统。 硬链接:原来的inode link count域再增加1(在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号inode ),不可跨文件系统,删除原文件也可继续使用。ln是创建硬链接 ln -s是创建软连接)

18 linux查看进程的命令

ps命令 -A:所有的进程均显示出来、-a显示现行终端机下的所有进程,包括其他用户的进程 、-u以用户为主的进程状态

linux常用命令:linux常用命令这篇文章就够了

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 好好学java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、情话部分
  • 二、闯关阶段
    • 先看迭代器的 next 方法
      • 来看一下 ArrayList 的 remove 方法
        • for 下标遍历删除
          • Iterator 迭代遍历删除
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档