多線程和多進程的應用場景
多線程模型適用于I/O密集型場景,因為I/O密集型場景因為I/O阻塞導致頻繁切換,線程只占用棧,程序計數器,一組寄存器等少量資源,切換效率高,單機多核分布式。
多進程模型適用于需要頻繁的計算場景,多機分布式。
網絡的七層協議和五層協議;各層都分別有哪些協議?
TCP/IP:?
數據鏈路層:ARP,RARP?
網絡層: IP,ICMP,IGMP?
傳輸層:TCP ,UDP,UGP?
應用層:Telnet,FTP,SMTP,SNMP.?
OSI:?
物理層:EIA/TIA-232, EIA/TIA-499, V.35, V.24, RJ45, Ethernet, 802.3, 802.5, FDDI, NRZI, NRZ, B8ZS
數據鏈路層:Frame Relay, HDLC, PPP, IEEE 802.3/802.2, FDDI, ATM, IEEE 802.5/802.2?
網絡層:IP,IPX,AppleTalk DDP?
傳輸層:TCP,UDP,SPX?
會話層:RPC,SQL,NFS,NetBIOS,names,AppleTalk,ASP,DECnet,SCP?
表示層:TIFF,GIF,JPEG,PICT,ASCII,EBCDIC,encryption,MPEG,MIDI,HTML?
應用層:FTP,WWW,Telnet,NFS,SMTP,Gateway,SNMP
http長鏈接和短連接的區別?
短連接?
連接->傳輸數據->關閉連接
HTTP是無狀態的,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。
也可以這樣說:短連接是指SOCKET連接后發送后接收完數據后馬上斷開連接。
長連接?
連接->傳輸數據->保持連接 -> 傳輸數據-> 。。。 ->關閉連接。
長連接指建立SOCKET連接后不管是否使用都保持連接,但安全性較差。
http的長連接?
HTTP也可以建立長連接的,使用Connection:keep-alive,HTTP 1.1默認進行持久連接。HTTP1.1和HTTP1.0相比較而言,最大的區別就是增加了持久連接支持(貌似最新的 http1.0 可以顯示的指定 keep-alive),但還是無狀態的,或者說是不可以信任的。
什么時候用長連接,短連接??
長連接多用于操作頻繁,點對點的通訊,而且連接數不能太多情況。每個TCP連接都需要三步握手,這需要時間,如果每個操作都是先連接,再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,次處理時直接發送數據包就OK了,不用建立TCP連接。例如:數據庫的連接用長連接, 如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創建也是對資源的浪費。
而像WEB網站的http服務一般都用短鏈接,因為長連接對于服務端來說會耗費一定的資源,而像WEB網站這么頻繁的成千上萬甚至上億客戶端的連接用短連接會更省一些資源,如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。所以并發量大,但每個用戶無需頻繁操作情況下需用短連好。
總之,長連接和短連接的選擇要視情況而定。?
TCP協議在每次建立連接時,都要在收發雙方之間交換?(? ?三個? )?報文
TCP建立連接的過程會交換哪些信息(起始的序列號、窗口、各種選項)???
string簡單實現
#include <iostream>
#include <string>
using namespace std;
class String
{
public:
? ? String(const char* str = NULL);//通用構造函數,String("abc")
? ? String(const String &str);//拷貝構造
? ? ~String();
? ? String& operator=(const String &str);//賦值運算符。返回引用
? ? String operator+(const String &str) const;
? ? String& operator+=(const String &str);//+=操作符。返回引用
? ? char& operator[](int n) const;//下標操作符。返回引用
? ? bool operator==(const String &str) const;
? ? int size() const;//字符串實際大小,不包括結束符
? ? const char *c_str() const;//將string轉為char *
private:
? ? char *data;
? ? int length;
};
String::String(const char* str)//通用構造
{
? ? if (!str)
? ? {//為空。String a()
? ? ? ? length = 0;
? ? ? ? data = new char[1];
? ? ? ? *data = '\0';
? ? }
? ? else
? ? {
? ? ? ? length = strlen(str);
? ? ? ? data = new char[length + 1];
? ? ? ? strcpy(data, str);//會拷貝源的結束符
? ? }
}
String::String(const String &str)//拷貝構造,深拷貝
{
? ? length = str.size();
? ? data = new char[length + 1];
? ? strcpy(data, str.c_str());
}
String::~String()
{
? ? delete[] data;
? ? length = 0;
}
String& String::operator=(const String &str)//賦值操作符4步
{
? ? if (this == &str) return *this;//1 自我賦值,返回自身引用
? ? delete[] data;//2 刪除原有數據
? ? length = str.size();//3 深拷貝
? ? data = new char[length + 1];
? ? strcpy(data, str.c_str());
? ? return *this;//4 返回自身引用
}
String String::operator+(const String &str) const//+操作符3步
{//新建對象包括新空間,拷貝兩個數據,返回新空間
? ? String newString;
? ? newString.length = length + str.size();
? ? newString.data = new char[newString.length + 1];
? ? strcpy(newString.data, data);
? ? strcat(newString.data, str.data);
? ? return newString;
}
String& String::operator+=(const String &str)//+=操作符5步
{//重分配新空間,拷貝兩個數據,刪除自己原空間,賦值為新空間,返回引用
? ? length += str.size();//成員length是實際長度
? ? char *newdata = new char[length + 1];
? ? strcpy(newdata, data);
? ? strcat(newdata, str.c_str());
? ? delete[] data;
? ? data = newdata;
? ? return *this;
}
char& String::operator[](int n) const
{//下標操作符,返回引用
? ? if (n >= length) return data[length - 1];//如果越界,返回最后一個字符
? ? else return data[n];
}
bool String::operator==(const String &str) const
{
? ? if (length != str.size()) return false;
? ? return strcmp(data, str.c_str()) ? false : true;
}
int String::size() const
{
? ? return length;
}
const char *String::c_str() const
{
? ? return data;
}
int main()
{
? ? char a[] = "Hello", b[] = "World!";
? ? String s1(a), s2(b);
? ? cout << s1.c_str() << endl;
? ? cout << s2.c_str() << endl;
? ? s1 += s2;
? ? cout << s1.c_str() << endl;
? ? s1 = s2;
? ? cout << s1.c_str() << endl;
? ? cout << (s1 + s2).c_str() << endl;
? ? cout << s1.size() << endl;
? ? cout << s1[1] << endl;
? ? if (s1 == s2)
? ? ? ? cout << "相等" << endl;
}
拷貝構造函數為什么是常引用
一、為何要用引用:
例如,在執行bbb.myTestFunc(aaa);時,其實會調用拷貝構造函數。如果我們的拷貝構造函數的參數不是引用,那么在bbb.myTestFunc(aaa);時,調用CExample ex = aaa;,又因為ex之前沒有被創建,所以又需要調用拷貝構造函數,故而又執行CExample ex = aaa;,就這樣永遠的遞歸調用下去了。
所以, 拷貝構造函數是必須要帶引用類型的參數的, 而且這也是編譯器強制性要求的。
二、為何要要用const
如果在函數中不會改變引用類型參數的值,加不加const的效果是一樣的。而且不加const,編譯器也不會報錯。但是為了整個程序的安全,還是加上const,防止對引用類型參數值的意外修改。
孤兒進程可以理解為一個子進程的父進程英年早逝(父進程先于子進程退出),就將這樣的一個進程稱為孤兒進程。
僵尸進程:
(1)父進程成功創建子進程,且子進程先于父進程退出。?
(2)子進程需要父進程回收其所占資源,釋放pcb(進程控制塊)。但是父進程不作為,不去釋放已經退出子進程的pcb。?
(3)這樣的子進程變為僵尸進程。?
(4)僵尸進程是一個已經死掉了的進程。
重載和重寫的區別:
(1)范圍區別:重寫和被重寫的函數在不同的類中,重載和被重載的函數在同一類中。
(2)參數區別:重寫與被重寫的函數參數列表一定相同,重載和被重載的函數參數列表一定不同。
(3)virtual的區別:重寫的基類必須要有virtual修飾,重載函數和被重載函數可以被virtual修飾,也可以沒有。
隱藏和重寫,重載的區別:
(1)與重載范圍不同:隱藏函數和被隱藏函數在不同類中。
(2)參數的區別:隱藏函數和被隱藏函數參數列表可以相同,也可以不同,但函數名一定同;當參數不同時,無論基類中的函數是否被virtual修飾,基類函數都是被隱藏,而不是被重寫。?
循環引用
https://blog.csdn.net/sai_j/article/details/82908241
拷貝構造函數起作用的三種情況:
1.當用類的對象去初始化同類的另一個對象時。
????Date d2(d1);
????Date d2 = d1;? //初始化語句,并非賦值語句。
2.當函數的形參是類的對象,調用函數進行形參和實參結合時。
????void Func(A a1)? //形參是類Date的對象a1
????{? }
????int main( )
????{
????? A a
????? Func(a2); //調用Func時,實參a2是類Date的對象,將調用拷貝構造函數,初始化形參a1.
????? return 0;
????}
3.當函數的返回值是對象,函數執行完成返回調用者時。
????A Func1()
????{
????? ? A a1(4);
????? ? return a1;? //函數的返回值是對象
????}
????int main( )
????{
????? ? A a2;
????? ? a2 = Func1();? //函數執行完成,返回調用者時,調用拷貝構造函數
????? ? return 0;
????}
????在函數Func1( )內,執行語句“return a1;”時,將會調用拷貝構造函數將a1的值復制到一個匿名對象中,
????這個匿名對象是編譯系統在主程序中臨時創建的。函數執行結束時對象a1消失,但臨時對象會存在于語句
????“a2 = Func( )”中。執行完這個語句后,臨時對象的使命也就完成了,該臨時對象便自動消失了。
異步進程通信方式---信號
同步,是所有的操作都做完,才返回給用戶結果。即寫完數據庫之后,在相應用戶,用戶體驗不好。
異步,不用等所有操作等做完,就相應用戶請求。即先相應用戶請求,然后慢慢去寫數據庫,用戶體驗較好。
TCP 滑動窗口(發送窗口和接收窗口)
TCP的滑動窗口主要有兩個作用,一是提供TCP的可靠性,二是提供TCP的流控特性。同時滑動窗口機制還體現了TCP面向字節流的設計思路。
發送窗口與接收窗口關系
TCP是雙工的協議,會話的雙方都可以同時接收、發送數據。TCP會話的雙方都各自維護一個“發送窗口”和一個“接收窗口”。其中各自的“接收窗口”大小取決于應用、系統、硬件的限制(TCP傳輸速率不能大于應用的數據處理速率)。各自的“發送窗口”則要求取決于對端通告的“接收窗口”,要求相同。
數據庫索引為什么要用B+樹?
B/B+樹是為了磁盤或其它存儲設備而設計的一種平衡多路查找樹(相對于二叉,B樹每個內節點有多個分支),與紅黑樹相比,在相同的的節點的情況下,一顆B/B+樹的高度遠遠小于紅黑樹的高度(在下面B/B+樹的性能分析中會提到)。B/B+樹上操作的時間通常由存取磁盤的時間和CPU計算時間這兩部分構成,而CPU的速度非???,所以B樹的操作效率取決于訪問磁盤的次數,關鍵字總數相同的情況下B樹的高度越小,磁盤I/O所花的時間越少。
名稱 關鍵字 ????用法
增加 insert ????insert into user(name,age,sex) values(值1,值2,值3);
刪除 delete ????delete from user where 條件;
修改 update ????update user set 字段1=值1,字段2=值2 where 條件;
查詢 select ????select * from user;
去重 distinct???? select distinct 去重字段 from user;
在···之間 between ????select * from user where age between 20 and 30; (查詢年齡在20-30之間的用戶)
模糊匹配 like ????select * from user where name like ‘張_%’; (其中_匹配 一個字符,%匹配 一個或多個)
分頁查詢 LIMIT ????SELECT * FROM user LIMIT 5; (查詢前 5 個記錄行)
記錄條數 count ????select COUNT(*) from user; (查詢user表所有記錄條數)
求和 sum ????select sum(age) from user;(查詢所有的年齡和)
最大最小值 max、min ????select max(age) from user;(最大的年齡最小同理)
平均值 avg ????select avg(age) from user;(所有人年齡的平均值)
排序 order by ????select * from user order by age;(默認從小到大的正序, asc 正序,desc倒序)
分組 group by???? select sex,count(*) from user group by sex;(分組查詢男女總人數)
分組后篩選 having 其實與where用法相似,having后能用聚合函數where不行,分組篩選后建議用having關鍵字
在設計數據庫時需要注意哪些?
1.在針對表結構設計時如果是n對n的關系,盡可能的設計成1對N的關系。避免表關聯太復雜,以便于提高查詢效率。
2.首先在定義字段名稱是盡可能以簡單字符串來完成,建議是能讀懂字段所存儲內容的大概意思,同時字段名稱的長度在14個字符以內。
3.明確表字段是否允許為空,不建議表字段都為可為空,因為當為null時就會影響到查詢效率。
4.在設置字段類型是時需要考慮字段應該存放那些值,有效的節省空間大?。?/p>
????????只存在兩個是否兩個值 使用 bit類型;
????????數字值 建議使用 int,大數據使用bigint,極小數據使用tinyint;
????????如果是跟錢打交道字段,或者精度維度的地理位置,或者是有小數的,使用decimal;
????????時間值得使用datetime類型
????????如果是在有中文的字段 建議使用nvarchar,nchar
????????如果只是字符 使用 varchar,char
????????如果是大文本可使用text,ntext(Unicode碼)
5.每張表建聚集索引
6.針對查詢功能適當對表建立非聚集索引,但不建議建太多索引,索引太多會影響插入效率。
Linux對字符串常用操作命令
以空格分割字符串?
awk ‘{print $1}’
以特定字符分割字符串?
str=${str//,/ } ——————–//后面是分割字符串的標志符號,最后一個/后面還有一個空格
剪切字符串?
cut -b|-c|-f 3 ———————–b代表字節,-c代表字符,-f代表域 后面的數組是第幾個字符
去掉字符串中的特定字符?
sed ‘s/\”//g’ s代表替換,默認字符被替換為空,\后面的字符是要被替換的字符,g表示全部替換
查找近十天更改過的文件
find ./ -name * -mtime -10
1.Linux查看當前操作系統版本信息? cat /proc/version
2.Linux查看版本當前操作系統內核信息 uname -a
3.linux查看版本當前操作系統發行信息 cat /etc/issue 或 cat /etc/centos-release
4.Linux查看cpu相關信息,包括型號、主頻、內核信息等 cat /etc/cpuinfo
1.查看系統版本信息的命令 lsb_release -a
2.查看centos版本號 cat /etc/issue
3.使用 file /bin/ls
后臺運行進程的命令?
1、nohup
2、setsid
3、&
vector的實現技術,關鍵在于其對大小的控制以及重新配置時的數據移動效率,一旦vector的舊有空間滿載,如果客戶端每新增一個元素,vector擴充空間,都是配置新空間,vector動態增加大小時,并不是在原空間之后持續新空間,而是以原大小的兩倍另外配置一塊較大的空間,然后將內容拷貝過來,然后才開始在原內容之后構造新元素,并釋放原空間,因此,一旦引起空間重新配置,指向原vector的所有迭代器都失效了,這是程序員易犯的一個錯誤,務必小心。? ?并且vector維護的是一個連續線性空間,所以支持vector隨機存取。
?Map是關聯容器,以鍵值對的形式進行存儲,方便進行查找,關鍵詞起到索引的作用,值則表示與索引相關聯的數據,以紅黑樹的結構實現,插入刪除等操作都可以在O(log n)時間內完成。
?Set是關聯容器,set中每個元素都只包含一個關鍵字,set支持高效的關鍵字查詢操作---檢查每一個給定的關鍵字是否在set中,set是以紅黑樹的平衡二叉檢索樹結構實現的,支持高效插入刪除,插入元素的時候會自動調整二叉樹的結構,使得每個子樹根節點鍵值大于左子樹所有節點的鍵值,小于右子樹所有節點的鍵值,另外還得保證左子樹和右子樹的高度相等。
程序優化方法:
1.消除循環的低效率
2.減少過程調用
3.消除不必要的內存引用
4.循環展開
5.提高并行性
野指針是指存在一個指針變量,但是這個指針變量指向的內存空間已經被釋放,這時候指針的值還是不為空?;蛭瓷暾堅L問受限內存區域的指針。
"野指針"的成因主要有兩種:
? ?? ???(1)指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NULL,要么讓它指向合法的內存。例如
? ?? ?? ?? ?? ? char *p = NULL;
? ?? ?? ?? ?? ? char *str = (char *) malloc(100);
? ?? ???(2)指針p被free或者delete之后,沒有置為NULL,讓人誤以為p是個合法的指針。
空指針的簡單描述:
它 “與任何對象或函數的指針值都不相等”。也就是說, 取地址操作符 & 永遠也不能得到空指針, 同樣對 malloc() 的成功調用也不會返回空指針, 如果失敗, malloc() 的確返回空指針, 這是空指針的典型用法:表示 “未分配”或者 “尚未指向任何地方”的指針。
空指針和未初始化的指針:
空指針在概念上不同于未初始化的指針??罩羔樋梢源_保不指向任何對象或函數; 而未初始化指針則可能指向任何地方。
野指針是如何產生的
野指針的產生有以下3種情況
1、定義一個指針變量時沒有初始化
int *p;
2、動態開辟的內存空間在使用完后調用free函數釋放掉這段內存空間,
//卻沒有將對應的指針職位NULL。雖然開辟的空間被釋放掉但指針依舊存在。
int func()
{
? ? int *p = malloc(sizeof(int));
? ? free(p);//沒有將p值為NULL的操作
}
3、對指針的操作已經超出了指針變量的作用域
比如通常我們實現了一個函數,該函數里創建了一個指針變量,而函數結束時最終返回這個指針變量,但是函數調用結束后,該函數的函數棧幀就會被銷毀,所以返回的這個指針變量所指向的空間已經被釋放了,因此這個指針變量指向的空間就變成了隨機的。
使用野指針會產生的后果
我們知道野指針是指向一個不可知地址的指針,這里分為3種情況:
1、指向不可訪問的地址
危害:觸發段錯誤。
2、指向一個可用的,但是沒有明確意義的空間
危害:程序可以正確運行,但通常這種情況下,我們就會認為我們的程序是正確的沒有問題的,然而事實上就是有問題存在,所以這樣就掩蓋了我們程序上的錯誤。
3、指向一個可用的,而且正在被使用的空間
危害:如果我們對這樣一個指針進行解引用,對其所指向的空間內容進行了修改,但是實際上這塊空間正在被使用,那么這個時候變量的內容突然被改變,當然就會對程序的運行產生影響,因為我們所使用的變量已經不是我們所想要使用的那個值了。通常這樣的程序都會崩潰,或者數據被損壞。
如何避免?
1、定義一個指針變量時一定記得初始化
2、動態開辟的內存空間使用完free之后一定將對應的指針置為NULL
3、不要在函數中返回??臻g的指針和引用
4、注意在使用時對指針的合法性的判斷
內存泄露就是系統回收不了那些分配出去但是又不使用的內存, 隨著程序的運行,可以使用的內存就會越來越少,機子就會越來越卡,直到內存數據溢出,然后程序就會掛掉,再跟著操作系統也可能無響應. 接著你就按重啟了。
線程通信方式:
互斥、事件、臨界區、消息隊列
線程安全?
一般說來,確保線程安全的方法有這幾個:競爭與原子操作、同步與鎖、可重入、防過度優化。
競爭與原子操作?
多個線程同時訪問和修改一個數據,可能造成很嚴重的后果。出現嚴重后果的原因是很多操作被操作系統編譯為匯編代碼之后不止一條指令,因此在執行的時候可能執行了一半就被調度系統打斷了而去執行別的代碼了。一般將單指令的操作稱為原子的(Atomic),因為不管怎樣,單條指令的執行是不會被打斷的。
因此,為了避免出現多線程操作數據的出現異常,Linux系統提供了一些常用操作的原子指令,確保了線程的安全。但是,它們只適用于比較簡單的場合,在復雜的情況下就要選用其他的方法了。
同步與鎖?
為了避免多個線程同時讀寫一個數據而產生不可預料的后果,開發人員要將各個線程對同一個數據的訪問同步,也就是說,在一個線程訪問數據未結束的時候,其他線程不得對同一個數據進行訪問。
同步的最常用的方法是使用鎖(Lock),它是一種非強制機制,每個線程在訪問數據或資源之前首先試圖獲取鎖,并在訪問結束之后釋放鎖;在鎖已經被占用的時候試圖獲取鎖時,線程會等待,直到鎖重新可用。
二元信號量是最簡單的一種鎖,它只有兩種狀態:占用與非占用,它適合只能被唯一一個線程獨占訪問的資源。對于允許多個線程并發訪問的資源,要使用多元信號量(簡稱信號量)。
可重入?
一個函數被重入,表示這個函數沒有執行完成,但由于外部因素或內部因素,又一次進入該函數執行。一個函數稱為可重入的,表明該函數被重入之后不會產生任何不良后果??芍厝胧遣l安全的強力保障,一個可重入的函數可以在多線程環境下放心使用。
防過度優化?
在很多情況下,即使我們合理地使用了鎖,也不一定能夠保證線程安全,因此,我們可能對代碼進行過度的優化以確保線程安全。
我們可以使用volatile關鍵字試圖阻止過度優化,它可以做兩件事:第一,阻止編譯器為了提高速度將一個變量緩存到寄存器而不寫回;第二,阻止編譯器調整操作volatile變量的指令順序。
手寫strcpy
char * strcpy(char *dst,const char *src) //[1]
{
? ? assert(dst != NULL && src != NULL);? ? //[2]
? ? char *ret = dst;? //[3]
? ? while ((*dst++=*src++)!='\0'); //[4]
? ? return ret;
}
注意問題:
[1]const修飾
源字符串參數用const修飾,防止修改源字符串。
[2]空指針檢查
(A)不檢查指針的有效性,說明答題者不注重代碼的健壯性。
(B)檢查指針的有效性時使用assert(!dst && !src);
char *轉換為bool即是類型隱式轉換,這種功能雖然靈活,但更多的是導致出錯概率增大和維護成本升高。
(C)檢查指針的有效性時使用assert(dst != 0 && src != 0);
直接使用常量(如本例中的0)會減少程序的可維護性。而使用NULL代替0,如果出現拼寫錯誤,編譯器就會檢查出來。
[3]返回目標地址
(A)忘記保存原始的strdst值。
[4]'\0'
(A)循環寫成while (*dst++=*src++);明顯是錯誤的。
(B)循環寫成while (*src!='\0') *dst++=*src++;
循環體結束后,dst字符串的末尾沒有正確地加上'\0'。
C語言文件操作API
FILE這個結構包含了文件操作的基本屬性,對文件的操作都要通過這個結構的指針來進行,此種文件操作常用的函數見下表 函數 功能
fopen() 打開流
fclose() 關閉流
fputc() 寫一個字符到流中
fgetc() 從流中讀一個字符
fseek() 在流中定位到指定的字符
fputs() 寫字符串到流
fgets() 從流中讀一行或指定個字符
fprintf() 按格式輸出到流
fscanf() 從流中按格式讀取
feof() 到達文件尾時返回真值
ferror() 發生錯誤時返回其值
rewind() 復位文件定位器到文件開始處
remove() 刪除文件
fread() 從流中讀指定個數的字符
fwrite() 向流中寫指定個數的字符
tmpfile() 生成一個臨時文件流
tmpnam() 生成一個唯一的文件名
二、直接I/O文件操作
函數 說明
open() 打開一個文件并返回它的句柄
close() 關閉一個句柄
lseek() 定位到文件的指定位置
read() 塊讀文件
write() 塊寫文件
eof() 測試文件是否結束
filelength() 取得文件長度
rename() 重命名文件
chsize() 改變文件長度
https://blog.csdn.net/tantion/article/details/85927721
兩個結點的最近公共祖先
/**
* Definition for a binary tree node.
* struct TreeNode {
*? ? int val;
*? ? TreeNode *left;
*? ? TreeNode *right;
*? ? TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
? ? TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
? ? ? ? if(p == NULL || q == NULL || root == NULL)
? ? ? ? ? ? return NULL;
? ? ? ? if(p->val < root->val && q->val < root->val)
? ? ? ? ? ? return lowestCommonAncestor(root->left, p, q);
? ? ? ? if(p->val > root->val && q->val > root->val)
? ? ? ? ? ? return lowestCommonAncestor(root->right, p, q);
? ? ? ? return root;
? ? }
};
二叉樹距離最遠的兩個節點?
//后序 求 每個節點最遠距離, 倒著遍歷, 只遍歷了一遍 O( N )
//如果從上往下遍歷 則 O( N*N )
int GetMaxPathLen( Node* root, int& maxLen ) //maxLen初始值傳0
{
//別用 全局變量(存在線程安全問題)
if ( NULL == root )
return 0;
int left = GetMaxPathLen( root->_left, maxLen );
int right = GetMaxPathLen( root->_right, maxLen );
if ( left + right > maxLen )
maxLen = left + right;
//返回高度
return (left>right? left:right) + 1;
}
//運行結束后的 maxLen值 就是樹中最遠的兩個節點的距離
紅黑樹和B樹應用場景有何不同?
2者都是有序數據結構,可用作數據容器。紅黑樹多用在內部排序,即全放在內存中的,微軟STL的map和set的內部實現就是紅黑樹。B樹多用在內存里放不下,大部分數據存儲在外存上時。因為B樹層數少,因此可以確保每次操作,讀取磁盤的次數盡可能的少。
在數據較小,可以完全放到內存中時,紅黑樹的時間復雜度比B樹低。反之,數據量較大,外存中占主要部分時,B樹因其讀磁盤次數少,而具有更快的速度。
B樹相對于紅黑樹的區別
在大規模數據存儲的時候,紅黑樹往往出現由于樹的深度過大而造成磁盤IO讀寫過于頻繁,進而導致效率低下的情況。為什么會出現這樣的情況,我們知道要獲取磁盤上數據,必須先通過磁盤移動臂移動到數據所在的柱面,然后找到指定盤面,接著旋轉盤面找到數據所在的磁道,最后對數據進行讀寫。磁盤IO代價主要花費在查找所需的柱面上,樹的深度過大會造成磁盤IO頻繁讀寫。根據磁盤查找存取的次數往往由樹的高度所決定,所以,只要我們通過某種較好的樹結構減少樹的結構盡量減少樹的高度,B樹可以有多個子女,從幾十到上千,可以降低樹的高度。
B樹和B+樹的區別
B樹:所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字信息。
B+樹:所有的葉子結點中包含了全部關鍵字的信息,及指向含有這些關鍵字記錄的指針,且葉子結點本身依關鍵字的大小自小而大的順序鏈接,所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最?。╆P鍵字。?(而B 樹的非終節點也包含需要查找的有效信息)
為什么說B+比B樹更適合實際應用中操作系統的文件索引和數據庫索引?
1)?B+的磁盤讀寫代價更低
B+的內部結點并沒有指向關鍵字具體信息的指針。因此其內部結點相對B樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說IO讀寫次數也就降低了。
2)?B±tree的查詢效率更加穩定
由于非終結點并不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。
數據庫索引采用B+樹的主要原因是 B樹在提高了磁盤IO性能的同時并沒有解決元素遍歷的效率低下的問題。正是為了解決這個問題,B+樹應運而生。B+樹只要遍歷葉子節點就可以實現整棵樹的遍歷。而且在數據庫中基于范圍的查詢是非常頻繁的,而B樹不支持這樣的操作(或者說效率太低)
.內核態與用戶態的區別
內核態與用戶態是操作系統的兩種運行級別,當程序運行在3級特權級上時,就可以稱之為運行在用戶態。因為這是最低特權級,是普通的用戶進程運行的特權級,大部分用戶直接面對的程序都是運行在用戶態;
當程序運行在0級特權級上時,就可以稱之為運行在內核態。
運行在用戶態下的程序不能直接訪問操作系統內核數據結構和程序。當我們在系統中執行一個程序時,大部分時間是運行在用戶態下的,在其需要操作系統幫助完成某些它沒有權力和能力完成的工作時就會切換到內核態(比如操作硬件)。
這兩種狀態的主要差別是
處于用戶態執行時,進程所能訪問的內存空間和對象受到限制,其所處于占有的處理器是可被搶占的
處于內核態執行時,則能訪問所有的內存空間和對象,且所占有的處理器是不允許被搶占的。
Linux系統是依靠軟中斷來實現用戶態到內核態的切換的。具體過程可分為以下幾步:
1、用戶程序執行到調用系統調用處引發一個異常,這個異常就是觸發int&0x80指令
2、int&0x80會導致系統切換到內核態,并執行第128號異常(中斷)處理程序
3、而第128號異常處理程序就是用來執行系統調用的。
字符串反轉單詞
string reverseWords(string s) {
? ? ? ? int front = 0;
? ? ? ? for(int i = 0; i <= s.length(); ++i){
? ? ? ? ? ? if(i == s.length() || s[i] == ' '){
? ? ? ? ? ? ? ? reverse(&s[front], &s[i]);
? ? ? ? ? ? ? ? front = i + 1;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return s;
? ? }
class Solution {
public:
? ? int maxProfit(vector<int>& prices) {
? ? ? ? int buyprice = INT_MAX;
? ? ? ? int benifit = 0;
? ? ? ? for (int i = 0; i < prices.size();i++){
? ? ? ? ? ? if (prices[i] < buyprice ){
? ? ? ? ? ? ? ? buyprice = prices[i];
? ? ? ? ? ? }
? ? ? ? ? ? else if (prices[i]-buyprice > benifit){
? ? ? ? ? ? ? ? benifit = prices[i]-buyprice;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return benifit;
? ? }
};
海量字符流判重問題(bitmap,布隆過濾器)
功能:把格式化的數據寫入某個字符串緩沖區。
頭文件:stdio.h
原型:int sprintf( char *buffer, const char *format, [ argument] … );
參數列表:buffer:char型指針,指向將要寫入的字符串的緩沖區;format:格式化字符串;[argument]...:可選參數,可以是任何類型的數據。
返回值: 返回成功寫入buffer 的字符數,出錯則返回-1. 如果 buffer 或 format 是空指針,且不出錯而繼續,函數將返回-1,并且 errno 會被設置為 EINVAL。sprintf 返回被寫入buffer 的字節數,結束字符‘\0’不計入內。即,如果“Hello”被寫入空間足夠大的buffer后,函數sprintf 返回5。
功能:用于將格式化的數據寫入字符串
*1 如果格式化后的字符串長度 < n,則將此字符串全部復制到str中,并給其后添加一個字符串結束符('\0');
*2 如果格式化后的字符串長度 >= n,則只將其中的(n-1)個字符復制到str中,并給其后添加一個字符串結束符('\0'),返回值為欲寫入的字符串長度。(跟編譯器有關)
頭文件:stdio.h
原型:int snprintf(char *str, int n, char * format [, argument, ...]);
參數列表:str為要寫入的字符串;n為要寫入的字符的最大數目,超過n會被截斷;format為格式化字符串,與printf()函數相同;[argument]...:可選參數,可以是任何類型的數據。
返回值:成功則返回參數str 字符串長度,失敗則返回-1,錯誤原因存于errno 中。
**注意**:snprintf()并不是標C中規定的函數,在GCC中,該函數名稱就snprintf(),而在VC中稱為_snprintf()。由于不是標準函數,沒有一個統一的標準來規定該函數的行為。所以不同編譯器結果可能不同。[參考](http://c.biancheng.net/cpp/html/2417.html)
在VS2008中需在預編譯處加入
區別
snprintf比sprintf更加安全,可以控制寫入字符串長度;
snprintf會先把目標字符串清空,然后寫入;而sprintf不會。這會導致問題參考
返回值不同。snprintf成功則返回欲寫入字符串長度即格式化后字符串長度,失敗則返回負值;sprintf返回成功寫入字符串長度,出錯返回-1。所以在使用返回值時候一定要注意特別是snprintf;
一、為什么會出現內存溢出問題?導致內存溢出問題的原因有很多,比如:
(1) 使用非類型安全(non-type-safe)的語言如 C/C++ 等。
(2) 以不可靠的方式存取或者復制內存緩沖區。
(3) 編譯器設置的內存緩沖區太靠近關鍵數據結構。
下面來分析這些因素:
1. 內存溢出問題是 C 語言或者 C++ 語言所固有的缺陷,它們既不檢查數組邊界,又不檢查類型可靠性(type-safety)。眾所周知,用 C/C++ 語言開發的程序由于目標代碼非常接近機器內核,因而能夠直接訪問內存和寄存器,這種特性大大提升了 C/C++ 語言代碼的性能。只要合理編碼,C/C++ 應用程序在執行效率上必然優于其它高級語言。然而,C/C++ 語言導致內存溢出問題的可能性也要大許多。其他語言也存在內容溢出問題,但它往往不是程序員的失誤,而是應用程序的運行時環境出錯所致。
2. 當應用程序讀取用戶(也可能是惡意攻擊者)數據,試圖復制到應用程序開辟的內存緩沖區中,卻無法保證緩沖區的空間足夠時(換言之,假設代碼申請了 N 字節大小的內存緩沖區,隨后又向其中復制超過 N 字節的數據)。內存緩沖區就可能會溢出。想一想,如果你向 12 盎司的玻璃杯中倒入 16 盎司水,那么多出來的 4 盎司水怎么辦?當然會滿到玻璃杯外面了!
3. 最重要的是,C/C++ 編譯器開辟的內存緩沖區常常鄰近重要的數據結構?,F在假設某個函數的堆棧緊接在在內存緩沖區后面時,其中保存的函數返回地址就會與內存緩沖區相鄰。此時,惡意攻擊者就可以向內存緩沖區復制大量數據,從而使得內存緩沖區溢出并覆蓋原先保存于堆棧中的函數返回地址。這樣,函數的返回地址就被攻擊者換成了他指定的數值;一旦函數調用完畢,就會繼續執行“函數返回地址”處的代碼。非但如此,C++ 的某些其它數據結構,比如 v-table 、例外事件處理程序、函數指針等,也可能受到類似的攻擊。
二、解決內存溢出問題不要太悲觀,下面討論內存溢出問題的解決和預防措施。
1、改用受控代碼
2、遵守黃金規則
當你用 C/C++ 書寫代碼時,應該處處留意如何處理來自用戶的數據。如果一個函數的數據來源不可靠,又用到內存緩沖區,那么它就必須嚴格遵守下列規則:
必須知道內存緩沖區的總長度。
檢驗內存緩沖區。
提高警惕。
多態性,在c++中指具有不同功能的函數可以用同一個函數名,即可以用同一個函數名調用不同內容的函數。向不同的對象發送用一個消息,不同的對象在接收同樣的消息,會產生不同的行為(方法)。
從系統實現角度來看。多態性分為兩類:靜態多態性和動態多態性。
靜態多態性:在程序編譯時系統就能決定調用哪個函數,因此靜態函數有稱編譯時的多態性(實質上是通過函數的重載實現)。例如:函數的重載和運算符重載實現.
動態多態性:運行過程中才動態地確定操作指針所指的對象。主要通過虛函數和重寫來實現。
虛函數vptr的初始化時間
vptr都是在自身構造函數體之前初始化,vptr初始化是在初始化列表之前還是之后是跟編譯器實現有關的
TCP慢啟動
TCP在連接過程的三次握手完成后,開始傳數據,并不是一開始向網絡通道中發送大量的數據包,這樣很容易導致網絡中路由器緩存空間耗盡,從而發生擁塞;而是根據初始的cwnd大小逐步增加發送的數據量,cwnd初始化為1個最大報文段(MSS)大?。?b>這個值可配置不一定是1個MSS);每當有一個報文段被確認,cwnd大小指數增長。
TCP_NODELAY選項是用來控制是否開啟Nagle算法,該算法是為了提高較慢的廣域網傳輸效率,減小小分組的報文個數,完整描述:
該算法要求一個TCP連接上最多只能有一個未被確認的小分組,在該小分組的確認到來之前,不能發送其他小分組。
套接字上設置了TCP_NODELAY。有的包頭的包將被立即傳輸,在某些情況下(取決于內部的包計數器),因為這個包成功的被對方收到后需要請求對方確認。這樣,大量數據的傳輸就會被推遲而且產生了不必要的網絡流量交換。
map:
優點:
有序性,這是map結構最大的優點,其元素的有序性在很多應用中都會簡化很多的操作
紅黑樹,內部實現一個紅黑書使得map的很多操作在lgn的時間復雜度下就可以實現,因此效率非常的高
缺點: 空間占用率高,因為map內部實現了紅黑樹,雖然提高了運行效率,但是因為每一個節點都需要額外保存父節點、孩子節點和紅/黑性質,使得每一個節點都占用大量的空間
適用處:對于那些有順序要求的問題,用map會更高效一些
unordered_map:
優點: 因為內部實現了哈希表,因此其查找速度非常的快
缺點: 哈希表的建立比較耗費時間
適用處:對于查找問題,unordered_map會更加高效一些,因此遇到查找問題,常會考慮一下用unordered_map。
1)滑動窗口的作用:
?滑動窗口機制是TCP用來控制發送數據包速率的。
?發送方每次只能發送滑動窗口內部的數據包。
2)滑動窗口的運行方式:
?每收到一個新的確認(ack),滑動窗口的位置就向右移動一格。
?滑動窗口大小,受擁塞窗口(cwnd)和通告窗口(awnd)的制約。swnd = min [ cwnd , awnd ]。
?擁塞窗口是為了不造成阻塞,網絡對發送方發包數量的限制。
?通告窗口是接收方TCP緩存當前的大小。它阻止由于發包數量過多,超出接收方緩存的容量。
3)滑動窗口的意義:
?因特網中有數以萬計的TCP連接,它們需要共享帶寬,緩存等網絡資源。 TCP希望能最大效率的利用網絡資源,并將資源公平的分配到每條TCP連接上,還要盡量保證不讓網絡超負荷?;瑒哟翱跈C制有效的解決了這些問題。
DNS域名解析使用的是TCP協議還是UDP協議?
DNS在進行區域傳輸的時候使用TCP協議,其它時候則使用UDP協議;
DNS的規范規定了2種類型的DNS服務器,一個叫主DNS服務器,一個叫輔助DNS服務器。在一個區中主DNS服務器從自己本機的數據文件中讀取該區的DNS數據信息,而輔助DNS服務器則從區的主DNS服務器中讀取該區的DNS數據信息。當一個輔助DNS服務器啟動時,它需要與主DNS服務器通信,并加載數據信息,這就叫做區傳送(zone transfer)。
為什么既使用TCP又使用UDP?
首先了解一下TCP與UDP傳送字節的長度限制:
UDP報文的最大長度為512字節,而TCP則允許報文長度超過512字節。當DNS查詢超過512字節時,協議的TC標志出現刪除標志,這時則使用TCP發送。通常傳統的UDP報文一般不會大于512字節。
區域傳送時使用TCP,主要有一下兩點考慮:
1.輔域名服務器會定時(一般時3小時)向主域名服務器進行查詢以便了解數據是否有變動。如有變動,則會執行一次區域傳送,進行數據同步。區域傳送將使用TCP而不是UDP,因為數據同步傳送的數據量比一個請求和應答的數據量要多得多。
2.TCP是一種可靠的連接,保證了數據的準確性。
域名解析時使用UDP協議:
客戶端向DNS服務器查詢域名,一般返回的內容都不超過512字節,用UDP傳輸即可。不用經過TCP三次握手,這樣DNS服務器負載更低,響應更快。雖然從理論上說,客戶端也可以指定向DNS服務器查詢的時候使用TCP,但事實上,很多DNS服務器進行配置的時候,僅支持UDP查詢包。
LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。