好未來面試準備

目錄

 

算法題

求01矩陣最大正方形面積

歸併排序

LeetCode第一題,twosum

二面算法 一個先升序後降序的無重複元素數組,找出給定數字k的索引,不能遍歷,即實現一個 find 函數。找出字符串裏面 ,出現的次數最多的字符和其次數.

C++

虛析構函數,虛析構得調用順序。

構造函數不能聲明爲虛函數的原因

extern c

memset(this,/0,sizeof(*this)。我從有無虛函數,有無對象成員,是否pod的情況來說。不過面試官說我漏考慮一種情況。至今爲止不知道漏考慮啥了。

手寫單例模式,懶漢式就常用的,雙重判斷,安全考慮用用new的nothrow版本,再判空指針。又問不準用新特性,安全嗎?

隊列和棧的區別

如何用隊列實現棧

lambda表達式。問了值捕獲,引用捕獲。

volatile關鍵字的適用場景

讀過 STL 源碼是吧?是,讀過空間配置器和紅黑樹的?

哈希瞭解嗎?如何處理衝突問題?講了講底層實現

TIME_WAIT 相關的,主動端關閉還是被動端關閉?

如何通過多線程解決高併發問題?

操作系統

進程和線程的區別

Linux進程和線程的區別

進程間的通信方式和線程間的通信方式

操作系統裏面有哪些鎖

線程池

線程池如何管理線程的

計算機網絡

OIS七層網絡

tcp的過程

TCP 有哪些字段

爲什麼要三次握手和四次揮手?

粘包是在那一層發生的,怎麼解決(我是傳輸層,他說不是。。。然後講了好久,但是我並沒有聽懂)

Tcp四大定時器,client和server如果一方掉線會怎麼樣,掉線重連會怎麼樣,進程掛了怎麼樣,宕機了會怎麼樣?什麼時候發送rst,收不到RST的話重試多久?這地方問的很細。

軟鏈接和硬鏈接的區別,我從inode引用計數的角度來答

Linux下信號量,怎麼屏蔽,知道哪些不可屏蔽的信號。

數據庫

存儲引擎myisam和innodb的區別

數據庫有哪些鎖(我說讀寫鎖)

copyonwrite鎖在寫的時候是每個線程都複製還是怎麼樣(我說都複製,但是這麼一想和私有變量有什麼區別,面試官說寫時複製鎖其實是讀寫鎖的升級,保證了讀和寫不互斥)

手撕sql,一個表中符合條件的人成績+1。

手撕sql,一個表中男女性別交換。

sql優化

輸入一個url之後發生了什麼


算法題

01矩陣最大正方形面積

參考:https://leetcode-cn.com/problems/maximal-square/solution/leetcode221-xian-xing-dp-by-happy_yuxuan/

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if (matrix.empty() || matrix[0].empty()) return 0;
        int n = matrix.size(), m = matrix[0].size();
        vector<vector<int>> dp(n+1, vector<int>(m+1));
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (matrix[i-1][j-1] == '1') {
                    dp[i][j] = min({dp[i-1][j], dp[i-1][j-1], dp[i][j-1]}) + 1;
                    ans = max(ans, dp[i][j]);
                }
            }
        }
        return ans * ans;
    }
};

歸併排序

 

LeetCode第一題,twosum

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> m;
        for (int i = 0; i < nums.size(); ++i) {
            if (m.count(target - nums[i])) {
                return {i, m[target - nums[i]]};
            }
            m[nums[i]] = i;
        }
        return {};
    }
};

二面算法 一個先升序後降序的無重複元素數組,找出給定數字k的索引,不能遍歷,即實現一個 find 函數。找出字符串裏面 ,出現的次數最多的字符和其次數.

參考:https://blog.csdn.net/u010232305/article/details/50889714

/********************************************************
Description:求字符串中出現次數最多的字符和次數
********************************************************/
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void search(char* pData, int len)
{
	char counts[1024] = {0};    //存放原始數據作爲爲索引出現的次數
	char bufMax[1024] = {0};    //用於存放出現次數最多的字符
	int max = 0;			    //出現次數最多的字符
 
	for (int i=0; i<len; i++)
	{
		counts[pData[i]] ++;
	}
 
	for (int i=0; i<1024; i++)
	{
		if (counts[i] > max)
		{
			max = counts[i];
			bufMax[0] = i;
		}else if ((counts[i] == max) && (counts[i] !=0))
		{
			bufMax[strlen(bufMax)] = i;
		}
	}
 
	printf("出現最多的字符分別爲:");
	for (int i=0; i<strlen(bufMax); i++)
	{
		printf("%c ", bufMax[i]);
	}
	printf("\n");
	printf("出現最多的字符的次數:%d\n", max);
}
 
int main()
{
	char* srcData = "aabbbcddddeeffffghijklmnopqrst";
 
	search(srcData, strlen(srcData));
	getchar();
	return 1;
}

算法題https://blog.csdn.net/xiaxzhou/article/details/77461179?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-11&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-11

C++

虛析構函數,虛析構得調用順序。

當你用基類指針指向某個派生類對象,delete該對象時,如果基類析構函數不是虛函數,則該對象析構會異常。

爲了能夠正確的調用對象的析構函數,一般要求具有層次結構的頂級類定義其析構函數爲虛函數。因爲在delete一個抽象類指針時候,必須要通過虛函數找到真正的析構函數。如果析構函數不加virtual,delete pb只會執行Base的析構函數,而不是真正的Derived析構函數。
因爲不是virtual函數,所以調用的函數依賴於指向靜態類型,即Base

構造函數不能聲明爲虛函數的原因

1 構造一個對象的時候,必須知道對象的實際類型,而虛函數行爲是在運行期間確定實際類型的。而在構造一個對象時,由於對象還未構造成功。編譯器無法知道對象 的實際類型,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。

2 虛函數的執行依賴於虛函數表。而虛函數表在構造函數中進行初始化工作,即初始化vptr,讓他指向正確的虛函數表。而在構造對象期間,虛函數表還沒有被初 始化,將無法進行。

extern c

extern "C"的主要作用就是爲了能夠正確實現C++代碼調用其他C語言代碼。加上extern "C"後,會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。由於C++支持函數重載,因此編譯器編譯函數的過程中會將函數的參數類型也加到編譯後的代碼中,而不僅僅是函數名;而C語言並不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參數類型,一般之包括函數名。

#ifndef __INCvxWorksh  /*防止該頭文件被重複引用*/
#define __INCvxWorksh
 
#ifdef __cplusplus    //__cplusplus是cpp中自定義的一個宏
extern "C" {          //告訴編譯器,這部分代碼按C語言的格式進行編譯,而不是C++的
#endif
 
    /**** some declaration or so *****/  
 
#ifdef __cplusplus
}
#endif
 
#endif /* __INCvxWorksh */

memset(this/0sizeof(*this)。我從有無虛函數,有無對象成員,是否pod的情況來說。不過面試官說我漏考慮一種情況。至今爲止不知道漏考慮啥了。

 

手寫單例模式,懶漢式就常用的,雙重判斷,安全考慮用用newnothrow版本,再判空指針。又問不準用新特性,安全嗎?

我說在外面catch異常,那個鎖上加guard_lock。他不說話我突然想起來guard_lock也是新特性。那就手寫構造析構自己實現RAII

單例模式(Singleton Pattern,也稱爲單件模式),使用最廣泛的設計模式之一。其意圖是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點,該實例被所有程序模塊共享。

定義一個單例類:

私有化它的構造函數,以防止外界創建單例類的對象;
使用類的私有靜態指針變量指向類的唯一實例;
使用一個公有的靜態方法獲取該實例

class Singleton
{
private:
	static Singleton* instance;
private:
	Singleton() {};
	~Singleton() {};
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
public:
	static Singleton* getInstance() 
        {
		if(instance == NULL) 
			instance = new Singleton();
		return instance;
	}
};
 
// init static member
Singleton* Singleton::instance = NULL;

智能指針,循環引用,unique_ptr這些東西,然後unique_ptr的刪除器簡潔寫法?

shared_ptr、unique_ptr、weak_ptr

shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。

unique_ptr“唯一”擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用於其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命週期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷燬(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關係:在智能指針生命週期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。

weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指針的引用計數永遠不可能下降爲0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。

隊列和棧的區別

隊列(Queue):是限定只能在表的一端進行插入和另一端刪除操作的線性表
棧(Stack):是限定之能在表的一端進行插入和刪除操作的線性表

隊列和棧的規則
隊列:先進先出
棧:先進後出
隊列和棧的遍歷數據速度
隊列:基於地址指針進行遍歷,而且可以從頭部或者尾部進行遍歷,但不能同時遍歷,無需開闢空間,因爲在遍歷的過程中不影響數據結構,所以遍歷速度要快
棧:只能從頂部取數據,也就是說最先進入棧底的,需要遍歷整個棧才能取出來,遍歷數據時需要微數據開闢臨時空間,保持數據在遍歷前的一致性

如何用隊列實現棧

思路:

1、當插入時,直接插入 stack1

2、當彈出時,當 stack2 不爲空,彈出 stack2 棧頂元素,如果 stack2 爲空,將 stack1 中的全部數逐個出棧入棧 stack2,再彈出 stack2 棧頂元素

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }
 
    int pop() {
        //當彈出時,當 stack2 不爲空,彈出 stack2 棧頂元素,
        //如果 stack2 爲空,將 stack1 中的全部數逐個出棧入棧 stack2,
        //再彈出 stack2 棧頂元素
        if(stack2.empty())
        {
            while(!stack1.empty())
            {
                stack2.push(stack1.top());
                stack1.pop();
            }
        }
        int ret = stack2.top();
        stack2.pop();
        return ret;
        
    }
 
private:
    stack<int> stack1;
    stack<int> stack2;
};

lambda表達式。問了值捕獲,引用捕獲。

參考:https://docs.microsoft.com/zh-cn/cpp/cpp/lambda-expressions-in-cpp?view=vs-2019

ambda 表達式-通常稱爲lambda— 是定義匿名函數對象的簡便方法 (閉包) 坐在調用或作爲參數傳遞位置的位置到函數。 Lambda 通常用於封裝傳遞給算法或異步方法的少量代碼行。

volatile關鍵字的適用場景

一個定義爲volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:
1). 並行設備的硬件寄存器(如:狀態寄存器)
2). 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3). 多線程應用中被幾個任務共享的變量
硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。

讀過 STL 源碼是吧?是,讀過空間配置器和紅黑樹的?

答:vector,支持下標,內存連續,resize,capacity,還分析push_back

vector特點是:其容量在需要時可以自動分配,本質上是數組形式的存儲方式。即在索引可以在常數時間內完成。缺點是在插入或者刪除一項時,需要線性時間。但是在尾部插入或者刪除,是常數時間的。

list 是雙向鏈表:如果知道位置,在其中進行插入和刪除操作時,是常數時間的。索引則需要線性時間(和單鏈表一樣)。

vector 和 list 都支持在常量的時間內在容器的末尾添加或者刪除項,vector和list都支持在常量的時間內訪問表的前端的項.

vector會一次分配多個元素內存,那麼下次增加時,只是改寫內存而已,就不會再分配內存了,但是list每次只分配一個元素的內存,每次增加一個元素時,都會執行一次內存分配行爲。如果是大量數據追加,建議使用list,因爲vector 在有大量元素,並且內存已滿,再pushback元素時,需要分配大塊內存並把已經有的數據move到新分配的內存中去(vector不能加入不可複製元素)然後再釋放原來的內存塊,這些操作很耗時。而list的性能而會始終於一的,不會出現vector的性能變化情況,所以對於容器構件,需要用什麼類型最好,取決於業務邏輯。

Map也是一種關聯容器,它是 鍵—值對的集合,即它的存儲都是以一對鍵和值進行存儲的,Map通常也可以理解爲關聯數組(associative array),就是每一個值都有一個鍵與之一一對應,因此,map也是不允許重複元素出現的。

底層數據結構是紅黑樹。具體理解參考:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md

哈希瞭解嗎?如何處理衝突問題?講了講底層實現

TIME_WAIT 相關的,主動端關閉還是被動端關閉?

如何通過多線程解決高併發問題?

操作系統

進程和線程的區別

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

1) 簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.

2) 線程的劃分尺度小於進程,使得多線程程序的併發性高。

3) 另外,進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,從而極大地提高了程序的運行效率。

4) 線程在執行過程中與進程還是有區別的。每個獨立的線程有一個程序運行的入口、順序執行序列和程序的出口。但是線程不能夠獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

5) 從邏輯角度來看,多線程的意義在於一個應用程序中,有多個執行部分可以同時執行。但操作系統並沒有將多個線程看做多個獨立的應用,來實現進程的調度和管理以及資源分配。這就是進程和線程的重要區別。

Linux進程和線程的區別

參考:https://my.oschina.net/cnyinlinux/blog/422207

答:沒有區別,從內核角度來看調度的單位都一樣,都是task struct。講了一下怎麼創建task structdoforkclone的時候共享了什麼,不共享什麼。再問那爲什麼主線程沒了,其他也跟着沒了。然後講了一下linux threadNPTL線程模型,管理線程的弊端。

進程間的通信方式和線程間的通信方式

參考:https://www.jianshu.com/p/9218692cb209

線程間的同步方式有四種

進程間通信又稱IPC(Inter-Process Communication),指多個進程之間相互通信,交換信息的方法。根據進程通信時信息量大小的不同,可以將進程通信劃分爲兩大類型:
1、低級通信,控制信息的通信(主要用於進程之間的同步,互斥,終止和掛起等等控制信息的傳遞)
2、高級通信,大批數據信息的通信(主要用於進程間數據塊數據的交換和共享,常見的高級通信有管道,消息隊列,共享內存等).

操作系統裏面有哪些鎖

參考:https://martian101.github.io/2017/07/14/%E8%81%8A%E8%81%8A%E5%B8%B8%E8%A7%81%E7%9A%84%E9%94%81/

概念:是計算機對資源進行併發訪問控制的一種機制,多線程情況下來實現對臨界資源的同步互斥訪問。

操作系統層面

鎖的實現機制有自旋鎖(spinlock)和mutex兩種。

1、自旋鎖是一種非阻塞鎖,多個線程嘗試獲取自旋鎖時,沒有獲取到的線程會持續嘗試獲取直到獲取到爲止。

2、mutex是阻塞鎖,多個線程嘗試獲取鎖時,沒有獲取到鎖的線程會被操作系統調度爲阻塞狀態直到鎖被釋放然後纔會被重新喚醒。OS線程調度,線程上下文切換帶來的開銷是很大的,多線程程序如果有大量的線程切換,最壞情況下性能甚至會比單線程運行的代碼效率還要差。mutex鎖如果被頻繁的獲取和釋放,代價也可想而知。

鎖狀態

1、死鎖是已經進入等待狀態的線程相互等待各自鎖釋放的一種循環等待的狀態。

2、活鎖和死鎖產生的條件一樣,只是表現不同。死鎖下線程進入等待狀態,而活鎖下線程仍然處於運行狀態嘗試獲取鎖。活鎖下線程仍然消耗CPU,這樣看來,活鎖和死鎖的區別也有點類似spinlock和mutex。

鎖策略

1. 獨佔鎖

獨佔鎖(exclusive lock),有時候也被稱爲寫鎖,排它鎖等。被獨佔鎖保護的資源,同一時刻只能有一個線程可以進行讀寫操作。

2. 共享鎖

共享鎖(shared lock),有時候也被稱爲讀鎖。被共享鎖保護的資源,當有線程寫時仍然可以被別的線程讀取,讀線程數並不限定爲1。但是同一時刻只能有一個線程寫入。

3. 可重入鎖

可重入鎖可以並且只能被已經持有鎖的線程加鎖多次,一個線程內有多個地方形成對該鎖的嵌套獲取時可以防止死鎖發生。實現上可重入鎖會記錄自己被lock的次數,只有unlock的次數和lock次數相等時才完成對鎖的釋放。

4. 公平鎖、非公平鎖

公平鎖顧名思義,申請鎖的線程按照申請順序來獲取鎖,遵循先申請先得到的原則。而非公平鎖則沒有該順序保障。公平鎖通常使用等待隊列來實現,申請線程未獲取到鎖時則進入等待隊列,鎖被釋放時得到通知或者直接由上個擁有者移交鎖的所有權。

線程池

參考:https://www.cnblogs.com/lzpong/p/6397997.html

線程池如何管理線程的

參考:https://blog.csdn.net/xiaosong_2016/article/details/80416744

計算機網絡

OIS七層網絡

webp

每層有哪些協議

五層體系結構包括:應用層、運輸層、網絡層、數據鏈路層和物理層。 
五層協議只是OSI和TCP/IP的綜合,實際應用還是TCP/IP的四層結構。爲了方便可以把下兩層稱爲網絡接口層

tcp的過程

建立連接需要tcp三次握手

所謂的“三次握手”即對每次發送的數據量是怎樣跟蹤進行協商使數據段的發送和接收同步,根據所接收到的數據量而確定的數據確認數及數據發送、接收完畢後何時撤消聯繫,並建立虛連接。爲了提供可靠的傳送,TCP在發送新的數據之前,以特定的順序將數據包的序號,並需要這些包傳送給目標機之後的確認消息。TCP總是用來發送大批量的數據。當應用程序在收到數據後要做出確認時也要用到TCP。

在TCP/IP協議中,TCP協議提供可靠的連接服務,採用三次握手建立一個連接。
      第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
      第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
      第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
完成三次握手,客戶端與服務器開始傳送數據。

四次揮手
第一次揮手:客戶端給服務器發送TCP包,用來關閉客戶端到服務器的數據傳送。將標誌位FIN和ACK置爲1,序號爲X=1,確認序號爲Z=1。

第二次揮手:服務器收到FIN後,發回一個ACK(標誌位ACK=1),確認序號爲收到的序號加1,即X=X+1=2。序號爲收到的確認序號=Z。

第三次揮手:服務器關閉與客戶端的連接,發送一個FIN。標誌位FIN和ACK置爲1,序號爲Y=1,確認序號爲X=2。

第四次揮手:客戶端收到服務器發送的FIN之後,發回ACK確認(標誌位ACK=1),確認序號爲收到的序號加1,即Y+1=2。序號爲收到的確認序號X=2。

三次握手屬於傳輸層。

TCP 有哪些字段

1、ACK 是 TCP 報頭的控制位之一,對數據進行確認。確認由目的端發出, 用 它來告訴發送端這個序列號之前的數據段都收到了。 比如確認號爲 X,則表示 前 X-1 個數據段都收到了,只有當 ACK=1 時,確認號纔有效,當 ACK=0 時,確認 號無效,這時會要求重傳數據,保證數據的完整性。
2、SYN 同步序列號,TCP 建立連接時將這個位置 1。
3、FIN 發送端完成發送任務位,當 TCP 完成數據傳輸需要斷開時,,􏰅出斷開 連接的一方將這位置 1。
Socket 建立網絡連接的步驟:服務器監聽、客戶端請求、連接確認

爲什麼要三次握手和四次揮手?

TCP協議是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,採用全雙工通信。

那爲什麼需要三次握手呢?請看如下的過程:

A向B發起建立連接請求:A——>B;
B收到A的發送信號,並且向A發送確認信息:B——>A;
A收到B的確認信號,並向B發送確認信號:A——>B。
三次握手大概就是這麼個過程。
通過第一次握手,B知道A能夠發送數據。通過第二次握手,A知道B能發送數據。結合第一次握手和第二次握手,A知道B能接收數據。結合第三次握手,B知道A能夠接收數據。

至此,完成了握手過程,A知道B能收能發,B知道A能收能發,通信連接至此建立。三次連接是保證可靠的最小握手次數,再多次握手也不能提高通信成功的概率,反而浪費資源。

那爲什麼需要四次揮手呢?請看如下過程:

A向B發起請求,表示A沒有數據要發送了:A——>B;
B向A發送信號,確認A的斷開請求請求:B——>A;
B向A發送信號,請求斷開連接,表示B沒有數據要發送了:B——>A;
A向B發送確認信號,同意斷開:A——>B。
B收到確認信號,斷開連接,而A在一段時間內沒收到B的信號,表明B已經斷開了,於是A也斷開了連接。至此,完成揮手過程。

可能有捧油會問,爲什麼2、3次揮手不能合在一次揮手中?那是因爲此時A雖然不再發送數據了,但是還可以接收數據,B可能還有數據要發送給A,所以兩次揮手不能合併爲一次。

揮手次數比握手多一次,是因爲握手過程,通信只需要處理連接。而揮手過程,通信需要處理數據+連接。

粘包是在那一層發生的,怎麼解決(我是傳輸層,他說不是。。。然後講了好久,但是我並沒有聽懂)

參考:https://zhuanlan.zhihu.com/p/108822858

UDP 是基於報文發送的,UDP首部採用了 16bit 來指示 UDP 數據報文的長度,因此在應用層能很好的將不同的數據報文區分開,從而避免粘包和拆包的問題。而 TCP 是基於字節流的,雖然應用層和 TCP 傳輸層之間的數據交互是大小不等的數據塊,但是 TCP 並沒有把這些數據塊區分邊界,僅僅是一連串沒有結構的字節流;另外從 TCP 的幀結構也可以看出,在 TCP 的首部沒有表示數據長度的字段,基於上面兩點,在使用 TCP 傳輸數據時,纔有粘包或者拆包現象發生的可能。

什麼是粘包、拆包?

假設 Client 向 Server 連續發送了兩個數據包,用 packet1 和 packet2 來表示,那麼服務端收到的數據可以分爲三種情況,現列舉如下:

第一種情況,接收端正常收到兩個數據包,即沒有發生拆包和粘包的現象。

第二種情況,接收端只收到一個數據包,但是這一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即爲粘包。這種情況由於接收端不知道這兩個數據包的界限,所以對於接收端來說很難處理。

第三種情況,這種情況有兩種表現形式,如下圖。接收端收到了兩個數據包,但是這兩個數據包要麼是不完整的,要麼就是多出來一塊,這種情況即發生了拆包和粘包。這兩種情況如果不加特殊處理,對於接收端同樣是不好處理的。

爲什麼會發生 TCP 粘包、拆包?

要發送的數據大於 TCP 發送緩衝區剩餘空間大小,將會發生拆包。
待發送數據大於 MSS(最大報文長度),TCP 在傳輸前將進行拆包。
要發送的數據小於 TCP 發送緩衝區的大小,TCP 將多次寫入緩衝區的數據一次發送出去,將會發生粘包。
接收數據端的應用層沒有及時讀取接收緩衝區中的數據,將發生粘包。
粘包、拆包解決辦法

由於 TCP 本身是面向字節流的,無法理解上層的業務數據,所以在底層是無法保證數據包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,歸納如下:

消息定長:發送端將每個數據包封裝爲固定長度(不夠的可以通過補 0 填充),這樣接收端每次接收緩衝區中讀取固定長度的數據就自然而然的把每個數據包拆分開來。
設置消息邊界:服務端從網絡流中按消息邊界分離出消息內容。在包尾增加回車換行符進行分割,例如 FTP 協議。
將消息分爲消息頭和消息體:消息頭中包含表示消息總長度(或者消息體長度)的字段。
更復雜的應用層協議比如 Netty 中實現的一些協議都對粘包、拆包做了很好的處理。

Tcp四大定時器,clientserver如果一方掉線會怎麼樣,掉線重連會怎麼樣,進程掛了怎麼樣,宕機了會怎麼樣?什麼時候發送rst,收不到RST的話重試多久?這地方問的很細。

軟鏈接和硬鏈接的區別,我從inode引用計數的角度來答

Linux下信號量,怎麼屏蔽,知道哪些不可屏蔽的信號。

 

數據庫

存儲引擎myisaminnodb的區別

關於二者的對比與總結:

count運算上的區別:因爲MyISAM緩存有表meta-data(行數等),因此在做COUNT(*)時對於一個結構很好的查詢是不需要消耗多少資源的。而對於InnoDB來說,則沒有這種緩存。
是否支持事務和崩潰後的安全恢復: MyISAM 強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。但是InnoDB 提供事務支持事務,外部鍵等高級數據庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
是否支持外鍵: MyISAM不支持,而InnoDB支持。
MyISAM更適合讀密集的表,而InnoDB更適合寫密集的的表。 在數據庫做主從分離的情況下,經常選擇MyISAM作爲主庫的存儲引擎。 一般來說,如果需要事務支持,並且有較高的併發讀取頻率(MyISAM的表鎖的粒度太大,所以當該表寫併發量較高時,要等待的查詢就會很多了),InnoDB是不錯的選擇。如果你的數據量很大(MyISAM支持壓縮特性可以減少磁盤的空間佔用),而且不需要支持事務時,MyISAM是最好的選擇。

MyISAM 存儲引擎的特點
在 5.1 版本之前,MyISAM 是 MySQL 的默認存儲引擎,MyISAM 併發性比較差,使用的場景比較少,主要特點是

不支持事務操作,ACID 的特性也就不存在了,這一設計是爲了性能和效率考慮的。

不支持外鍵操作,如果強行增加外鍵,MySQL 不會報錯,只不過外鍵不起作用。

MyISAM 默認的鎖粒度是表級鎖,所以併發性能比較差,加鎖比較快,鎖衝突比較少,不太容易發生死鎖的情況。

MyISAM 會在磁盤上存儲三個文件,文件名和表名相同,擴展名分別是 .frm(存儲表定義)、.MYD(MYData,存儲數據)、MYI(MyIndex,存儲索引)。這裏需要特別注意的是 MyISAM 只緩存索引文件,並不緩存數據文件。

MyISAM 支持的索引類型有 全局索引(Full-Text)、B-Tree 索引、R-Tree 索引

Full-Text 索引:它的出現是爲了解決針對文本的模糊查詢效率較低的問題。

B-Tree 索引:所有的索引節點都按照平衡樹的數據結構來存儲,所有的索引數據節點都在葉節點

R-Tree索引:它的存儲方式和 B-Tree 索引有一些區別,主要設計用於存儲空間和多維數據的字段做索引,目前的 MySQL 版本僅支持 geometry 類型的字段作索引,相對於 BTREE,RTREE 的優勢在於範圍查找。

數據庫所在主機如果宕機,MyISAM 的數據文件容易損壞,而且難以恢復。

增刪改查性能方面:SELECT 性能較高,適用於查詢較多的情況

InnoDB 存儲引擎的特點
自從 MySQL 5.1 之後,默認的存儲引擎變成了 InnoDB 存儲引擎,相對於 MyISAM,InnoDB 存儲引擎有了較大的改變,它的主要特點是

支持事務操作,具有事務 ACID 隔離特性,默認的隔離級別是可重複讀(repetable-read)、通過MVCC(併發版本控制)來實現的。能夠解決髒讀和不可重複讀的問題。

InnoDB 支持外鍵操作。

InnoDB 默認的鎖粒度行級鎖,併發性能比較好,會發生死鎖的情況。

和 MyISAM 一樣的是,InnoDB 存儲引擎也有 .frm文件存儲表結構 定義,但是不同的是,InnoDB 的表數據與索引數據是存儲在一起的,都位於 B+ 數的葉子節點上,而 MyISAM 的表數據和索引數據是分開的。

InnoDB 有安全的日誌文件,這個日誌文件用於恢復因數據庫崩潰或其他情況導致的數據丟失問題,保證數據的一致性。

InnoDB 和 MyISAM 支持的索引類型相同,但具體實現因爲文件結構的不同有很大差異。

增刪改查性能方面,如果執行大量的增刪改操作,推薦使用 InnoDB 存儲引擎,它在刪除操作時是對行刪除,不會重建表。

MyISAM 和 InnoDB 存儲引擎的對比
鎖粒度方面:由於鎖粒度不同,InnoDB 比 MyISAM 支持更高的併發;InnoDB 的鎖粒度爲行鎖、MyISAM 的鎖粒度爲表鎖、行鎖需要對每一行進行加鎖,所以鎖的開銷更大,但是能解決髒讀和不可重複讀的問題,相對來說也更容易發生死鎖

可恢復性上:由於 InnoDB 是有事務日誌的,所以在產生由於數據庫崩潰等條件後,可以根據日誌文件進行恢復。而 MyISAM 則沒有事務日誌。

查詢性能上:MyISAM 要優於 InnoDB,因爲 InnoDB 在查詢過程中,是需要維護數據緩存,而且查詢過程是先定位到行所在的數據塊,然後在從數據塊中定位到要查找的行;而 MyISAM 可以直接定位到數據所在的內存地址,可以直接找到數據。

表結構文件上:MyISAM 的表結構文件包括:.frm(表結構定義),.MYI(索引),.MYD(數據);而 InnoDB 的表數據文件爲:.ibd和.frm(表結構定義);

數據庫有哪些鎖(我說讀寫鎖)

在DBMS中,可以按照鎖的粒度把數據庫鎖分爲行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )。

行級鎖:

行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少數據庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共享鎖 和 排他鎖。

特點: 開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

表級鎖:

表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MYISAM與INNODB都支持表級鎖定。表級鎖定分爲表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)。

特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度最低。

頁級鎖:

頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。

特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般
 

copyonwrite鎖在寫的時候是每個線程都複製還是怎麼樣(我說都複製,但是這麼一想和私有變量有什麼區別,面試官說寫時複製鎖其實是讀寫鎖的升級,保證了讀和寫不互斥)
 

手撕sql,一個表中符合條件的人成績+1

select  subject,max(score),name from grade GROUP BY subject

SELECT S# FORM SC WHERE C#=“C2” AND GRADE>=ALL (SELECT GRADE FORM SC WHERE C#=“C2”)

手撕sql,一個表中男女性別交換。

update salary
set sex = 
    case sex
        when 'm'
        then 'f'
        else 'm'
    end;

sql優化

參考:

https://database.51cto.com/art/200904/118526.htm

https://www.cnblogs.com/yunfeifei/p/3850440.html

查詢語句無論是使用哪種判斷條件 等於、小於、大於, WHERE 左側的條件查詢字段不要使用函數或者表達式

使用 EXPLAIN 命令優化你的 SELECT 查詢,對於複雜、效率低的 sql 語句,我們通常是使用 explain sql 來分析這條 sql 語句,這樣方便我們分析,進行優化。

當你的 SELECT 查詢語句只需要使用一條記錄時,要使用 LIMIT 1

不要直接使用 SELECT *,而應該使用具體需要查詢的表字段,因爲使用 EXPLAIN 進行分析時,SELECT * 使用的是全表掃描,也就是 type = all。

爲每一張表設置一個 ID 屬性

避免在 WHERE 字句中對字段進行 NULL 判斷

避免在 WHERE 中使用 != 或 <> 操作符

使用 BETWEEN AND 替代 IN

爲搜索字段創建索引

選擇正確的存儲引擎,InnoDB 、MyISAM 、MEMORY 等

使用 LIKE %abc% 不會走索引,而使用 LIKE abc% 會走索引

對於枚舉類型的字段(即有固定羅列值的字段),建議使用ENUM而不是VARCHAR,如性別、星期、類型、類別等

拆分大的 DELETE 或 INSERT 語句

選擇合適的字段類型,選擇標準是 儘可能小、儘可能定長、儘可能使用整數。

字段設計儘可能使用 NOT NULL

進行水平切割或者垂直分割

輸入一個url之後發生了什麼

參考:https://zhuanlan.zhihu.com/p/43369093

1.DNS域名解析:瀏覽器緩存、系統緩存、路由器、ISP的DNS服務器、根域名服務器。把域名轉化成IP地址。

2.與IP地址對應的服務器建立TCP連接,經歷三次握手:SYN,ACK、SYN,ACK

3.以get,post方式發送HTTP請求,get方式發送主機,用戶***,connection屬性,cookie等

4.獲得服務器的響應,顯示頁面
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章