可酷淘面试经历(2021-7.13)
面试时间从14:30-18:00,过程包括笔试1小时+一面1小时+二面1小时。
可酷淘是一家广州的游戏公司,产品有小森灵、少年名将等,面试地点在天河区天河公园旁边。
笔试:
大概二三十道题,题型为选择题可编程题,选择题考了概论问题,计算机网络,数据结构这些,编程题有四道题:1)两个整数的汉明距离、2)二叉树的直径问题、3)青蛙走楼梯问题(动态规划)、4)游戏聊天的屏蔽字实现思路;
一面:
一面是技术面,有两个面试官,问的问题很多,只记得一小部分,很多都是基础知识:
- ArrayList和Object[]的区别;
- 如何实现将一个整数划分k组;
- 如何判断链表有环(快慢指针);
- mysql索引和引擎;
- TCP/IP协议、TCP和UDP的区别和使用场景、TCP如何保证可靠传输、TCP的三次握手和四次挥手;
- 创建对象有几种方式、new和IOC的区别(为什么要IOC)、IOC一定是单例模式吗?
- 抽象类和接口的区别;
- C++和JAVA的对比;
- 你的项目中使用redis做缓存为什么不用map做缓存,redis和map做缓存的优缺点;
- 动态代理;
- java反射会调用构造函数吗?
- 一个客户预约挂号之后,然后减库存,如何实时刷新给其他用户;
- 你常用的数据结构有哪些,链表和数据的区别;
- 说一下equal()和hashCode();
- 两个队列实现一个栈;
- 访问一个域名,整个过程发生了什么,域名解析如何实现;
- 除了http,还了解过那些协议(websocket);
- …
二面:
二面也是两个面试官,不过问的不是java的基础,而是问一些业务场景的实现,还问了一下兴趣爱好,未来的规划,对我们公司的产品有什么了解吗,玩过什么游戏,你觉得这游戏那些方面最难实现,对游戏开发岗位有没有兴趣。。。
二面最终过了,但是我以为这样就能拿到offer了,没想到居然还安排了三面,不过本人没有精力转游戏开发,所以就拒绝了三面o(╥﹏╥)o
答案:
-
你的项目中使用redis做缓存为什么不用map做缓存,redis和map做缓存的优缺点;
- Redis 可以实现分布式的缓存,Map 属于本地缓存,只能存在创建它的程序里;
- Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了;
- Redis 缓存有过期机制,Map 本身无此功能
- Redis 可以处理每秒百万级的并发,是专业的缓存服务,Map 只是一个普通的对象;
- Redis 可以用几十 G 内存来做缓存,Map 不行,一般 JVM 也就分几个 G 数据就够大了;
- Redis 有丰富的 API,Map 就简单太多了
- 缓存分为本地缓存和分布式缓存:
- 以java为例,使用自带的map实现的是本地缓存,其最主要的特点是轻量、快速,生命周期随着jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
- 使用redis或memcached之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持redis或memcached服务的高可用,整个程序架构上较为复杂。
-
抽象类和接口的区别:
抽象类:
可以有私有方法或私有变量的,通过把类或者类中的方法声明为abstract来表示一个类是抽象类,被声明为抽象的方法不能包含方法体。子类实现方法必须含有相同的或者更低的访问级别(public->protected->private)。抽象类的子类为父类中所有抽象方法的具体实现,否则也是抽象类。
接口:
是公开的,不能有私有的方法或变量,接口中的所有方法都
没有方法体
,通过关键字interface
实现。 接口可以被看作是抽象类的变体,接口中所有的方法都是抽象的,可以通过接口来间接的实现
多重继承
。接口中的成员变量都是static final
类型,由于抽象类可以包含部分方法的实现,所以,在一些场合下抽象类比接口更有优势
。相同点:
- 都不能被实例化;
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点:
- 接口只有定义,**不能有方法的实现**,`java 1.8中可以定义default方法体`,而抽象类可以有方法(非抽象方法)定义与实现;(注意:**java9之后接口可以有私有方法的定义**) - 实现接口的关键字为`implements`,继承抽象类的关键字为`extends`,一个类**可以实现多个接口**,但一个类只能继承一个抽象类,所以,使用接口可以间接地实现多继承; - 接口强调特定功能的实现,而抽象类强调所属关系。
- 接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
- 接口被用于常用的功能,便于日后维护和添加删除,而抽象类更倾向于充当公共类的角色,不适用于日后重新对立面的代码修改。功能需要累积时用抽象类,不需要累积时用接口。
-
创建对象有几种方式、new和IOC的区别(为什么要IOC)、IOC一定是单例模式吗?
-
new和IOC的区别:
- Spring默认创建的单例Bean,集中管理Bean的生命周期,其中也是用到池化技术(容器集合),当使用Bean时会从池中获取,当使用完后重新放入池中,不会立马销毁,这样就减少了Bean创建的时间和节省CPU资源。
- Spring这种控制反转降低了代码的耦合度,当使用到了具体的Bean时才会进行组合,同时也解决了复杂的循环依赖问题,比如A依赖B,B依赖C,C依赖D,D依赖A这种多个类相互依赖,创建起来就麻烦了,当存在循环依赖时处理更麻烦。
-
创建对象有几种方式:
- new关键字;
- 反射(Class对象的newInstance()方法);
- 反序列化;(不会调用构造方法);
- 使用**clone()**方法(不会调用构造方法);
-
IOC一定是单例模式吗:
-
不一定。Spring容器管理的bean在默认情况下是单例的,也即,一个bean只会创建一个对象,存在内置map中,之后无论获取多少次该bean,都返回同一个对象。
-
但是在实际开发中是存在多例的需求的,Spring也提供了选项可以将bean设置为多例模式。
多例 scope="prototype"
单例 scope=“singleton” (默认方式)
例:
<bean id="dog" class="com.dreamguard.domain.Dog" scope="prototype"></bean>
-
-
-
C++和JAVA的对比:
- C++中有指针,Java中没有指针,但是有引用;
- C++支持多继承,Java中类都是单继承的。但是继承都有传递性,同时Java中的接口是多继承,类对接口的实现也是多实现。
- C++需要自己去管理内存,但是Java中JVM有自己的GC机制,虽然有自己的GC机制,但是也会出现OOM和内存泄漏的问题。
- C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。
- C++比Java执行速度快,但是Java可以利用JVM跨平台。
- Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程的东西,比如是全局变量和全局函数。
-
java反射会调用构造函数吗?
java反射创建对象会调用构造函数;newInstance()创建对象只能调用无参构造函数;getConstructor(String.class,Integer.class).newInstance(“张三”,12)可以调用有参构造函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NSFk3ul9-1630982568333)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20210715170951172.png)]
-
你常用的数据结构有哪些,链表和数组的区别:
数组、链表、二叉树、图、栈、队列…
- 区别一:物理地址存储的连续性
- 数组的元素在内存中是连续存放的。
- 链表的元素在内存中不一定是连续存放的,通常是不连续的。
- 区别二:访问速度
- 数组的访问速度很快,因为数组可以根据下标进行快速定位。
- 链表的访问速度较慢,因为链表访问元素需要移动指针。
- 区别三:添加、删除元素速度
- 数组的元素增删速度较慢,因为需要移动大量的元素。
- 链表的元素增删速度较快,因为只需要修改指针即可。
- 区别一:物理地址存储的连续性
-
说一下equal()和hashCode():
hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equal()里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
- equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。 - hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所有对于需要大量并且快速的对比的话如果都用equal()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
为什么在重写equals方法的时候要重写hashcode的方法?
上述了两种方法的关系之后,我们知道判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。