黑马程序员技术交流社区

标题: 【上海校区】【Java】几道常见的秋招面试题 [打印本页]

作者: 不二晨    时间: 2018-10-30 09:21
标题: 【上海校区】【Java】几道常见的秋招面试题
前言
只有光头才能变强
Redis目前还在看,今天来分享一下我在秋招看过(遇到)的一些面试题(相对比较常见的)
0、final关键字
简要说一下final关键字,final可以用来修饰什么?
这题我是在真实的面试中遇到的,当时答得不太好,现在来整理一下吧。
final可以修饰类、方法、成员变量
值得一说的是:并不是被final修饰的成员变量就一定是编译期常量了。比如说我们可以写出这样的代码:private final int java3y = new Randon().nextInt(20);
你有没有这样的编程经验,在编译器写代码时,某个场景下一定要将变量声明为final,否则会出现编译不通过的情况。为什么要这样设计?
在编写匿名内部类的时候就可能会出现这种情况,匿名内部类可能会使用到的变量:
class Outer {    // string:外部类的实例变量    String string = "";    //ch:方法的参数    void outerTest(final char ch) {        // integer:方法内局部变量        final Integer integer = 1;        new Inner() {            void innerTest() {                System.out.println(string);                System.out.println(ch);                System.out.println(integer);            }        };    }    public static void main(String[] args) {        new Outer().outerTest(' ');    }    class Inner {    }}复制代码其中我们可以看到:方法或作用域内的局部变量和方法参数都要显示使用final关键字来修饰(在jdk1.7下)!


如果切换到jdk1.8编译环境下,可以通过编译的~


下面我们首先来说一下显示声明为final的原因:为了保持内部外部数据一致性
为什么仅仅针对方法中的参数限制final,而访问外部类的属性就可以随意
内部类中是保存着一个指向外部类实例的引用,内部类访问外部类的成员变量都是通过这个引用。
那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候,表面上看起来好像都成功了,但实际上并不会影响到外部的变量。所以,Java为了不让自己看起来那么奇怪,才加了这个final的限制。
参考资料:
一、char和varchar的区别选用考量:
二、多个线程顺序打印问题
三个线程分别打印A,B,C,要求这三个线程一起运行,打印n次,输出形如“ABCABCABC....”的字符串。
原博主给出了4种方式,我认为信号量这种方式比较简单和容易理解,我这里粘贴一下(具体的可到原博主下学习)..
public class PrintABCUsingSemaphore {    private int times;    private Semaphore semaphoreA = new Semaphore(1);    private Semaphore semaphoreB = new Semaphore(0);    private Semaphore semaphoreC = new Semaphore(0);    public PrintABCUsingSemaphore(int times) {        this.times = times;    }    public static void main(String[] args) {        PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10);        // 非静态方法引用  x::toString   和() -> x.toString() 是等价的!        new Thread(printABC::printA).start();        new Thread(printABC::printB).start();        new Thread(printABC::printC).start();        /*new Thread(() -> printABC.printA()).start();        new Thread(() -> printABC.printB()).start();        new Thread(() -> printABC.printC()).start();*/    }    public void printA() {        try {            print("A", semaphoreA, semaphoreB);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void printB() {        try {            print("B", semaphoreB, semaphoreC);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void printC() {        try {            print("C", semaphoreC, semaphoreA);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    private void print(String name, Semaphore current, Semaphore next)            throws InterruptedException {        for (int i = 0; i < times; i++) {            current.acquire();            System.out.print(name);            next.release();        }    }}复制代码2018年9月14日18:15:36  yy笔试题就出了..
三、生产者和消费者在不少的面经都能看到它的身影哈~~~基本都是要求能够手写代码的。
其实逻辑并不难,概括起来就两句话:
基于原作者的代码,我修改了部分并给上我认为合适的注释(下面附上了原作者出处,感兴趣的同学可到原文学习)
生产者:
import java.util.Random;import java.util.Vector;import java.util.concurrent.atomic.AtomicInteger;public class Producer implements Runnable {    // true--->生产者一直执行,false--->停掉生产者    private volatile boolean isRunning = true;    // 公共资源    private final Vector sharedQueue;    // 公共资源的最大数量    private final int SIZE;    // 生产数据    private static AtomicInteger count = new AtomicInteger();    public Producer(Vector sharedQueue, int SIZE) {        this.sharedQueue = sharedQueue;        this.SIZE = SIZE;    }    @Override    public void run() {        int data;        Random r = new Random();        System.out.println("start producer id = " + Thread.currentThread().getId());        try {            while (isRunning) {                // 模拟延迟                Thread.sleep(r.nextInt(1000));                // 当队列满时阻塞等待                while (sharedQueue.size() == SIZE) {                    synchronized (sharedQueue) {                        System.out.println("Queue is full, producer " + Thread.currentThread().getId()                                + " is waiting, size:" + sharedQueue.size());                        sharedQueue.wait();                    }                }                // 队列不满时持续创造新元素                synchronized (sharedQueue) {                    // 生产数据                    data = count.incrementAndGet();                    sharedQueue.add(data);                    System.out.println("producer create data:" + data + ", size:" + sharedQueue.size());                    sharedQueue.notifyAll();                }            }        } catch (InterruptedException e) {            e.printStackTrace();            Thread.currentThread().interrupted();        }    }    public void stop() {        isRunning = false;    }}复制代码消费者:
import java.util.Random;import java.util.Vector;public class Consumer implements Runnable {    // 公共资源    private final Vector sharedQueue;    public Consumer(Vector sharedQueue) {        this.sharedQueue = sharedQueue;    }    @Override    public void run() {        Random r = new Random();        System.out.println("start consumer id = " + Thread.currentThread().getId());        try {            while (true) {                // 模拟延迟                Thread.sleep(r.nextInt(1000));                // 当队列空时阻塞等待                while (sharedQueue.isEmpty()) {                    synchronized (sharedQueue) {                        System.out.println("Queue is empty, consumer " + Thread.currentThread().getId()                                + " is waiting, size:" + sharedQueue.size());                        sharedQueue.wait();                    }                }                // 队列不空时持续消费元素                synchronized (sharedQueue) {                    System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size());                    sharedQueue.notifyAll();                }            }        } catch (InterruptedException e) {            e.printStackTrace();            Thread.currentThread().interrupt();        }    }}复制代码Main方法测试:
import java.util.Vector;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test2 {    public static void main(String[] args) throws InterruptedException {        // 1.构建内存缓冲区        Vector sharedQueue = new Vector();        int size = 4;        // 2.建立线程池和线程        ExecutorService service = Executors.newCachedThreadPool();        Producer prodThread1 = new Producer(sharedQueue, size);        Producer prodThread2 = new Producer(sharedQueue, size);        Producer prodThread3 = new Producer(sharedQueue, size);        Consumer consThread1 = new Consumer(sharedQueue);        Consumer consThread2 = new Consumer(sharedQueue);        Consumer consThread3 = new Consumer(sharedQueue);        service.execute(prodThread1);        service.execute(prodThread2);        service.execute(prodThread3);        service.execute(consThread1);        service.execute(consThread2);        service.execute(consThread3);        // 3.睡一会儿然后尝试停止生产者(结束循环)        Thread.sleep(10 * 1000);        prodThread1.stop();        prodThread2.stop();        prodThread3.stop();        // 4.再睡一会儿关闭线程池        Thread.sleep(3000);        // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束)        service.shutdown();    }}复制代码另外,上面原文中也说了可以使用阻塞队列来实现消费者和生产者。这就不用我们手动去写wait/notify的代码了,会简单一丢丢。可以参考:
四、算法[1]
我现在需要实现一个栈,这个栈除了可以进行普通的push、pop操作以外,还可以进行getMin的操作,getMin方法被调用后,会返回当前栈的最小值,你会怎么做呢?你可以假设栈里面存的都是int整数
解决方案:
import java.util.ArrayList;import java.util.List;public class MinStack {    private List<Integer> data = new ArrayList<Integer>();    private List<Integer> mins = new ArrayList<Integer>();    public void push(int num) {        data.add(num);        if (mins.size() == 0) {            // 初始化mins            mins.add(num);        } else {            // 辅助栈mins每次push当时最小值            int min = getMin();            if (num >= min) {                mins.add(min);            } else {                mins.add(num);            }        }    }    public int pop() {        // 栈空,异常,返回-1        if (data.size() == 0) {            return -1;        }        // pop时两栈同步pop        mins.remove(mins.size() - 1);        return data.remove(data.size() - 1);    }    public int getMin() {        // 栈空,异常,返回-1        if (mins.size() == 0) {            return -1;        }        // 返回mins栈顶元素        return mins.get(mins.size() - 1);    }}复制代码继续优化:
但是,如果一直push的值是最小值,那我们的mins辅助栈还是会有大量的重复元素,此时我们可以使用索引(mins辅助栈存储的是最小值索引,非具体的值)!
最终代码:
import java.util.ArrayList;import java.util.List;public class MinStack {    private List<Integer> data = new ArrayList<Integer>();    private List<Integer> mins = new ArrayList<Integer>();    public void push(int num) throws Exception {        data.add(num);        if(mins.size() == 0) {            // 初始化mins            mins.add(0);        } else {            // 辅助栈mins push最小值的索引            int min = getMin();            if (num < min) {                mins.add(data.size() - 1);            }        }    }    public int pop() throws Exception {        // 栈空,抛出异常        if(data.size() == 0) {            throw new Exception("栈为空");        }        // pop时先获取索引        int popIndex = data.size() - 1;        // 获取mins栈顶元素,它是最小值索引        int minIndex = mins.get(mins.size() - 1);        // 如果pop出去的索引就是最小值索引,mins才出栈        if(popIndex == minIndex) {            mins.remove(mins.size() - 1);        }        return data.remove(data.size() - 1);    }    public int getMin() throws Exception {        // 栈空,抛出异常        if(data.size() == 0) {            throw new Exception("栈为空");        }        // 获取mins栈顶元素,它是最小值索引        int minIndex = mins.get(mins.size() - 1);        return data.get(minIndex);    }}复制代码参考资料:
五、多线程下的HashMap众所周知,HashMap不是一个线程安全的类。但有可能在面试的时候会被问到:如果在多线程环境下使用HashMap会有什么现象发生呢??
结论:
参考资料:
六、Spring和Springboot区别
一、SpringBoot是能够创建出独立的Spring应用程序的
二、简化Spring配置
三、嵌入式Tomcat,Jetty容器,无需部署WAR包
七、G1和CMSG1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
拓展阅读:
八、海量数据解决方案海量数据的处理也是一个经常考的知识点,无论在面试还是在笔试中都是比较常见的。有幸读了下面的文章,摘录了一些解决海量数据的思路:
详细可参考原文:
九、幂等性9.1HTTP幂等性昨天去做了一套笔试题,经典的HTTP中get/post的区别。今天回来搜了一下,发现跟之前的理解有点出入
如果一个人一开始就做Web开发,很可能把HTML对HTTP协议的使用方式,当成HTTP协议的唯一的合理使用方式。从而犯了以偏概全的错误
单纯以HTTP协议规范来说,可能我们之前总结出的GET/POST区别就没用了。(但通读完整篇文章,我个人认为:如果面试中有GET/POST区别,还是默认以Web开发场景下来回答较好,这也许是面试官想要的答案)
参考资料:

其中也学习到了幂等性这么一个概念,于是也做做笔记吧~~~
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
从定义上看,HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用
HTTP的GET/POST/DELETE/PUT方法幂等的情况:
题外话:
HTTP协议本身是一种面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不同的方式:一种是RESTful的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定(充分利用了HTTP的方法);另一种是SOA的,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议
参考资料:
9.2接口幂等性在查阅资料的时候,可以发现很多博客都讲了接口的幂等性。从上面我们也可以看出,POST方法是非幂等的。但我们可以通过一些手段来令POST方法的接口变成是幂等的。
说了那么多,那接口设计成幂等的好处是什么????
举个例子说一下非幂等的坏处:
如果我的抢课接口是幂等的话,那就不会出现这个问题了。因为幂等是多次请求某一个资源应该具有同样的副作用。
说白了,设计幂等性接口就是为了防止重复提交的(数据库出现多条重复的数据)!
网上有博主也分享了几条常见解决重复提交的方案:
参考资料:


【转载】
作者:Java3y
链接:https://juejin.im/post/5bceb67ee51d457aa25bd1eb




作者: 不二晨    时间: 2018-10-31 14:23

作者: 魔都黑马少年梦    时间: 2018-11-1 16:08





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2