简单的自我介绍
1、写一个单例,自己写了懒汉模式和饿汉模式(源码在util中)
//懒汉模式
class Singleton{
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(Singleton singleton){
if(singleton==null){
return new Singleton();
}
return singleton;
}
}
//饿模式
class Singleton{
private static Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
怎么调用这个实例
public static void main(String[] args) {
Singleton1 singleton1=Singleton1.getInstance(null);
Singleton2 singleton2=Singleton2.getInstance();
Singleton1 singleton3=Singleton1.getInstance(singleton1);
}
自己放的致命错误是:没有加关键字static
单例在内存中国保存唯一性,在于1、通过构造函数私有化,不能通过new进行创建对象 2、通过static保证内存中只存在一个对象。注意字段和方法上都要加关键字
二面提到单例在那些情景中应用到:
自己只答了在spring容器的bean,就是应用到单例模式
还可以答:
在数据库连接的时候,用单例,不要每次连接数据库,都创建会话session
2、然后面试官问自己哪方面擅长一点,我就答了多线程这一块
面试官问线程启动的低层实现,就是start方法,自己不知道低层是咋实现的,回答了线程的生命周期,并画图出来了,其中写了创建线程、启动、运行、结束线程,其中会进行线程阻塞,包括线程资源占用,调用wait方法、其他线程让出cpu比如sleep、yeild。
这个画出来,面试官挺高兴的。
3、然后问wait是Thread方法么
不是,是object方法,追问为什么
问题的点1:object是一切类的基类,Thread也是继承object类,故它也可以调用wait方法。
问题的点2:为什么不将wait方法放在Thread,而放在object中。是因为线程对每个对象进行操作时,都是获取该对象的锁就是monitor(即锁),然后通过这个对象来操作。若当前线程等待的资源不满足,就通过这个对象,进行线程挂起。若是通过线程操作,因为当前线程可能会等待多个线程的锁,那么他就要通知多个线程,这样就非常复杂。
这个当前答的非常混乱,但是思想是对的
4、问了线程的实现方式
(1)通过实现runnable
(2)通过继承Thread类
(3)通过ExecutorService实现线程池,然后重写callable中的call接口,将线程返回值保存在future中
5、基于这个ExecutorService,问线程池低层是怎么实现的
真是该死,上次一点资讯就问到了线程池创建的两种方式是什么,那时就没有答出来。面试时介绍了coreSize与max,线程的核心数和最大数之间的变动。讲的很烂,结果面试官问,你是对那个类,进行低层源码看的,这个都没回答出来。答的是ExecutorService,应该是ThreadPoolExecutor。
ThreadPoolExecutor的核心参数
corePoolSize:核心池大小
maxmunPoolSize:线程池最大线程数
keepAliveTime:线程没有任务执行时最多保持多久会终止(注意只有线程数大于corePoolSize时才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的数目小于corePoolSize)
Unit:keepAliveTime的单位时间
workQueue:一个阻塞队列,用来存储等待执行的任务。一般使用LinkedBlockingQueue
(基于链表的队列)
threadFactory:线程工厂,主要用来创建线程
Handler:表示拒绝处理任务时的策略(1、丢弃任务抛出异常2、丢弃任务,不抛出异常3、丢弃队列最前面的任务,重新尝试执行任务 4、调用线程处理任务)
ThreadPoolExecutor的核心方法:
(1)execute是ThreadPoolExecutor的核心方法,通过这个方法向线程池提交一个任务,submit也是向线程池提交一个任务,但是不同的是它有返回值(通过future来获取任务执行结果)(submit的低层实现也是execute)
(2)shutdown()(处于该状态线程池不能接受新的任务,它会等待所有任务执行完毕)和shutdownNow()(尝试终止正在执行的任务)是关闭线程池
线程池执行的过程:
1)通过ThreadPoolExecutor创建线程池(包括corePoolSize的大小、maximum -PoolSize的大小、keepAliveTime的时间、keepAliveTime的单位、workQueue工作队列)
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue
2)然后创建任务
创建的任务,只是实现了runnable接口
3)并将任务提交到线程池
executor.execute(myTask);
任务放入线程池会出现几种情况
(1)线程池中有空闲的线程,那么任务一来,线程就执行任务了
(2)线程中没有空闲的线程,就是任务数量大于corePoolSize,这时候,就将任务放入阻塞队列中,此时线程池就会启动补救措施,在创建线程,新创建的线程小于maximunPoolSize,新创建的线程从阻塞队列中取任务执行。当任务数小于maximunPoolSize时,空闲的线程空闲的时间达到keepAlive,就会消亡,直到线程数小于corePoolSize.
6、写了一个二分查找
public static int find(int[] a,int num){
int low=0,high=0,middle=0;
if(a.length==0){
return -1;
}
high=a.length-1;
while(low<=high){
middle=(low+high)/2;
if(a[middle]>num){
high=middle-1;
}else if(a[middle]==num){
return 1;
}else{
low=middle+1;
}
}
return -1;
}
面试官说代码可能造成内存溢出,我查看了一下,添加了一下输入的判断,这个因为可能造成数组越界。这些代码在编译器中也运行了,没有错误,可能是面试官自己弄错了
7、然后问了二叉排序树、二叉平衡树
什么是二叉排序树,二叉排序树的时间复杂度是多少,二叉排序树存在什么问题
(1)二叉排序树是左子树都比根结点小,右子树都比根结点大,且左右子树也是二叉排序树
(2)二叉排序树的时间复杂度是logn(一定要记牢了)
(3)二叉排序树在最好的状态像折半查找的判定树,最差的是排好序的单支树
为了解决最差排好序的单支数,引入了二叉树上结点的平衡因子,就是二叉树任何一个结点的绝对值不大于1,这就是平衡二叉树。记住平衡二叉树的左右旋转
8、二叉排序树的实现
public class BSTree {
//定义二叉排序树根节点初始值为空
BSTNode root=null;
public void insert(BSTree T,int data){
//构造一个结点
BSTNode node=new BSTNode();
node.data=data;
node.left=null;
node.right=null;
BSTNode x,y=null;//x是指向根结点,y是待插入结点的位置
x=root;
while(x!=null){
y=x;
if(data
x=x.left;
}else if(x.data==data){
break;//表示找到待插入结点的位置
}else{
x=x.right;
}
}
if(y==null){//表示该二叉树排序树是一个空树
T.root=node;//将该结点作为根节点
}else if(y.data>data){
y.left=node;
}else{
y.right=node;
}
}
public BSTNode find(BSTNode root,int data){
if(root==null){
return null;
}
if(root.data==data){
return root;
}else if(root.data>data){
return find(root.left,data);
}else{
return find(root.right,data);
}
}
}
class BSTNode{
int data;
BSTNode left;
BSTNode right;
}
美团1面总结:
错误1:写单例太粗心,少了static,保证字段和方法的唯一性(致命错误),还有在main方法中怎么调用单例实例,自己也优点蒙,虽然写的是对的,感觉还是不自信
错误2:在多线程中,自己wait方法为什么是object方法,而不是Thread方法,解释的不是很到位,还有线程池,说的太乱了
错误3:二叉排序树在有序时,形成单支数,查找效率很低,二叉平衡树,就是解决这种有序情况,是每个结点的平衡因子绝对值不大于1.还有写二叉排序树,写的太慢,还没写完
虽然第一面过了,但是错误很多,致使评价不是很高
1、走上来写一道算法题:
一个很长的字符串,找出字符串中连续字符的最大数
采用hashmap,字符为key,连续的长度为value,若后面有更大的连续字符,则更新value值
这个写的很不错,基本满足面试官的要求
2、一切类的基类(父类)是哪个类,这个类有哪些方法
一切类的父类是object,其中有wait()、notify()、notifyAll()、hashcode()、equal()、toString()、finalize()(在垃圾回收时,对象与GC root失去引用,通过finalize可以避免被垃圾回收,只要下一次重新引用上,就不会被垃圾回收)、clone()(对象拷贝)
3、问了解java集合不,比如hashmap
自己说了hashmap的低层实现,自己说的太粗略,面试官后面总结的时候,就提到要多结合源码和专业术语说。
回答的策略:
(1)hashmap是key-value键值对,低层实现的结构是数组链表。
(2)通过put,将key,value存储到数组链表中,先key通过hash(),获得hash值(通过低层可知是移位实现的)
(3)通过indexFor(hash,table.length),获得数组下标中的值,就是我们说的桶
(4)但是会出现不同的key,有相同的桶,这就是hash冲突(一般的解决办法是:开发定址法(线性探测在散列、二次探测在散列,会产生二次聚集)、链地址法(也是最常用的))
第一次扩容在整个数组链表不满75%,却有链表的长度很长,这时候将链表长度超过8的改成,红黑树。
4、什么是红黑树
画出基本的结构,然后说出红黑树的一些特性
(1)根节点和叶子结点都是黑的
(2)父节点是黑的,子节点就是红的
(3)从一个节点到该节点的子孙结点的所有路径上包含相同数目的黑节点
5、hashmap是线程安全的么
不是,以前通过hashtable来实现线程安全,就是表级锁,但是性能比较低,现在主要用concurrentHashmap,采用行级锁,就是读写分离。介绍concurrentHashmap的低层实现
(1)通过key的hashcode获得hash
(2)Hash通过segmentFor获得Segment(桶锁的范围)
(3)通过lock锁定对象segment,而不是锁定整个concurrentHashmap
(4)判断HashEntry 是否超过阀值(负载因子*数组长度),若超过要进行散列
(5) 没超过,判断键值对是否存在,不存在,采用头插法加入链表中‘;
(6) 然后解锁
6、怎样保存线程安全
自己说了volatitle,能保证线程的安全性(一致性),其实是错的,他只能保证变量的可见性,也不能保证原子性。可以结合内存可见性
其实可用synchronized、lock保证线程的安全性,还可以用wait、notify、notifyAll,其实这个时候,自己可以往各种锁上去讲解
7、对数据库了解么,讲讲存储引擎
存储引擎主要是innodb和myisam
(1)innodb是行级锁、myisam是表级锁,故innodb在并发上比myisam效果好
(2)Innodb支持事务,myisam不支持事务,故Innodb处理上比myisam安全
8、什么是事务的ACID
事务四大特性,分别是原子性、一致性、隔离性和持久性(持久性当时怎么都没想起来)
隔离性主要有四个隔离级别,分别是:
1)未提交读
其他事务可以看到没有提交的事务,会导致脏读
2)已提交读
数据库是读写分离的,事务在读的时候,其他事务是可以修改数据的,这会造成前后读取的数据不相同,这就是不可重复读
3)可重复读
所有被select数据获取的数据,是不能被修改的,但会出现新添加的数据,这就是幻读
4)可串行读
所有事务一个接着一个执行,就可避免幻读
脏读:
是针对事务未提交的,比如一个事务读取另一个事务的数据,但是另一个事务由于问题,进行回滚了,这时候事务就造成了脏读。
不可重复读:
比如事务T1读取了一条数据,另一个事务T2马上修改了这条记录,事务T1在去读这条记录,发现前后数据不一致,这就是不可重复读
幻读:
事务T1按照需求读取了相应的数据,事务t2,插入一条记录,这条记录满足t1的要求,t1重新查询的时候,看到了这条记录,造成了幻读
自己这一块都答晕了,非常混乱
9、然后说对linux了解么
我只说会基本的linux命令,如jps查进程、chomd修改用户权限、chown修改所有者,find和grep查找、软硬链接(软链接相当于一个快捷方式,有自己独立的inode。硬链接有者相同的inode只是文件名不同罢了,从不同方式访问源文件)
10、对网络了解么
讲了一下socket是一个基于客户端和服务器的短连接,然后讲了webSocket基于ws协议的全双工长连接,通过注解onopen,onmessage、onclose进行session的唯一通道,进行通信。问如何不用webSocket,实现长连接。自己不知道。然后面试官说,采用心跳包,就是在快断开连接的时候,通过心跳包,进行连接。
11、然后谈了一下项目,在项目中说了一下爬虫去重复值
对于连接转发的,可以用hashmap,直接去掉。但是对于修改标题的,只是看其相似度。只是说了trie树,这个大坑,把自己害惨了。
Trie树,又称字典树,单词查找树或则前缀树
Trie树的基本性质:
(1)根节点不包含字符,除根节点以外每个节点只包含一个 字符
(2)从根节点到某一个节点,路径上经过的字符。链接起来,为该节点对应的字符串
(3)每个节点的所有子节点包含的字符串都不相同
12、然后谈了一下redis
自己用redis作为缓存,key、value分别是什么,key是秒杀的id,value是秒杀的对象,客户端采用的jedis,然后将id转化为字节数组通过序列化,进行数据的传输.(TCP协议是基于字节流进行传输的)
Redis的五个存储的类型
1、字符串String
2、Hash类型
3、List类型
4、Set类型
5、Sort Set