part 1:自我介绍
这个过程大家自由发挥,尽量做到条理清晰(尽量控制在3min)
part2:八股文
操作系统:
请简述下虚拟内存:
(1)操作系统使用虚拟内存来进行内存管理。
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
(2)虚拟内存的优点
1.既然每个进程的内存空间都是一致而且固定的,所以链接器在链接可执行文件时,可以设定内存地址,而不用去管这些数据最终实际的内存地址,这是有独立内存空间的好处
2.当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去就可以了,节省内存
3.在程序需要分配连续的内存空间的时候,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片。
C++
如果一个结构体中又有int 类型,又有char类型,又有double类型,那么他的大小为多少?
这个知识涉及到内存对其的相关知识:
结构体变量的首地址能够被其最宽基本类型成员大小与对齐基数中的较小者所整除;
结构体每个成员相对于结构体首地址的偏移量 (offset) 都是该成员大小与对齐基数中的较小者的整数倍,如有需要编译器会在成员之间加上填充字节 (internal padding);
结构体的总大小为结构体最宽基本类型成员大小与对齐基数中的较小者的整数倍,如有需要编译器会在最末一个成员之后加上填充字节 (trailing padding)。
/*
说明:程序是在 64 位编译器下测试的
*/
#include <iostream>
using namespace std;
struct A
{
short var; // 2 字节
int var1; // 8 字节 (内存对齐原则:填充 2 个字节) 2 (short) + 2 (填充) + 4 (int)= 8
long var2; // 12 字节 8 + 4 (long) = 12
char var3; // 16 字节 (内存对齐原则:填充 3 个字节)12 + 1 (char) + 3 (填充) = 16
string s; // 48 字节 16 + 32 (string) = 48
};
int main()
{
short var;
int var1;
long var2;
char var3;
string s;
A ex1;
cout << sizeof(var) << endl; // 2 short
cout << sizeof(var1) << endl; // 4 int
cout << sizeof(var2) << endl; // 4 long
cout << sizeof(var3) << endl; // 1 char
cout << sizeof(s) << endl; // 32 string
cout << sizeof(ex1) << endl; // 48 struct
return 0;
}
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e47vys/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
进行内存对齐的原因:(主要是硬件设备方面的问题)
某些硬件设备只能存取对齐数据,存取非对齐的数据可能会引发异常;
某些硬件设备不能保证在存取非对齐数据的时候的操作是原子操作;
相比于存取对齐的数据,存取非对齐的数据需要花费更多的时间;
某些处理器虽然支持非对齐数据的访问,但会引发对齐陷阱(alignment trap);
某些硬件设备只支持简单数据指令非对齐存取,不支持复杂数据指令的非对齐存取。
内存对齐的优点:
便于在不同的平台之间进行移植,因为有些硬件平台不能够支持任意地址的数据访问,只能在某些地址处取某些特定的数据,否则会抛出异常;
提高内存的访问效率,因为 CPU 在读取内存时,是一块一块的读取。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/cpp-interview-highlights/e47vys/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
**数据库:读写锁
- 读写锁**
互斥锁(Exclusive),简写为 X 锁,又称写锁。
共享锁(Shared),简写为 S 锁,又称读锁。
有以下两个规定:
一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁
. 意向锁
使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
作者:CyC2018
链接:https://leetcode-cn.com/leetbook/read/tech-interview-cookbook/om3xch/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
计算机网络
TCP发生在哪一个层?TCP为什么需要三次握手?如果只有两次握手会发生什么?
1:传输层
2:三次握手的主要目的是确认自己和对方的发送和接收都是正常的,从而保证了双方能够进行可靠通信。若采用两次握手,当第二次握手后就建立连接的话,此时客户端知道服务器能够正常接收到自己发送的数据,而服务器并不知道客户端是否能够收到自己发送的数据。
我们知道网络往往是非理想状态的(存在丢包和延迟),当客户端发起创建连接的请求时,如果服务器直接创建了这个连接并返回包含 SYN、ACK 和 Seq 等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直接收不到返回的数据包。由于客户端可能设置了一个超时时间,一段时间后就关闭了连接建立的请求,再重新发起新的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器客户端能否收到服务器传输的数据的话,服务器端的端口就会一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。长此以往, 这样的端口越来越多,就会造成服务器开销的浪费。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/networks-interview-highlights/esrt4m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
HTTP请求报文分为哪几个部分
请求行:
①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。
②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL。
③是协议名称及版本号。
请求头:
④是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
与缓存相关的规则信息,均包含在header中
请求体:
⑤是报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数。
算法题目
根据二叉树的前序遍历以及中序遍历重建二叉树,利用递归,挺简单的
class Solution {
private:
unordered_map<int, int> index;
public:
TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return nullptr;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = index[preorder[preorder_root]];
// 先把根节点建立出来
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
// 构造哈希映射,帮助我们快速定位根节点
for (int i = 0; i < n; ++i) {
index[inorder[i]] = i;
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
大体思路:首先构建一个hash映射,帮助我们快速定位中序遍历中的根节点,index第一位存储inorder的值,第二位存储inorder的序号;
利用中序遍历确认左子树和右子树,例如上述的案例就是
左子树:前序:9,中序,9
右子树:前序:20 15 7 中序:15 20 7
各位不用担心,手撕代码不用写出全部程序,只需要像leetcode上面写出核心代码就成
写在最后:这是我第一次进行实习面试,其实有很多准备的不到位的地方,但字节的面试官确实很好,一直鼓励我让我不要紧张(之前紧张地睡不着觉简直没有必要淦),总而言之,希望各位好好加油,也希望我好好努力,争取找到一个满意的实习,淦!