瓜子二手车面经

一面:

面试官是个比较耐心的大姐姐类型,很多问题都不会也跟我说没关系,总共面了45分钟。我问了一下瓜子的开发岗都是在一起面得,内容都很相似,并没有针对特定的岗位问对应的内容,下面说下面试过程趴。

1、自我介绍和项目介绍,没撑过5分钟,让直接讲里面用到的技术栈,不要讲业务逻辑。用到的技术包括缓冲区、kafka、spark、hbase之类的。

2、java中的弱引用与强引用;

  • 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
    如果强引用对象不使用时,需要弱化从而使GC能够回收,如下:

    strongReference = null;

    显式地设置strongReference对象为null,或让其超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于GC算法。

  • 软引用如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。(软引用可用来实现内存敏感的高速缓存。)软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

    当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收,也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象,而且虚拟机会尽可能优先回收长时间闲置不用软引用对象。对那些刚构建的或刚使用过的**“较新的”软对象会被虚拟机尽可能保留**,这就是引入引用队列ReferenceQueue的原因。

    应用场景:

    浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

    如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建;
    如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出。
    这时候就可以使用软引用,很好的解决了实际的问题。

  • 弱引用软引用的区别在于:只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。

  • 虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

特别注意,在实际程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

3、让实现LRU算法,讲讲思路;

最近最少使用算法。常用于页面置换算法,是为虚拟页式存储管理服务的。当一个数据最近一段时间没有被访问,未来被访问的概率也很小。当空间被占满后,最先淘汰最近最少使用的数据。

思路一:采用链表维持一个虚拟队列,有新元素进入时,插入到头结点;当某个结点被访问时,将其移到头结点;当超过队列容量时,删除表尾元素。

思路二:采用容器与计数的方法。元素按顺序进入容器,每个元素有一个计数器,按照进入容器的顺序计数,每被访问一次计数器加一;

4、三次握手及详细过程,什么时候调用accept()函数,有什么作用;timewaite发生在什么时候,为什么要wait?

1.服务器调用listen进行监听。

2.客户端调用connect来发送syn报文
服务器协议栈负责三次握手的交互过程。

3.连接建立后,往listen队列中添加一个成功的连接,直到队列的最大长度。

4.服务器调用accept从listen队列中取出一条成功的tcp连接,listen队列中的连接个数就少一个

5、讲一下session和cookie的区别;在服务器上多台机器多实例需要共享session中的内容该怎么办?

session在服务端,它 的含义是指一类用来在客户端与服务器之间保持状态的解决方案。每个客户端对应一个session,每个session有一个session id,

cookie在客户端,浏览器缓存访问信息、记录与资源在cookie中。 cookie的内容主要包括:名字,值,过期时间,路径和域。 由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路 径的后面,附加方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面.

共享一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里 的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘 上,当服务器进程重新启动或这些信息将能够被再次使用,Weblogic Server支持的持久性方 式包括文件、数据库、客户端cookie保存和复制。复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息 被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其 他进程中取得session。

6、socket编程中,客户端和服务端建立连接需要用到哪些函数并解释一下作用。

7、socket连接中shutdown()和close()有什么区别。

close关闭的是双向的,in out都关了~~但是是引用计数--直到--到为0才真正的关闭~因而是线程安全的。

shutdown 是单向关闭,通过参数指定关闭不同的方向,直接强制关闭,非线程安全;

8、多线程和多进程的区别和联系,多进程访问临界资源如何处理。

进程是资源分配的最小单位,线程是CPU调度的最小单位

多线程之间共享同一
个进程的地址空间,线程间通信简单,同步复杂,线程创建、销毁和切换简单,速度快,占用内存少,适用于多核分布式系统,但是线程间会相互影响,一个线程意
外终止会导致同一个进程的其他线程也终止,程序可靠性弱。

多进程间拥有各自
独立的运行地址空间,进程间不会相互影响,程序可靠性强,但是进程创建、销毁
和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步简单,适用于多核
、多机分布。

8、手撕算法题,输出某二叉树中路径和等于定值n的所有路径,路径:从根节点到叶结点或从叶节点到叶节点

想法:将二叉树建立子节点指向父节点的引用,BFS遍历某叶节点或root结点到剩下结点的的路径,和为n则输出。

9、手撕代码,给出二叉树中的两个结点,找出它们的最低公共父节点。

BFS或DFS找到指点两个结点n1和n2,记录root结点到n1和n2结点的路径,最后一个重复结点即为最低公共父结点。

二面:

一面面经写到一半,然后打电话来了二面,效率是真的高。二面小哥超帅的,总共面了47分钟,大多时间都在撕代码,或者在撕代码的路上。

自我介绍,然后讲3分钟项目,根据项目提了一些对应的问题,然后就是你问我答环节。

1、kafka当分区数大于消费者数量的时候如何消费,反过来呢?

分区数>消费者数:一个消费者消费固定负责一个或多个分区数据;

分区数<消费者数:一个消费者固定消费一个分区,多余消费者空闲。

2、kafka如何保证多分区数据的顺序性。

采用重排序、阻塞、单分区。

3、kakfa在消费者端调试过哪些参数,有什么意义;在使用poll拉取消息的时候有个除了有个每次拉取的数据条数设置还有哪些参数。

	// 创建一份kafka参数map
	Map<String, Object> kafkaParams = new HashMap<String, Object>();
	kafkaParams.put("bootstrap.servers", "master:9092,slave1:9092,slave2:9092");
	kafkaParams.put("key.deserializer", StringDeserializer.class);
	kafkaParams.put("value.deserializer", ByteArrayDeserializer.class);
	kafkaParams.put("group.id", "topic6-groud1");
	kafkaParams.put("auto.offset.reset", "latest");
	kafkaParams.put("enable.auto.commit", true);
	Collection<String> topics = Arrays.asList("topic6");

while (true) {
	ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(500));
	for (ConsumerRecord<String, byte[]> record : records) {
    }
}

4、介绍一下spark流式计算框架,RDD弹性数据集以及对其的了解。

5、hbase优化策略调过哪些参数。

6、多线程与多进程的区别(两面都问到过),在线程和进程切换时有什么区别,哪个开销大,为什么?

7、排序算法中有哪些是稳定的,哪些不稳定

稳定算法:冒泡排序、插入排序、归并排序、基数排序

不稳定算法:选择排序、快速排序、希尔排序、堆排序

8、linux命令,查看内存情况,剩余磁盘空间,网络状态

top、df -lh、netstat

9、构造最小生成树Prim算法(不记得)

10、迪杰斯特拉算法求最短路径(还是不记得,默默心疼一波自己)

11、手撕一个算法题

有多个柱子靠在一起,柱子宽度固定为1,给定一个数组,表示每个柱子的高度,问从柱子上方泼水,柱子中最多能容纳多少水。示意图如下

首先想到的是计算每个柱子相邻的两个柱子较低的那个高度,然后相加。在小哥的提醒下发现,当如图中第7根和第9根酱紫的时候不对。

然后我又想了一会儿,提出对每个柱子遍历,找到其左右两边的最高的那个柱子,取较低的那个然后所有相加。然后小哥说复杂度太大了,有没有办法在O(n)的复杂度内把左右两边的最高柱子算出来。

然后我想了会儿,采用遍历一次,每次用tempL和tempR记录当前最大值,放进辅助数组里,然后让我用代码写一下。然后在作业本上撕了一下:

答案还在手打中,待续。。。