2020騰訊面試題!附答案

一面:
集合有哪些:
List(ArrayList Linklist ) set(Set Treeset Hashset) map(Hashmap currentHashmap hashtable )
2020騰訊面試題!附答案mysql

arraylist和linkedlist區別
一個是基於數組的實現 一個是基於的鏈表的實現面試

hashmap怎麼擴容(多線程擴容爲何會死循環),put過程redis

出現的是鏈表的閉環。
2020騰訊面試題!附答案算法

concurrentHashMap 1.7和1.8spring

1.7是採用採用的仍是分段鎖的機制 1.8採用的是CAS機制來實現的。sql

接口和抽象類區別數據庫

JVM內存分區
2020騰訊面試題!附答案編程

新生代:
eden,survivor_from, survivor_to設計模式

垃圾回收算法:數組

三種 標記清除 複製算法 標記整理算法

PretenureSizeThreshold,maxTenuringThreshold(默認15)
若是併發的線程數量不少,而且每一個線程都是執行一個時間很短的任務就結束了,這樣頻繁建立線程就會大大下降系統的效率,由於頻繁建立線程和銷燬線程須要時間。

1線程池狀態
在ThreadPoolExecutor中定義了一個volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:

volatile int runState;

static final int RUNNING = 0;

static final int SHUTDOWN = 1;

static final int STOP = 2;

static final int TERMINATED = 3;

runState表示當前線程池的狀態,它是一個volatile變量用來保證線程之間的可見性;

下面的幾個static final變量表示runState可能的幾個取值。

當建立線程池後,初始時,線程池處於RUNNING狀態;

若是調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不可以接受新的任務,它會等待全部任務執行完畢;

若是調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,而且會去嘗試終止正在執行的任務;

當線程池處於SHUTDOWN或STOP狀態,而且全部工做線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。

2任務的執行
ThreadPoolExecutor類中其餘的一些比較重要成員變量:

rivate final BlockingQueue<Runnable> workQueue; //任務緩存隊列,用來存放等待執行的任務

private final ReentrantLock mainLock = new ReentrantLock(); //線程池的主要狀態鎖,對線程池狀態(好比線程池大小//、runState等)的改變都要使用這個鎖

private final HashSet<Worker> workers = new HashSet<Worker>(); //用來存放工做集

private volatile long keepAliveTime; //線程存貨時間

private volatile boolean allowCoreThreadTimeOut//是否容許爲核心線程設置存活時間

private volatile int corePoolSize; //核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)

private volatile int maximumPoolSize; //線程池最大能容忍的線程數

private volatile int poolSize; //線程池中當前的線程數

private volatile RejectedExecutionHandler handler; //任務拒絕策略

private volatile ThreadFactory threadFactory; //線程工廠,用來建立線程

private int largestPoolSize; //用來記錄線程池中曾經出現過的最大線程數

private long completedTaskCount; //用來記錄已經執行完畢的任務個數

1)首先,要清楚corePoolSize和maximumPoolSize的含義;

2)其次,要知道Worker是用來起到什麼做用的;

3)要知道任務提交給線程池以後的處理策略,這裏總結一下主要有4點:

若是當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會建立一個線程去執行這個任務;

若是當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(通常來講是任務緩存隊列已滿),則會嘗試建立新的線程去執行這個任務;

若是當前線程池中的線程數目達到maximumPoolSize,則會採起任務拒絕策略進行處理;

若是線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;若是容許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。

3線程池中的線程初始化
默認狀況下,建立線程池以後,線程池中是沒有線程的,須要提交任務以後纔會建立線程。

4任務緩存隊列及排隊策略
在前面咱們屢次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務

5任務拒絕策略
當線程池的任務緩存隊列已滿而且線程池中的線程數目達到maximumPoolSize,若是還有任務到來就會採起任務拒絕策略,一般有如下四種策略:

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。

ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。

ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)

ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

6線程池的關閉
ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

shutdown():不會當即終止線程池,而是要等全部任務緩存隊列中的任務都執行完後才終止,但不再會接受新的任務

shutdownNow():當即終止線程池,並嘗試打斷正在執行的任務,而且清空任務緩存隊列,返回還沒有執行的任務
7線程池容量的動態調整
ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

setCorePoolSize:設置核心池大小

setMaximumPoolSize:設置線程池最大能建立的線程數目大小

當上述參數從小變大時,ThreadPoolExecutor進行線程賦值,還可能當即建立新的線程來執行任務。
2020騰訊面試題!附答案

若是是不採用是這個那就在隊列中的線程是不可能出隊列的,就是若是是的非公平的鎖的話那就永遠不能出隊列。那可能能執行不到該線程。

JVM調優(不太會)
Xms2g:初始化推大小爲 2g;

-Xmx2g:堆最大內存爲 2g;

-XX:NewRatio=4:設置年輕的和老年代的內存比例爲 1:4;

-XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例爲 8:2;

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;

-XX:+PrintGC:開啓打印 gc 信息;

-XX:+PrintGCDetails:打印 gc 詳細信息。

如何判斷對象是否應該被回收(引用計數法,可達性分析)

root根包括哪些:對象頭

CMS回收過程,優缺點

並行收集垃圾
2020騰訊面試題!附答案

初始標記:只是標記一下 GC Roots 能直接關聯的對象,速度很快,仍然須要暫停全部的工做線程。

併發標記:進行 GC Roots 跟蹤的過程,和用戶線程一塊兒工做,不須要暫停工做線程。

從新標記:爲了修正在併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,仍然須要暫停全部的工做線程。

併發清除:清除 GC Roots 不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程。因爲耗時最長的併發標記和併發清除過程當中,垃圾收集線程能夠和用戶如今一塊兒併發工做, 因此整體上來看CMS 收集器的內存回收和用戶線程是一塊兒併發地執行:

G1回收過程
2020騰訊面試題!附答案

類加載過程(加載,驗證,準備,解析,初始化)
2020騰訊面試題!附答案

雙親委派優勢
2020騰訊面試題!附答案

=

七層模型

物理層 數據鏈路層 網絡層 傳輸層 表示層 應用層 會話層
2020騰訊面試題!附答案

四次揮手過程(中間狀態也要答)
HttpTCP 在傳輸以前會進行三次溝通,通常稱爲「三次握手」,傳完數據斷開的時候要進行四次溝通,通常稱爲「四次揮手」。(就是Http的鏈接和斷開的模式)
2020騰訊面試題!附答案

第一次握手:主機 A 發送位碼爲 syn=1,隨機產生 seq number=1234567 的數據包到服務器,主機 B由 SYN=1 知道, A 要求創建聯機;

第 二 次 握 手 : 主 機 B 收 到 請 求 後 要 確 認 聯 機 信 息 , 向 A 發 送 ack number=( 主 機 A 的seq+1),syn=1,ack=1,隨機產生 seq=7654321 的包

第三次握手: 主機 A 收到後檢查 ack number 是否正確,即第一次發送的 seq number+1,以及位碼ack 是否爲 1,若正確, 主機 A 會再發送 ack number=(主機 B 的 seq+1),ack=1,主機 B 收到後確認。

四次揮手:
2020騰訊面試題!附答案

TCP 創建鏈接要進行三次握手,而斷開鏈接要進行四次。這是因爲 TCP 的半關閉形成的。由於 TCP 鏈接是全雙工的(即數據可在兩個方向上同時傳遞)因此進行關閉時每一個方向上都要單獨進行關閉。這個單方向的關閉就叫半關閉。當一方完成它的數據發送任務,就發送一個 FIN 來向另外一方通告將要終止這個方向的鏈接。

1關閉客戶端到服務器的鏈接:首先客戶端 A 發送一個 FIN,用來關閉客戶到服務器的數據傳送,而後等待服務器的確認。其中終止標誌位 FIN=1,序列號 seq=u

2服務器收到這個 FIN,它發回一個 ACK,確認號 ack 爲收到的序號加 1。

3關閉服務器到客戶端的鏈接:也是發送一個 FIN 給客戶端。

4客戶段收到 FIN 後,併發回一個 ACK 報文確認,並將確認序號 seq 設置爲收到序號加 1。

首先進行關閉的一方將執行主動關閉,而另外一方執行被動關閉。

爲何TCP能保證不丟失

(滑動窗口,擁塞控制)

HTTP和HTTPS的區別
2020騰訊面試題!附答案

GET和POST區別

安全性:get 不安全 post 相對安全

傳輸的大小:get的傳輸較小 POST的傳輸較大

數據的來源範式Get是從服務器上得到數據,而Post則是向服務器傳遞數據的。

mysql全家桶又來了,索引數據結構
採用是B+樹,B+的設計底層數據結構和相關的索引的知識。

爲何用B+樹而不用hash和B-Tree
二叉樹(可能出現所有在左邊和右邊的數據)——>AVL(平衡二叉樹數據大量的時候平衡的時間太多,)——>B Tree(多路平衡查找樹)(數據表中的數據都是存儲在頁中的,因此一個頁中能存儲多少行數據呢指針少的狀況下要保存大量數據,只能增長樹的高度,致使IO操做變多,查詢性能變低)——>B+ Tree的一個演變的過程來進行分析,爲何使用B+ Tree的?B+ Tree,都放在了葉子節點上。提升了檢索的效率。預讀原理,由於B+ Tree無 data 域,其實就是由於沒有date域了,可是每次IO的頁的大小是固定的,每次IO讀取若干個塊塊中包含的Key域的值確定更多啊,B+樹單次磁盤IO的信息量大於B樹,從這點來看B+樹相對B樹磁盤 IO 次數少。利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點只須要一次I/O就能夠徹底載入。一、B+Tree中由於數據都在葉子節點,因此每次查詢的時間複雜度是固定的,由於穩定性保證了二、並且葉子節點之間都是鏈表的結構,因此B+Tree也是能夠支持範圍查詢的,而B樹每一個節點 key 和 data 在一塊兒,則沒法區間查找。

InooDB和MyISAM的區別(事務,彙集索引,鎖的粒度等)
2020騰訊面試題!附答案

回表,聯合索引查詢會不會用到索引系列問題
下面咱們來假設一種狀況,一個表有三個字段 ID ,name ,age,我將ID設置成主鍵索引,name設成輔助索引。而後來看一下下面的sql:

1.select * from t where id='5';

2.select * from t where name='張三';

兩個很簡單的Sql,第一個sql不用說,直接經過主鍵索引,從樹上直接能夠獲得結果,那第二個sql:首先name,mysql並不能獲得全部列的信息(也就是 ),他只能獲得主鍵ID,而後他會根據ID在進行二次查詢,這就引起了--回表問題。這就是爲啥不能使用 的緣由。那麼怎麼解決那:第一不要寫*,第二利用組合索引,也就是說你根據業務實際須要,將須要的字段造成組合索引。

因此是會用到的索引的。

最左匹配是什麼意思,聯合索引創建索引過程
在Mysql創建多列索引(聯合索引)有最左前綴的原則,即最左優先。假設咱們有兩列a,b,a和b是聯合索引,他的順序是a,b,咱們在where語句中調用a=? and b=?的時候就會走聯合索引,若是調用where a = ?的時候也會走索引,可是當咱們使用where b = ?的時候就不會走這個聯合索引。

成因:mysql建立複合索引的規則是首先會對複合索引的最左邊,也就是索引中的第一個字段進行排序,在第一個字段排序的基礎上,在對索引上第二個字段進行排序,其實就像是實現相似order by 字段1,字段2這樣的排序規則,那麼第一個字段是絕對有序的,而第二個字段就是無序的了,所以通常狀況下直接只用第二個字段判斷是用不到索引的,這就是爲何mysql要強調聯合索引最左匹配原則的緣由。

獨佔所,共享鎖,樂觀鎖講一下
寫鎖是獨佔鎖 ,在這個期間是不容許的任何線程來操做對象的。 讀鎖就是共享鎖,可使讓其餘線程的來讀取的,可是不容許有修改。樂觀鎖是共享鎖的一種,在經過樂觀鎖的時候獲取對象的時候先比較一下樂觀鎖的版本號。若是版本號是正確的,那就能夠獲取對象。若是是版本不對的話。那就是不容許修改的。

mysql分庫分表?
垂直拆分
垂直分庫是根據數據庫裏面的數據表的相關性進行拆分,好比:一個數據庫裏面既存在用戶數據,又存在訂單數據,那麼垂直拆分能夠把用戶數據放到用戶庫、把訂單數據放到訂單庫。垂直分表是對數據表進行垂直拆分的一種方式,常見的是把一個多字段的大表按經常使用字段和很是用字段進行拆分,每一個表裏面的數據記錄數通常狀況下是相同的,只是字段不同,使用主鍵關聯

垂直拆分的優勢是:
可使得行數據變小,一個數據塊(Block)就能存放更多的數據,在查詢時就會減小I/O次數(每次查詢時讀取的Block 就少)

能夠達到最大化利用Cache的目的,具體在垂直拆分的時候能夠將不常變的字段放一塊兒,將常常改變的放一塊兒

數據維護簡單

垂直拆分缺點是:
主鍵出現冗餘,須要管理冗餘列

會引發錶鏈接JOIN操做(增長CPU開銷)能夠經過在業務服務器上進行join減小數據庫壓力

依然存在單表數據量過大的問題(須要水平拆分)

事務處理複雜

水平拆分
水平拆分是經過某種策略將數據分片來存儲,分庫內分表和分庫兩部分,每片數據會分散到不一樣的MySQL表或庫,達到分佈式的效果,可以支持很是大的數據量。前面的表分區本質上也是一種特殊的庫內分表 庫內分表,僅僅是單純的解決了單一表數據過大的問題,因爲沒有把表的數據分佈到不一樣的機器上,所以對於減輕MySQL服務器的壓力來講,並無太大的做用,你們仍是競爭同一個物理機上的IO、CPU、網絡,這個就要經過分庫來解決

水平拆分的優勢是:
不存在單庫大數據和高併發的性能瓶頸

應用端改造較少

提升了系統的穩定性和負載能力

缺點是:
分片事務一致性難以解決

跨節點Join性能差,邏輯複雜

數據屢次擴展難度跟維護量極大

分片原則
能不分就不分,參考單表優化

分片數量儘可能少,分片儘可能均勻分佈在多個數據結點上,由於一個查詢SQL跨分片越多,則整體性能越差,雖然要好於全部數據在一個分片的結果,在必要的時候進行擴容,增長分片數量

分片規則須要慎重選擇作好提早規劃,分片規則的選擇,須要考慮數據的增加模式,數據的訪問模式,分片關聯性問題,以及分片擴容問題,最近的分片策略爲範圍分片,枚舉分片,一致性Hash分片,這幾種分片都有利於擴容

儘可能不要在一個事務中的SQL跨越多個分片,分佈式事務一直是個很差處理的問題

查詢條件儘可能優化,儘可能避免Select * 的方式,大量數據結果集下,會消耗大量帶寬和CPU資源,查詢儘可能避免返回大量結果集,而且儘可能爲頻繁使用的查詢語句創建索引。

經過數據冗餘和表分區賴下降跨庫Join的可能。

這裏特別強調一下分片規則的選擇問題,若是某個表的數據有明顯的時間特徵,好比訂單、交易記錄等,則他們一般比較合適用時間範圍分片,由於具備時效性的數據,咱們每每關注其近期的數據,查詢條件中每每帶有時間字段進行過濾,比較好的方案是,當前活躍的數據,採用跨度比較短的時間段進行分片,而歷史性的數據,則採用比較長的跨度存儲。

整體上來講,分片的選擇是取決於最頻繁的查詢SQL的條件,由於不帶任何Where語句的查詢SQL,會遍歷全部的分片,性能相對最差,所以這種SQL越多,對系統的影響越大,因此咱們要儘可能避免這種SQL的產生。

sql優化
1字段的優化

2查詢的優化

3索引的優化

4讀寫分離

5分庫分表的操做

6數據庫集羣的操做

線程和進程概念(共享哪些區域)
1.堆

幾乎全部對象實例被分配到這裏,也是垃圾收集器管理的主要區域。Java堆能夠被分爲新生代和老生代。進一步劃分,則有Eden空間、From Survivor空間、To Survivor空間等。不管如何劃分,都是爲了更好地回收內存、更快的分配內存。

方法區
方法區因爲存儲虛擬機加載的類的信息、常量、靜態變量、JIT編譯後的代碼等。

虛擬內存講一下(分頁)

synchronized和Lock的區別

一個是經過指令集來實現鎖住的對象的頭來實現加鎖的。

一個是經過設置一個標誌位置來鎖住獨享的。

volatile的做用

JMM內存模型和緩存一致性協議還有就是的一個是保持可見性的

算法題:存儲有[0,n)的數組,數組長度爲len。只能交換數組裏n和0的位置進行排序

/查詢學生表中姓名、學號,並以學號降序排序/
select name,StuID from Students_information order by StuID desc /*order by 以什麼排序,默認爲升序,desc是降序/

/查詢學生表中前5名學生的姓名,學號,並以學號升序排列/
select top 5 name,StuID from Students_information order by StuID /order by 默認爲升序/
二面:
項目問題10分鐘,問到了Hash衝突

利用是數組+鏈表來解決hash衝突

synchronized底層實現(markWord,entrySet,waitSet)
經過鎖住對象的頭部來實現對對象加鎖,synchronize的關鍵字在之前是使用的指令來實現的

他屬於獨佔式的悲觀鎖,同時屬於可重入鎖。代碼塊同步是使用monitorenter和monitorexit指令實現的。monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每一個monitorenter必須有對應的monitorexit與之配對任何對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。

在Java中,鎖共有4種狀態,級別從低到高依次爲:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級。?經過一個標誌位來判斷 兩個位置00表示的4種鎖

AQS底層實現(非公平鎖,公平鎖)
底層採用的是雙鏈表的來實現了的,公共鎖的定義是全部的對象在獲取鎖的時候都是須要進入隊列的。非公平鎖是在對象獲取鎖的時候是採用是首先查看鎖是否爲空,若是是空的話,那就能夠對獲取,若是是有對象持有的話,那就進入隊列進行排隊。

Spring ICO,AOP介紹
SpringIOC是spring對提供了對類的全生命週期的管理的一種思想,利用反射機制來實現的對Bean的實例化產生和建立和銷燬這樣的機制。來實現對類對的屬性的控制,這個過程當中Bean實例和生命週期是SpringIOC中最重要的。Spring的Bean產生詳細請見其餘。

SpringAOP是一種切向編程的思想。在傳統的過程時候因爲是在傳統的架構中都是垂直的流程體系。可是在這個過程當中常常產生一些橫向問題,好比log日誌記錄,權限驗證,事務處理,性能檢查的問題,爲了遵循軟件的開閉原則。就是對原來不修改進而擴展原累的方法和功能。SpringAOP就是實現了這樣一種思想。經過對原方法和類在不修改代碼的狀況下而進行了類的加強的方式。

Spring用到了什麼設計模式
工廠模式:BeanFactory就是簡單工廠模式的體現,用來建立對象的實例;

單例模式:Bean默認爲單例模式。

代理模式:Spring的AOP功能用到了JDK的動態代理和CGLIB字節碼生成技術;

模板方法:用來解決代碼重複的問題。好比. RestTemplate, JmsTemplate, JpaTemplate。

觀察者模式:定義對象鍵一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都會獲得通知被制動更新,如Spring中listener的實現–ApplicationListener。

單例爲何加鎖,volatile什麼做用
單例模式中有一種是懶漢模式。這樣的模式是會產生的線程安全的問題。volatile是讓變量在多線程的狀況下保持對其餘線程的可見。

hashmap何時用到了紅黑樹
當鏈表的節點超過8個時候採用紅黑樹來實現存儲。

介紹紅黑樹特色,爲何不用AVL樹
紅黑樹屬於平衡二叉樹。它不嚴格是由於它不是嚴格控制左、右子樹高度或節點數之差小於等於1,但紅黑樹高度依然是平均log(n),且最壞狀況高度不會超過2log(n)。紅黑樹的插入效率比AVL的數要高。

紅黑樹不追求"徹底平衡",即不像AVL那樣要求節點的 |balFact| <= 1,它只要求部分達到平衡,可是提出了爲節點增長顏色,紅黑是用非嚴格的平衡來換取增刪節點時候旋轉次數的下降,任何不平衡都會在三次旋轉以內解決,而AVL是嚴格平衡樹,所以在增長或者刪除節點的時候,根據不一樣狀況,旋轉的次數比紅黑樹要多

算法題:一個鏈表:奇數序號升序,偶數序號降序,要求作這個鏈表的總體升序排序
private static ListNode[] splitList(ListNode head) {
ListNode cur = head;

ListNode head1 = null;
ListNode head2 = null;
ListNode cur1 = null;
ListNode cur2 = null;
int num = 1;

while (head != null) {
    if (num % 2 == 1) {
        if (cur1 != null) {
            cur1.next = head;
            cur1 = cur1.next;
        } else {
            cur1 = head;
            head1 = cur1;
        }
    } else {
        if (cur2 != null) {
            cur2.next = head;
            cur2 = cur2.next;
        } else {
            cur2 = head;
            head2 = cur2;
        }
    }

    head = head.next;
    num++;
}

cur1.next = null;
cur2.next = null;
ListNode[] heads = new ListNode[]{head1, head2};
return heads;

}

private static ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;

while (cur != null) {
    ListNode next = cur.next;
    cur.next = pre;
    pre = cur;
    cur = next;
}

return pre;

}

private static ListNode mergeLists(ListNode head1, ListNode head2) {
if (head1 == null && head2 == null) {
return null;
}

if (head1 == null || head2 == null) {
    return head1 == null ? head2 : head1;
}

ListNode first = new ListNode(-1);
ListNode cur = first;

while (head1 != null && head2 != null) {
    if (head1.val < head2.val) {
        cur.next = head1;
        head1 = head1.next;
    } else {
        cur.next = head2;
        head2 = head2.next;
    }
}

cur.next = head1 == null ? head2 : head1;

return first.next;

}
三面:

介紹了兩個項目

怎麼解決超賣(答:redis + mysql樂觀鎖)

職業規劃 + 想成爲tech lead應該應該具有什麼條件

如今有哪些offer

相關文章
相關標籤/搜索