只有光头才能变强
Redis目前还在看,今天来分享一下我在秋招看过(遇到)的一些面试题(相对比较常见的)html
简要说一下final关键字,final能够用来修饰什么?
这题我是在真实的面试中遇到的,当时答得不太好,如今来整理一下吧。java
final能够修饰类、方法、成员变量mysql
当final修饰方法的时候,说明该方法不能被重写面试
当final修饰成员变量时,有两种状况:redis
值得一说的是:并非被final修饰的成员变量就必定是编译期常量了。好比说咱们能够写出这样的代码:private final int java3y = new Randon().nextInt(20);
算法
你有没有这样的编程经验,在编译器写代码时,某个场景下必定要将变量声明为final,不然会出现编译不经过的状况。为何要这样设计?
在编写匿名内部类的时候就可能会出现这种状况,匿名内部类可能会使用到的变量:sql
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的缘由:为了保持内部外部数据一致性api
为何仅仅针对方法中的参数限制final,而访问外部类的属性就能够随意
内部类中是保存着一个指向外部类实例的引用,内部类访问外部类的成员变量都是经过这个引用。
那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候,表面上看起来好像都成功了,但实际上并不会影响到外部的变量。因此,Java为了避免让本身看起来那么奇怪,才加了这个final的限制。
参考资料:
选用考量:
三个线程分别打印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
的代码了,会简单一丢丢。能够参考:
我如今须要实现一个栈,这个栈除了能够进行普通的push、pop操做之外,还能够进行getMin的操做,getMin方法被调用后,会返回当前栈的最小值,你会怎么作呢?你能够假设栈里面存的都是int整数
解决方案:
使用一个min变量来记住最小值,每次push的时候,看看是否须要更新min。
使用辅助栈来存储最小值。若是当前要push的值比辅助栈的min值要小,那在辅助栈push的值是最小值
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); } }
继续优化:
算法的空间优化:上面的代码咱们能够发现:data栈和mins栈的元素个数老是相等的,mins栈中存储几乎都是最小的值(此部分是重复的!)
可是,若是一直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会有什么现象发生呢??
结论:
put()
的时候致使的多线程数据不一致(丢失数据)resize()
操做会致使环形链表
参考资料:
1、SpringBoot是可以建立出独立的Spring应用程序的
2、简化Spring配置
Spring Boot项目就是为了解决配置繁琐的问题,最大化的实现convention over configuration(约定大于配置)。
3、嵌入式Tomcat,Jetty容器,无需部署WAR包
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在如下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生不少内存碎片。
拓展阅读:
海量数据的处理也是一个常常考的知识点,不管在面试仍是在笔试中都是比较常见的。有幸读了下面的文章,摘录了一些解决海量数据的思路:
Bloom filter布隆过滤器
Hashing
bit-map
堆
双层桶划分----其实本质上就是【分而治之】的思想,重在“分”的技巧上!
数据库索引
倒排索引(Inverted index)
外排序
trie树
分布式处理 mapreduce
详细可参考原文:
昨天去作了一套笔试题,经典的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
方法幂等的状况:
GET
是幂等的,无反作用
http://localhost/order/2
,使用GET
屡次获取,这个ID为2的订单(资源)是不会发生变化的!DELETE/PUT
是幂等的,有反作用
http://localhost/order/2
,使用PUT/DELETE
屡次请求,这个ID为2的订单(资源)只会发生一次变化(是有反作用的)!但继续屡次刷新请求,订单ID为2的最终状态都是一致的 POST
是非幂等的,有反作用的
http://localhost/order
,使用POST
屡次请求,此时可能就会建立多个名称为3y的订单,这个订单(资源)是会屡次变化的,每次请求的资源状态都会变化!题外话:
HTTP协议自己是一种 面向资源的应用层协议,但对HTTP协议的使用实际上存在着两种不一样的方式:一种是 RESTful的,它把HTTP当成应用层协议,比较忠实地遵照了HTTP协议的各类规定( 充分利用了HTTP的方法);另外一种 是SOA的,它并无彻底把HTTP当成应用层协议,而是把HTTP协议做为了传输层协议,而后在HTTP之上创建了本身的应用层协议
参考资料:
在查阅资料的时候,能够发现不少博客都讲了接口的幂等性。从上面咱们也能够看出,POST
方法是非幂等的。但咱们能够经过一些手段来令POST
方法的接口变成是幂等的。
说了那么多,那接口设计成幂等的好处是什么????
举个例子说一下非幂等的坏处:
若是个人抢课接口是幂等的话,那就不会出现这个问题了。由于幂等是屡次请求某一个资源应该具备一样的反作用。
说白了,设计幂等性接口就是为了防止重复提交的(数据库出现多条重复的数据)!
网上有博主也分享了几条常看法决重复提交的方案:
参考资料:
若是以上有理解错的地方,或者说有更好的理解方式,但愿你们不吝在评论区下留言。共同进步!
若是想看更多的 原创技术文章,欢迎你们关注个人 微信公众号:Java3y。Java技术 群讨论:742919422。公众号还有 海量的视频资源哦,关注便可免费领取。