在3月初通过内推投递了今日头条,2日后收到电话确认视频面试。视频面试在牛客网上,会有一个视频以及一个双方都可以看得到的写代码的地方。目前准备的太不充分了,基本就是靠原来的记忆去裸面,代码题基本都没写过。在这里记录一下面试的题目以后也好准备
第一次视频面试
时长1个小时。面试官说正常时长应该是40分钟左右,因为写不出代码卡住了一会。项目经历没问,直接按照技能点问的。
1.python和java的区别
python和java都是面向对象语言。但在python中,可以直接定义函数,使用面向过程的思想进行编程,java则需要将函数封装在类中再进行调用。
python是解释性语言,直接运行;java先要把文件翻译成字节码文件再由jvm进行运行;
python有很多第三方库,但python3对python2的兼容有问题,开发速度快,代码量小;java版本更新稳定,格式要求严格,适合更大型的架构的开发;
python是一个动态语言,运行时可以改变结构,例如给类添加属性,也是在运行时才知道变量的类型,代码中不用声明;java则必须在代码中明确,属于静态语言;
其他就是语法及一些数据结构上的区别;
2.面向对象的特征
封装,继承,多态(事物在运行中存在不同的状态,父类实例指向子类对象,运行时会运行子类的实现)
3.重写与重载
方法的重载只和参数类型及参数个数相关,与返回类型无关
4.String a = new String("abc")分析其存储
在java中存在一个字符串池用于共享。如果是执行 String a = "abc" 则现在池中找是否存在,如果有则添加引用,若没有则创建放于池中,再将引用给a;若是new的,则创建对象“abc”,再引用给a ,a是在栈中的;变量+变量及substring都会采用new的方式产生新对象;
栈内存:基本类型变量及对象的引用变量
堆内存:new出来的对象
5.float是否为基础类型,基本类型有哪些,float a = 0.2是否会报错
基本类型:byte short int long float double boolean char;会报错,0.2默认是double类型,会损失精度所以不会自动转换
6.线性表有哪些,顺序表和链表有哪些不同
线性表的元素是线性1-1关系,具体体现就是顺序表及链表;顺序表的元素在内存中是用一组地址连续的存储单元来进行存储的。
7.linux系统查看日志的命令
/var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全相关的日志信息 /var/log/maillog 与邮件相关的日志信息 /var/log/cron 与定时任务相关的日志信息 /var/log/spooler 与UUCP和news设备相关的日志信息 /var/log/boot.log 守护进程启动和停止相关的日志消息 /var/log/wtmp 该日志文件永久记录每个用户登录、注销及系统的启动、停机的事件 |
查看命令有cat ,more, less ,head等,查找相关内容grep,查找文件find,locate,whereis
8.编程,给定数组,有序,插入一个数的算法,返回应该插入的下标
复杂度较低的算法应该采用二分查找,普通的算法就是依次比较
class Helloworld
{
public static void main(String[] args)
{
int[] arr = {13,15,19,28,33,45,78,106};
int index = insertToArrary(arr,50);
System.out.println("index = "+ index);
}
public static int insertToArrary(int[] arr, int key)
{
//头指针,中指针,尾指针
int max,min,mid;
min = 0;
max = arr.length-1;
while(min<=max)
{
mid = (min+max)/2;
if(key>arr[mid])
min = mid+1;
else if(keymax的情况下,返回min值,就是元素插入的位置
}
}
二分查找本身代码
public static int biSearch(int []array,int a){
int lo=0;
int hi=array.length-1;
int mid;
while(lo<=hi){
mid=(lo+hi)/2;
if(array[mid]==a){
return mid+1;
}else if(array[mid]
9.编程,用两个栈实现一个队列
import java.util.Stack;
public class Demo07 {
Stack stack1 = new Stack();
Stack stack2 = new Stack();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.size()<=0){
while(stack1.size()>0){
/*int data = stack1.peek();//查看栈顶元素,但不移除它
stack1.pop();//弹出栈顶元素
stack2.push(data);//压入
*/
stack2.push(stack1.pop());
}
}
if(stack2.isEmpty()){
try {
throw new Exception("queue is empty.");
} catch (Exception e) {
}
}
/**
* int head = stack2.peek();
* stack2.pop();
*/
int head = stack2.pop();
return head;
}
}
用两个队列实现一个栈,思想就是维持一个队列是空的
import java.util.LinkedList;
public class StackByTwoQueue {
private LinkedList queue1 = new LinkedList();
private LinkedList queue2 = new LinkedList();
/*
* 两个队列实现一个栈
* pop完成出栈操作,push完成入栈操作
*/
public void push(String obj) {
if(queue1.isEmpty()){
queue2.add(obj);
}
if(queue2.isEmpty()){
queue1.add(obj);
}
}
public String pop() {
//两个栈都为空时,没有元素可以弹出
if (queue1.isEmpty()&&queue2.isEmpty()) {
try {
throw new Exception("stack is empty");
} catch (Exception e) {
}
}
if(queue1.isEmpty()){
while(queue2.size()>1){
queue1.add(queue2.poll());
}
return queue2.poll();
}
if(queue2.isEmpty()){
while(queue1.size()>1){
queue2.add(queue1.poll());
}
return queue1.poll();
}
return null;
}
public static void main(String[] args) {
StackByTwoQueue stack = new StackByTwoQueue();
for(int i=0;i<10;i++){
stack.push(i+"");
}
for(int i=0;i<20;i++){
System.out.println(stack.pop());
}
}
}
10.socket编程
socket在应用层之下,传输层之上的一个借口
代码
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) throws Exception {
// 监听指定的端口
int port = 55533;
ServerSocket server = new ServerSocket(port);
// server将一直等待连接的到来
System.out.println("server将一直等待连接的到来");
Socket socket = server.accept();
// 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len,"UTF-8"));
}
System.out.println("get message from client: " + sb);
inputStream.close();
socket.close();
server.close();
}
}
public class SocketClient {
public static void main(String args[]) throws Exception {
// 要连接的服务端IP地址和端口
String host = "127.0.0.1";
int port = 55533;
// 与服务端建立连接
Socket socket = new Socket(host, port);
// 建立连接后获得输出流
OutputStream outputStream = socket.getOutputStream();
String message="你好 yiwangzhibujian";
socket.getOutputStream().write(message.getBytes("UTF-8"));
outputStream.close();
socket.close();
}
}
11.网络协议栈分为几层,TCP/IP运行于第几层,与UDP的区别
物理层,数据链路层,网络层,运输层,应用层,TCP/IP运行于运输层。TCP提供面向连接的服务,保证数据的传递,并提供拥塞控制机制,流模式,UDP提供无连接服务,数据报模式。
12.TCP/IP的握手过程
报文:SYN建立连接,FIN释放连接
总共需要客户端与服务器端发送三个包建立连接 客户端->(SYN)->服务器端 ->(SYN,ACK) ->客户端 ->(ACK)->服务器端
断开连接需要发送4个包 客户端 ->(FIN) ->服务器 ->(ACK)->(FIN) ->客户端 ->(ACK)
13.并发和并行的区别
并发:处理器同时处理多个任务,实际同一时刻只有一个任务在执行
并行:多核多处理器同时处理多个任务
第二天进行了二面,时间大概40分钟,其中问题和第一面有重复。
1.jvm垃圾回收机制及主要算法(相关概念及算法较多,继续学习深入理解java虚拟机后才能回答相关问题)
垃圾回收器负责分配内存,保证所有正在引用的对象存在于内存中,回收代码已不再引用的对象所在的内存。
分代垃圾回收器:不同的生命周期对象放在不同的地址池中,基于弱年代假设:越早分配的对象越容易失效,老对象很少会引用新对象
分为三个分代空间:
年轻代:两个suivivor,1个Eden区
Java应用在分配Java对象时,这些对象会被分配到年轻代堆空间中去 这个空间大多是小对象并且会被频繁回收 由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率 |
老年代:
年轻代堆空间的长期存活对象会转移到(也许是永久性转移)年老代堆空间 这个堆空间通常比年轻代的堆空间大,并且其空间增长速度较缓 由于大部分JVM堆空间都分配给了年老代,因此其垃圾回收算法需要更节省空间,此算法需要能够处理低垃圾密度的堆空间 |
持久代:
存放VM和Java类的元数据(metadata),以及interned字符串和类的静态变量 |
垃圾回收方式:
次收集:年轻带空间满后,触发,将还存活的对象移至老年代;
算法:Mark_copy,使用引用计数器进行标记,轮番使用suivivor区域,每次次收集后年纪+1;
全收集:老年代空间满后触发;持久代空间满后触发;System.gc()触发;
算法:
Serial GC Parallel GC Parallel Old GC(Parallel Compacting GC) Concurrent Mark & Sweep GC (or “CMS”) Garbage First (G1) GC |
2.TCP为啥是可靠的
TCP在传输过程中,通信双方按照协议进行通信;将数据截断为合理的长度;超时重发;首部校验,若发现有改动则抛弃该包;通过可变大小的窗口协议进行流量控制;
针对乱序:在三次握手时,序列号被初始化,传输过程中将继续使用这个序列号,每传送一个包,序列号进行加1;TCP也会进行重新排序;
针对丢包:收到一个数据包过后,会用ACK应答码进行确认,发送方可以针对未确认的包进行重传;
针对重复:若已收到过该序列号的包,则丢弃;
3.TCP拥塞控制机制
可变大小的窗口协议:加法增大,乘法减小;
增大算法先采用慢启动算法,再才采用拥塞避免算法;
4.编程题,给定一棵二叉树,按层次打印出来,要求有换行;
如果是二叉树的层次遍历使用队列实现即可,入队,出队时若有左右子树则入队;
要求有换行的情况下需要增加两个指针,last指针指向每行结尾的节点,初始化为根节点,nlast指针指向最后入队的节点,初始化为null;当当前出队列的元素 = last时,则遇到一行结尾,打印换行符,并更新last 为 nlast定位到下一行结束节点(已经走到last表示已走完上一层,而此时队列尾的元素即为下一层的结尾节点)
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
public class Solution {
ArrayList > Print(TreeNode pRoot) {
ArrayList > al=new ArrayList > ();
ArrayList li=new ArrayList();
LinkedList theQueue=new LinkedList<>();
TreeNode last=pRoot;
TreeNode nlast=null;
if(pRoot==null)
return al;
theQueue.add(pRoot);
while(!theQueue.isEmpty())
{
TreeNode node=theQueue.remove();
if(node.left!=null)
{
theQueue.add(node.left);
nlast=node.left;
}
if(node.right!=null)
{
theQueue.add(node.right);
nlast=node.right;
}
li.add(node.val);
if(node.equals(last))
{
al.add(new ArrayList(li));
li.clear();
last=nlast;
}
}
return al;
}
}
5.进程和线程的区别
(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
(3)进程的创建调用fork或者vfork,而线程的创建调用pthread_create,进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束
(4)线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
(5)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
(6)线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志
6.进程间通信
1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
6. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
7. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
8. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
线程间通信:同一个进程间的可以直接进行通信,线程间的通信多用于线程的同步,所以线程没有像进程通信中用于数据交换的方式。
# 锁机制:包括互斥锁、条件变量、读写锁
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理