java面試總結

Java基礎

一、Hashmap是怎麼實現的,底層原理?

HashMap的底層使用數組+鏈表/紅黑樹實現。html

transient Node<K,V>[] table;這表示HashMap是Node數組構成,其中Node類的實現以下,能夠看出這其實就是個鏈表,鏈表的每一個結點是一個<K,V>映射。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

HashMap的每一個下標都存放了一條鏈表。java

常量/變量定義node

 1 /* 常量定義 */
 2 
 3 // 初始容量爲16
 4 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
 5 // 最大容量
 6 static final int MAXIMUM_CAPACITY = 1 << 30;
 7 // 負載因子,當鍵值對個數達到DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR會觸發resize擴容 
 8 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 9 // 當鏈表長度大於8,且數組長度大於MIN_TREEIFY_CAPACITY,就會轉爲紅黑樹
10 static final int TREEIFY_THRESHOLD = 8;
11 // 當resize時候發現鏈表長度小於6時,從紅黑樹退化爲鏈表
12 static final int UNTREEIFY_THRESHOLD = 6;
13 // 在要將鏈表轉爲紅黑樹以前,再進行一次判斷,若數組容量小於該值,則用resize擴容,放棄轉爲紅黑樹
14 // 主要是爲了在創建Map的初期,放置過多鍵值對進入同一個數組下標中,而致使沒必要要的鏈表->紅黑樹的轉化,此時擴容便可,可有效減小衝突
15 static final int MIN_TREEIFY_CAPACITY = 64;
16 
17 /* 變量定義 */
18 
19 // 鍵值對的個數
20 transient int size;
21 // 鍵值對的個數大於該值時候,會觸發擴容
22 int threshold;
23 // 非線程安全的集合類中幾乎都有這個變量的影子,每次結構被修改都會更新該值,表示被修改的次數
24 transient int modCount;

關於modCount的做用見這篇blogmysql

在一個迭代器初始的時候會賦予它調用這個迭代器的對象的modCount,若是在迭代器遍歷的過程當中,一旦發現這個對象的modCount和迭代器中存儲的modCount不同那就拋異常。
Fail-Fast機制:java.util.HashMap不是線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。這一策略在源碼中的實現是經過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的expectedModCount。在迭代過程當中,判斷modCount跟expectedModCount是否相等,若是不相等就表示已經有其餘線程修改了Map。程序員

注意初始容量和擴容後的容量都必須是2的次冪,爲何呢?算法

hash方法spring

先看散列方法sql

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap的散列方法如上,其實就是將hash值的高16位和低16位異或,咱們將立刻看到hash在與n - 1相與的時候,高位的信息也被考慮了,能使碰撞的機率減少,散列得更均勻。數據庫

在JDK 8中,HashMap的putVal方法中有這麼一句編程

 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); 

關鍵就是這句(n - 1) & hash,這行代碼是把待插入的結點散列到數組中某個下標中,其中hash就是經過上面的方法的獲得的,爲待插入Node的key的hash值,n是table的容量即table.length,2的次冪用二進制表示的話,只有最高位爲1,其他爲都是0。減去1,恰好就反了過來。好比16的二進制表示爲10000,減去1後的二進制表示爲01111,除了最高位其他各位都是1,保證了在相與時,能夠使得散列值分佈得更均勻(由於若是某位爲0好比1011,那麼結點永遠不會被散列到1111這個位置),且當n爲2的次冪時候有(n - 1) & hash == hash % n, 舉個例子,好比hash等於6時候,01111和00110相與就是00110,hash等於16時,相與就等於0了,多舉幾個例子即可以驗證這一結論。最後來回答爲何HashMap的容量要始終保持2的次冪

  • 使散列值分佈均勻
  • 位運算的效率比取餘的效率高

注意table.length是數組的容量,而transient int size表示存入Map中的鍵值對數。

int threshold表示臨界值,當鍵值對的個數大於臨界值,就會擴容。threshold的更新是由下面的方法完成的。

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

該方法返回大於等於cap的最小的二次冪數值。好比cap爲16,就返回16,cap爲17就返回32。

put方法

put方法主要由putVal方法實現:

  • 若是沒有產生hash衝突,直接在數組tab[i = (n - 1) & hash]處新建一個結點;
  • 不然,發生了hash衝突,此時key若是和頭結點的key相同,找到要更新的結點,直接跳到最後去更新值
  • 不然,若是數組下標中的類型是TreeNode,就插入到紅黑樹中
  • 若是隻是普通的鏈表,就在鏈表中查找,找到key相同的結點就跳出,到最後去更新值;到鏈表尾也沒有找到就在尾部插入一個新結點。接着判斷此時鏈表長度若大於8的話,還須要將鏈表轉爲紅黑樹(注意在要將鏈表轉爲紅黑樹以前,再進行一次判斷,若數組容量小於64,則用resize擴容,放棄轉爲紅黑樹)

get方法

get方法由getNode方法實現:

  • 若是在數組下標的鏈表頭就找到key相同的,那麼返回鏈表頭的值
  • 不然若是數組下標處的類型是TreeNode,就在紅黑樹中查找
  • 不然就是在普通鏈表中查找了
  • 都找不到就返回null

remove方法的流程大體和get方法相似。

HashMap的擴容,resize()過程?

newCap = oldCap << 1

resize方法中有這麼一句,說明是擴容後數組大小是原數組的兩倍。

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    // 若是數組中只有一個元素,即只有一個頭結點,從新哈希到新數組的某個下標
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        // 數組下標處的鏈表長度大於1,非紅黑樹的狀況
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            // oldCap是2的次冪,最高位是1,其他爲是0,哈希值和其相與,根據哈希值的最高位是1仍是0,鏈表被拆分紅兩條,哈希值最高位是0分到loHead。
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // 哈希值最高位是1分到hiHead
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            // loHead掛到新數組[原下標]處;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            // hiHead掛到新數組中[原下標+oldCap]處
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;

舉個例子,好比oldCap是16,二進制表示是10000,hash值的後五位和oldCap相與,由於oldCap的最高位(從右往左數的第5位)是1其他位是0,所以hash值的該位是0的全部元素被分到一條鏈表,掛到新數組中原下標處,hash值該位爲1的被分到另一條鏈表,掛到新數組中原下標+oldCap處。舉個例子:桶0中的元素其hash值後五位是0XXXX的就被分到桶0種,其hash值後五位是1XXXX就被分到桶4中。

二、Java中的錯誤和異常?

Java中的全部異常都是Throwable的子類對象,Error類和Exception類是Throwable類的兩個直接子類。

Error:包括一些嚴重的、程序不能處理的系統錯誤類。這些錯誤通常不是程序形成的,好比StackOverflowError和OutOfMemoryError。

Exception:異常分爲運行時異常和檢查型異常。

  • 檢查型異常要求必須對異常進行處理,要麼往上拋,要麼try-catch捕獲,否則不能經過編譯。這類異常比較常見的是IOException。
  • 運行時異常,可處理可不處理,在編譯時能夠經過,異常在運行時才暴露。好比數組下標越界,除0異常等。

三、Java的集合類框架介紹一下?

首先接口Collection和Map是平級的,Map沒有實現Collection。

Map的實現類常見有HashMap、TreeMap、LinkedHashMap和HashTable等。其中HashMap使用散列法實現,低層是數組,採用鏈地址法解決哈希衝突,每一個數組的下標都是一條鏈表,當長度超過8時,轉換成紅黑樹。TreeMap使用紅黑樹實現,能夠按照鍵進行排序。LinkedHashMap的實現綜合了HashMap和雙向鏈表,可保證以插入時的順序(或訪問順序,LRU的實現)進行迭代。HashTable和HashMap比,前者是線程安全的,後者不是線程安全的。HashTable的鍵或者值不容許null,HashMap容許。

Collection的實現類常見的有List、Set和Queue。List的實現類有ArrayList和LinkedList以及Vector等,ArrayList就是一個可擴容的對象數組,LinkedList是一個雙向鏈表。Vector是線程安全的(ArrayList不是線程安全的)。Set的裏的元素不可重複,實現類常見的有HashSet、TreeSet、LinkedHashSet等,HashSet的實現基於HashMap,實際上就是HashMap中的Key,一樣TreeSet低層由TreeMap實現,LinkedHashSet低層由LinkedHashMap實現。Queue的實現類有LinkedList,能夠用做棧、隊列和雙向隊列,另外還有PriorityQueue是基於堆的優先隊列。

四、Java反射是什麼?爲何要用反射,有什麼好處,哪些地方用到了反射?

反射:容許任意一個類在運行時獲取自身的類信息,而且能夠操做這個類的方法和屬性。這種動態獲取類信息和動態調用對象方法的功能稱爲Java的反射機制。

反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性。它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰,如Class.ForName()根本就沒有指定某個特定的類,徹底由你傳入的類全限定名決定,而經過new的方式你是知道運行時對象是哪一個類的。 反射避免了將程序「寫死」。

反射能夠下降程序耦合性,提升程序的靈活性。new是形成緊耦合的一大緣由。好比下面的工廠方法中,根據水果類型決定返回哪個類。

public class FruitFactory {
    public Fruit getFruit(String type) {
        Fruit fruit = null;
        if ("Apple".equals(type)) {
            fruit = new Apple();
        } else if ("Banana".equals(type)) {
            fruit = new Banana();
        } else if ("Orange".equals(type)) {
            fruit = new Orange();
        }
        return fruit;
    }
}

class Fruit {}
class Banana extends Fruit {}
class Orange extends Fruit {}
class Apple extends Fruit {}

可是咱們事先並不知道以後會有哪些類,好比新增了Mango,就須要在if-else中新增;若是之後不須要Banana了就須要從if-else中刪除。這就是說只要子類變更了,咱們必須在工廠類進行修改,而後再編譯。若是用反射呢?

public class FruitFactory {
    public Fruit getFruit(String type) {
        Fruit fruit = null;
        try {
            fruit = (Fruit) Class.forName(type).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fruit;
    }
}

class Fruit {}
class Banana extends Fruit {}
class Orange extends Fruit {}
class Apple extends Fruit {}

若是再將子類的全限定名存放在配置文件中。

class-type=com.fruit.Apple

那麼無論新增多少子類,根據不一樣的場景只需修改文件就行了,上面的代碼無需修改代碼、從新編譯,就能正確運行。

哪些地方用到了反射?舉幾個例子

  • 加載數據庫驅動時
  • Spring的IOC容器,根據XML配置文件中的類全限定名動態加載類
  • 工廠方法模式中(如上)

五、說說你對面向對象、封裝、繼承、多態的理解?

  • 封裝:隱藏實現細節,明確標識出容許外部使用的全部成員函數和數據項。 防止代碼或數據被破壞。
  • 繼承:子類繼承父類,擁有父類的全部功能,而且能夠在父類基礎上進行擴展。實現了代碼重用。子類和父類是兼容的,外部調用者無需關注二者的區別。
  • 多態:一個接口有多個子類或實現類,在運行期間(而非編譯期間)才決定所引用的對象的實際類型,再根據其實際的類型調用其對應的方法,也就是「動態綁定」。

Java實現多態有三個必要條件:繼承、重寫、向上轉型。

  • 繼承:子類繼承或者實行父類
  • 重寫:在子類裏面重寫從父類繼承下來的方法
  • 向上轉型:父類引用指向子類對象
    public class OOP {
        public static void main(String[] args) {
            /*
             * 1. Cat繼承了Animal
             * 2. Cat重寫了Animal的eat方法
             * 3. 父類Animal的引用指向了子類Cat。
             * 在編譯期間其靜態類型爲Animal;在運行期間其實際類型爲Cat,所以animal.eat()將選擇Cat的eat方法而不是其餘子類的eat方法
             */
            Animal animal = new Cat();
            printEating(animal);
        }
    
        public static void printEating(Animal animal) {
            animal.eat();
        }
    }
    
    abstract class Animal {
        abstract void eat();
    }
    class Cat extends Animal {
        @Override
        void eat() {
            System.out.println("Cat eating...");
        }
    }
    class Dog extends Animal {
        @Override
        void eat() {
            System.out.println("Dog eating...");
        }
    }

    六、實現不可變對象的策略?好比JDK中的String類。

    • 不提供setter方法(包括修改字段、字段引用到的的對象等方法)
    • 將全部字段設置爲final、private
    • 將類修飾爲final,不容許子類繼承、重寫方法。能夠將構造函數設爲private,經過工廠方法建立。
    • 若是類的字段是對可變對象的引用,不容許修改被引用對象。 1)不提供修改可變對象的方法;2)不共享對可變對象的引用。對於外部傳入的可變對象,不保存該引用。如要保存能夠保存其複製後的副本;對於內部可變對象,不要返回對象自己,而是返回其複製後的副本。

    七、Java序列話中若是有些字段不想進行序列化,怎麼辦?

    對於不想進行序列化的變量,使用transient關鍵字修飾。功能是:阻止實例中那些用此關鍵字修飾的的變量序列化;當對象被反序列化時,被transient修飾的變量值不會被持久化和恢復。transient只能修飾變量,不能修飾類和方法。

    八、==和equals的區別?

    == 對於基本類型,比較值是否相等,對於對象,比較的是兩個對象的地址是否相同,便是否是指相同一個對象。

    equals的默認實現實際上使用了==來比較兩個對象是否相等,可是像Integer、String這些類對equals方法進行了重寫,比較的是兩個對象的內容是否相等。

    對於Integer,若是依然堅持使用==來比較,有一些要注意的地方。對於[-128,127]區間裏的數,有一個緩存。所以

  • Integer a = 127;
    Integer b = 127;
    System.out.println(a == b); // true
    
    Integer a = 128;
    Integer b = 128;
    System.out.println(a == b); // false
    
    // 不過採用new的方式,a在堆中,這裏打印false
    Integer a = new Integer(127);
    Integer b = 127;
    System.out.println(a == b);

    對於String,由於它有一個常量池。因此

  • String a = "gg" + "rr";
    String b = "ggrr";
    System.out.println(a == b); // true
    
    // 固然牽涉到new的話,該對象就在堆上建立了,因此這裏打印false
    String a = "gg" + "rr";
    String b = new String("ggrr");
    System.out.println(a == b);

    九、接口和抽象類的區別?

    • Java不能多繼承,一個類只能繼承一個抽象類;可是能夠實現多個接口。
    • 繼承抽象類是一種IS-A的關係,實現接口是一種LIKE-A的關係。
    • 繼承抽象類能夠實現對父類代碼的複用,也能夠重寫抽象方法實現子類特有的功能。實現接口能夠爲類新增額外的功能。
    • 抽象類定義基本的共性內容,接口是定義額外的功能。
    • 調用者使用動機不一樣, 實現接口是爲了使用他規範的某一個行爲;繼承抽象類是爲了使用這個類屬性和行爲.

    十、給你一個Person對象p,如何將該對象變成JSON表示?

    本質是考察Java反射,由於要實現一個通用的程序。實現可能根本不知道該類有哪些字段,因此不能經過get和set等方法來獲取鍵-值。使用反射的getDeclaredFields()能夠得到其聲明的字段。若是字段是private的,須要調用該字段的f.setAccessible(true);,才能讀取和修改該字段。

  • import java.lang.reflect.Field;
    import java.util.HashMap;
    
    public class Object2Json {
        public static class Person {
            private int age;
            private String name;
    
            public Person(int age, String name) {
                this.age = age;
                this.name = name;
            }
        }
    
        public static void main(String[] args) throws IllegalAccessException {
            Person p = new Person(18, "Bob");
            Class<?> classPerson = p.getClass();
            Field[] fields = classPerson.getDeclaredFields();
            HashMap<String, String> map = new HashMap<>();
            for (Field f: fields) {
                // 對於private字段要先設置accessible爲true
                f.setAccessible(true);
                map.put(String.valueOf(f.getName()), String.valueOf(f.get(p)));
            }
            System.out.println(map);
        }
    }

    獲得了map,再弄成JSON標準格式就行了。

    十一、JDBC中sql查詢的完整過程?操做事務呢?

  • @Test
    public void fun2() throws SQLException, ClassNotFoundException {
        // 1. 註冊驅動
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8";
        // 2.創建鏈接
        Connection connection = DriverManager.getConnection(url, "root", "admin");
        // 3. 執行sql語句使用的Statement或者PreparedStatment
        Statement statement = connection.createStatement();
        String sql = "select * from stu;";
        ResultSet resultSet = statement.executeQuery(sql);
    
        while (resultSet.next()) {
            // 第一列是id,因此從第二行開始
            String name = resultSet.getString(2); // 能夠傳入列的索引,1表明第一行,索引不是從0開始
            int age = resultSet.getInt(3);
            String gender = resultSet.getString(4);
            System.out.println("學生姓名:" + name + " | 年齡:" + age + " | 性別:" + gender);
        }
        // 關閉結果集
        resultSet.close();
        // 關閉statemenet
        statement.close();
        // 關閉鏈接
        connection.close();
    }

    ResultSet維持一個指向當前行記錄的cursor(遊標)指針

    • 註冊驅動
    • 創建鏈接
    • 準備sql語句
    • 執行sql語句獲得結果集
    • 對結果集進行遍歷
    • 關閉結果集(ResultSet)
    • 關閉statement
    • 關閉鏈接(connection)

    因爲JDBC默認自動提交事務,每執行一個update ,delete或者insert的時候都會自動提交到數據庫,沒法回滾事務。因此若須要實現事務的回滾,要指定setAutoCommit(false)

    • true:sql命令的提交(commit)由驅動程序負責
    • false:sql命令的提交由應用程序負責,程序必須調用commit或者rollback方法

    JDBC操做事務的格式以下,在捕獲異常中進行事務的回滾。

  • 十二、實現單例,有哪些要注意的地方?

    就普通的實現方法來看。

    • 不容許在其餘類中直接new出對象,故構造方法私有化
    • 在本類中建立惟一一個static實例對象
    • 定義一個public static方法,返回該實例
    • public class SingletonImp {
          // 餓漢模式
          private static SingletonImp singletonImp = new SingletonImp();
          // 私有化(private)該類的構造函數
          private SingletonImp() {
          }
      
          public static SingletonImp getInstance() {
              return singletonImp;
          }
      }

      餓漢模式:線程安全,不能延遲加載

    • public class SingletonImp4 {
          private static volatile SingletonImp4 singletonImp4;
      
          private SingletonImp4() {}
      
          public static SingletonImp4 getInstance() {
              if (singletonImp4 == null) {
                  synchronized (SingletonImp4.class) {
                      if (singletonImp4 == null) {
                          singletonImp4 = new SingletonImp4();
                      }
                  }
              }
      
              return singletonImp4;
          }
      }

      雙重檢測鎖+volatile禁止語義重排。由於singletonImp4 = new SingletonImp4();不是原子操做。

    • public class SingletonImp6 {
          private SingletonImp6() {}
      
          // 專門用於建立Singleton的靜態類
          private static class Nested {
              private static SingletonImp6 singletonImp6 = new SingletonImp6();
          }
      
          public static SingletonImp6 getInstance() {
              return Nested.singletonImp6;
          }
      }

      靜態內部類,能夠實現延遲加載。

      最推薦的是單一元素枚舉實現單例。

      • 寫法簡單
      • 枚舉實例的建立默認就是線程安全的
      • 提供了自由的序列化機制。面對複雜的序列或反射攻擊,也能保證是單例
        public enum Singleton {
            INSTANCE;
            public void anyOtherMethod() {}
        }

        數據結構與算法

        一、二叉樹的遍歷方式?它們屬於深搜仍是廣搜?

        • 先序遍歷。父結點 -> 左子結點 -> 右子結點
        • 中序遍歷。左子結點 -> 父結點 -> 右子結點
        • 後序遍歷。左子結點 -> 右子結點 -> 父結點
        • 層序遍歷。一層一層自上而下,從左往右訪問。

        其中,先序、中序、後序遍歷屬於深度優先搜索(DFS),層序遍歷屬於廣度優先搜索(BFS)

        二、什麼是平衡二叉樹,它的好處是什麼?被應用在哪些場景中?

        平衡二叉樹首先是一棵二叉查找樹,其次它須要知足其左右兩棵子樹的高度之差不超過1,且子樹也必須是平衡二叉樹,換句話說對於平衡二叉樹的每一個結點,要求其左右子樹高度之差都不超過1。

        二叉查找樹在最壞狀況下,退化成鏈表,查找時間從平均O(lg n)降到O(n),平衡二叉樹使樹的結構更加平衡,提升了查找的效率;可是因爲插入和刪除後須要從新恢復樹的平衡,因此插入和刪除會慢一些。

        應用場景好比在HashMap中用到了紅黑樹(平衡二叉樹的特例),數據庫索引中的B+樹等。

        三、數組和鏈表的區別?

        • 數組是一段連續的內存空間,因此支持隨機訪問,能夠經過下標以O(1)的時間進行查找。鏈表中的元素在內存中不是連續的,能夠分散在內存中各個地方。所以它不支持隨機訪問,查找效率是O(n)
        • 數組的插入和刪除元素時間是O(n),插入和刪除後要移動元素;而鏈表只需斷開結點連接再從新連上,因此鏈表的刪除和插入時間是O(1)
        • 數組必須指定初始大小,達到最大容量若是不擴容就不能再存入新的元素;而鏈表沒有容量的限制

        應用場景:數組適合讀多寫少、事先知道元素大概個數的狀況;鏈表適合寫多讀少的狀況。

        四、冒泡和快排的區別,最差和平均的時間複雜度?

        • 冒泡:相鄰元素進行兩兩比較,將最大值推進到最右邊。重複以上過程。時間複雜度平均O(N^2)最差O(N^2),空間複雜度O(1)
        • 快排:選擇數組中第一個元素做爲基準,從左邊向右找到第一個大於等於基準的元素,從右邊向左找到第一個小於等於基準的元素,交換這兩個元素,最後基準左邊的元素都小於等於基準,基準右邊的元素都大於等於基準。而後固定基準元素,對其左邊和右邊採起一樣的作法。典型的分治思想。時間複雜度平均O(N lgN)最差O(N^2),基於遞歸的實現因爲用到了系統棧,因此平均狀況下空間複雜度爲O(lgN)
        • 冒泡排序是穩定的排序算法,快速排序不是穩定的排序算法。

        排序中所說的穩定是指,對於兩個相同的元素,在排序後其相對位置沒有發生變化。

        常見的穩定排序有,冒泡、插入、歸併、基數排序。選擇、希爾、快排、堆排序都不是穩定的。

        五、說說經常使用的散列方法?解決哈希衝突的方法有哪些?

        • 最經常使用的除留餘數法(取餘),大小爲M的數組,key的哈希值爲k,那麼k % M的值必定是落在0-M-1之間的。
        • 直接定址法。用一個函數對key做映射獲得哈希值。如線性函數:hash(key) = a * key + b
        • 其餘(略)

        解決哈希衝突的方法:

        • 開放定址法:採用M大小的數組存放N個鍵值對,其中M > N。開放定址中最簡單的是線性探測法,當發生碰撞時,直接檢查散列表中的下一個位置。若是到了數組末尾,折回到索引0處繼續查找。
        • 鏈地址法:採用鏈表數組實現,當發生哈希衝突時,將衝突鍵值以頭插或者尾插的方式插入數組下標所在的鏈表,HashMap中正是使用了這種方法。
        • 再哈希法:當發生哈希衝突時,換一個散列函數從新計算哈希值。
        • 公共溢出區法:創建一個基本表和溢出表,全部衝突的鍵值都存放到溢出表中。在查找時,先在基本表中查,相等,查找成功,如不相等則去溢出表中進行順序查找。

        六、堆排序的過程說一下?

        堆排序使用了最大堆/最小堆,拿數組升序排序來講,須要創建一個最大堆,基於數組實現的二叉堆能夠看做一棵徹底二叉樹,其知足堆中每一個父結點它左右兩個結點值都大,且堆頂的元素最大。

        • 將堆頂元素和數組最後一個元素交換,最大元素被交換數組最後一個位置,同時從堆中刪除原來處於堆頂的最大元素
        • 被交換到堆頂的元素通常會打破堆結構的定義,所以須要進行堆的調整(下沉)。將堆頂的元素和其左右兩個結點比較,將三者中的最大的交換到堆頂,而後繼續跟蹤此結點,循環上述過程,直到他比左右兩個結點都大時中止調整,此時堆調整完畢,再次知足堆結構的定義
        • 重複以上兩個過程。直到堆中只剩一個元素,此時排序完成

        每次調整堆的平均時間爲O(lg N),所以對大小爲N的數組排序,時間複雜度最差和平均都 O(N lg N).

        七、堆排序和快排應用場景的?時間複雜度和空間複雜度各是多少?

        快排序在平均狀況下,比絕大多數排序算法都快些。不少編程語言的sort默認使用快排,像Java的Array.sort()就採用了雙軸快速排序 。堆排序使用堆實現,空間複雜度只有O(1)。堆排序使用堆的結構,能以O(1)的時間得到最大/最小值,在處理TOP K問題時很方便,另外堆還能夠實現優先隊列。

        時間複雜度:

        • 快排,平均O(N lg N) ,最差O(N^2),這種狀況發生在數組自己就有序,這樣每次切分元素都是數組中的最小值,切分得就極爲不平衡。
        • 堆排序,平均和最差都是O(N lgN)。由於每次調整堆結構都是O(lg N),所以對大小爲N的數組排序,時間複雜度最差和平均都 O(N lg N).

        空間複雜度:

        • 快排,通常基於遞歸實現,須要使用系統棧O(lg N)
        • 堆排序,額外空間複雜度O(1)
        • 放一張神圖

          975503-20170214211234550-1109833343

          八、雙向鏈表,給你Node a,在其後插入Node c?

        • c.next = a.next;
          a.next = c;
          c.prev = a;
          // 若是a不是最後一個結點,就有下面一句
          c.next.prev = c;

          計算機網絡

          一、HTTP有哪些請求方法?它們的做用或者說應用場景?

          • GET: 請求指定的頁面信息,並返回實體主體。
          • HEAD: 和GET相似,只不過不返回報文主體,只返回響應首部。可用於確認URI的有效性及資源更新的日期時間;
          • POST: 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。
          • PUT: 用來傳輸文件,要求請求報文的主體中包含文件內容,而後保存到請求URI指定的位置。
          • DELETE: 和PUT相反,按請求URI刪除指定的資源。
          • OPTIONS: 用來查詢針對請求URI指定的資源支持的方法。若是請求成功,會有一個Allow的頭包含相似「GET,POST」這樣的信息
          • TRACE: 讓服務端將以前的請求通訊返回給客戶端的方法(所以客戶端能夠得知請求是怎麼一步步到服務端的)。主要用於測試或診斷。
          • CONNECT: 使用 SSL(Secure Sockets Layer,安全套接層)和 TLS(Transport Layer Security,傳輸層安全)協議把通訊內容加密後經網絡隧道傳輸。

          二、GET和POST的對比,或者說區別?

          • GET用於獲取數據,POST用於提交數據;
          • GET的參數長度有限制(不一樣的瀏覽器和服務器限制不一樣),POST沒有限制
          • GET把參數包含在URL中,POST經過封裝參數到請求體中發送;
          • GET請求只能進行url編碼,而POST支持多種編碼方式。
          • GET能夠發送的參數只能是ASCII類型,POST沒有限制,甚至能夠傳輸二進制。
          • GET比POST更不安全,由於參數直接暴露在URL上,因此不能用來傳遞敏感信息;
          • GET刷新無害,而POST會再次提交數據
          • 還有GET請求會被保存瀏覽器歷史記錄,能夠被收藏爲書籤,而POST請求不能等等

          GET和POST本質都是TCP鏈接。不過GET產生一個TCP數據包;POST產生兩個TCP數據包。

          對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200 OK(返回數據);

          而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 OK(返回數據)。

          三、TCP三次握手?四次揮手?

          三次握手

          • 請求先由客戶端發起。客戶端發送SYN = 1和客戶端序號c給服務端,同時進入SYN-SENT狀態
          • 服務端收到SYN後須要作出確認,因而發送ACK = 1,同時本身也發送SYN = 一、服務端序號s,還有確認號c + 1,表示想收到的下一個序號。此時服務端進入SYN-RCVD狀態
          • 客戶端收到服務端的SYN和ACK,作出確認,發送ACK = 1,以及序號c +1,同時發送確認號s + 1,表示客戶端想收到下一個序號。此時客戶端和服務端進入ESTABLISHED狀態,鏈接已創建!

          f8ef1381-713f-4fbe-83d7-b13a1c4b6bc9

          四次揮手

          • 關閉鏈接也是先有客戶端發起。客戶端發送FIN = 1和序號c向服務端請求斷開鏈接。此時客戶端進入FIN-WAIT-1狀態
          • 服務端收到FIN後作出確認,發送ACK = 1和服務端序號,還有確認號c + 1表示想要收到的下一個序號。服務端此時還能夠向客戶端發送數據。此時服務端進入CLOSE-WAIT狀態,客戶端進入FIN-WAIT-2狀態
          • 服務端沒有數據發送時,它向客戶端發送FIN= 一、ACK = 1請求斷開鏈接,同時發送服務端序號s以及確認號c + 1。此時服務端進入LAST-ACK狀態
          • 客戶端收到後進行確認,發送ACK = 1,以及須要c + 1和確認號s + 1。此時客戶端進入TIME-WAIT狀態。客戶端須要等待2MSL,確保服務端收到了ACK,若這期間客戶端沒有收到服務端的消息,即可認爲服務端收到了確認,此時能夠斷開鏈接。客戶端和服務端進入CLOSED狀態。

          8e9432d9-e6ca-4227-a8b6-fd958df00b6b

          四、TCP爲何須要三次握手?兩次不行嗎?

          兩次握手的話,只要服務端發出確認就創建鏈接了。有一種狀況是客戶端發出了兩次鏈接請求,但因爲某種緣由,使得第一次請求被滯留了。第二次請求先到達後創建鏈接成功,此後第一次請求終於到達,這是一個失效的請求了,服務端覺得這是一個新的請求因而贊成創建鏈接,可是此時客戶端不搭理服務端,服務端一直處於等待狀態,這樣就浪費了資源。假設採用三次握手,因爲服務端還須要等待客戶端的確認,若客戶端沒有確認,服務端就能夠認爲客戶端沒有想要創建鏈接的意思,因而此次鏈接不會生效。

          五、四次握手,爲何客戶端發送確認後還須要等待2MSL?

          由於第四次握手客戶端發送ACK確認後,有可能丟包了,致使服務端沒有收到,服務端就會再次發送FIN = 1,若是客戶端不等待當即CLOSED,客戶端就不能對服務端的FIN = 1進行確認。等待的目的就是爲了能在服務端再次發送FIN = 1時候能進行確認。若是在2MSL內客戶端都沒有收到服務端的任何消息,便認爲服務端收到了確認。此時能夠結束TCP鏈接。

          六、cookie和session區別和聯繫?

          • Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中;
          • Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。
          • session的運行依賴session id,而session id是存在cookie中的,也就是說,若是瀏覽器禁用了cookie ,同時session也會失效(可是能夠經過url重寫,即在url中傳遞session_id)

          七、爲什麼使用session?session的原理?

          好比網上購物,每一個用戶有本身的購物車,當點擊下單時,因爲HTTP協議無狀態,並不知道是哪一個用戶操做的,因此服務端要爲特定的用戶建立特定的Session,用於標識這個用戶,而且跟蹤用戶。

          Session原理:瀏覽器第一次訪問服務器時,服務器會響應一個cookie給瀏覽器。這個cookie記錄的就是sessionId,以後每次訪問攜帶着這個sessionId,服務器裏查詢該sessionId,即可以識別並跟蹤特定的用戶了。

          Cookie原理:第一次訪問服務器,服務器響應時,要求瀏覽器記住一個信息。以後瀏覽器每次訪問服務器時候,攜帶第一次記住的信息訪問。至關於服務器識別客戶端的一個通行證。Cookie不可跨域,瀏覽覽器判斷一個網站是否能操做另外一個網站Cookie的依據是域名。Google與Baidu的域名不同,所以Google不能操做Baidu的Cookie,換句話說Google只能操做Google的Cookie。

          八、網絡的7層模型瞭解嗎?

          即OSI參考模型。

          • 應用層。針對特定應用的協議,爲應用程序提供服務。如電子郵件、遠程登陸、文件傳輸等協議。
          • 表示層。主要負責數據格式的轉換,把不一樣表現形式的信息轉換成適合網絡傳輸的格式。
          • 會話層。通訊管理,負責創建和斷開通訊鏈接。即什麼時候創建鏈接、什麼時候斷開鏈接以及保持多久的鏈接。
          • 傳輸層。在兩個通訊結點之間負責數據的傳輸,起着可靠傳輸的做用。
          • 網絡層。路由選擇。在多個網絡之間轉發數據包,負責將數據包傳送到目標地址。
          • 數據鏈路層。負責物理層面上互聯設備之間的通訊傳輸。例如與一個以太網相連的兩個節點之間的通訊。是數據幀與一、0比特流之間的轉換。
          • 物理層。主要是一、0比特流與電子信號的高低電平之間的轉換。

          還有一種TCP/IP五層模型,就是把應用層、表示層、會話層統一歸到應用層。借用一張圖。

          20170905102825355

          九、有了傳輸層爲何還須要網絡層?或者說網絡層和傳輸層是如何協做的?

          網絡層是針對主機與主機之間的服務。而傳輸層針對的是不一樣主機進程之間的通訊。傳輸層協議將應用進程的消息傳送到網絡層,可是它並不涉及消息是怎麼在網絡層之間傳送(這部分是由網絡層的路由選擇完成的)。網絡層真正負責將數據包從源IP地址轉發到目標IP地址,而傳輸層負責將數據包再遞交給主機中對應端口的進程。

          e24d3846-c2d5-4714-afed-106c7c8096bf

          打個比方。房子A中的人要向房子B中的人寫信。房子中都有專門負責將主人寫好的信投遞到郵箱,以及從郵箱接收信件後交到主人手中的管家。那麼:

          • 房子 = 主機
          • 信的內容 = 應用程序消息
          • 信封 = 數據包,帶有源端口、目的端口、源IP地址、目的IP地址。
          • 郵遞員 = 網絡層協議,知道信從哪一個房子開始發的,以及最後要送到哪一個具體的房子。
          • 管家 = 傳輸層協議,負責將信投入到信箱中、以及從信箱中接收信件。知道這封信是誰寫的以及要送到誰手上(具體端口號)

          以上只是我的理解,若有誤請聯繫更正。

          十、TCP和UDP的區別?

          • TCP面向鏈接,傳輸數據以前要須要創建會話。UDP是無鏈接的。
          • TCP提供可靠傳輸,保證數據不丟包、不重複且按順序到達;UDP只盡努力交付,不保證可靠交付
          • TCP提供了擁塞控制;UDP不提供
          • TCP是面向字節流的;UDP面向報文。
          • TCP只支持點到點通訊;UDP支持一對1、一對多、多對多的交互通訊。
          • TCP首部開銷大20字節,UDP首部開銷小8字節。

          十一、傳輸層的可靠傳輸指的是什麼?是如何實現的?

          可靠傳輸是指

          • 傳輸的信道不產生差錯
          • 保證傳輸數據的正確性,無差錯、不丟失、不重複且按順序到達。

          TCP如何實現可靠傳輸:

          • 應用數據被分割成TCP認爲最適合發送的塊進行傳輸
          • 超時重傳,TCP發出一個分組後,它啓動一個定時器,等接收方確認收到這個分組。若是發送方不能及時收到一個確認,將重傳給接收方。
          • 序號,用於檢測丟失的分組和冗餘的分組。
          • 確認,告知對方已經正確收到的分組以及指望的下一個分組
          • 校驗和,校驗數據在傳輸過程當中是否發生改變,如校驗有錯則丟棄分組;
          • 流量控制,使用滑動窗口,發送窗口的大小由接收窗口和擁塞窗口的的大小決定(取二者中小的那個),當接收方來不及處理髮送方的數據,能提示發送方下降發送的速率,防止包丟失。
          • 擁塞控制:當網絡擁塞時,減小數據的發送。

          十二、主機A向主機B發送數據,在這個過程當中,傳輸層和網絡層作了什麼?

          當TCP鏈接創建以後,應用程序就可以使用該鏈接進行數據收發。應用程序將數據提交給TCP,TCP將數據放入本身的緩存,數據會被當作字節流並進行分段,而後加上TCP頭部並提交給網絡層。再加上IP頭後被網絡層提交給到目的主機,目的主機的IP層會將分組提交給TCP,TCP根據報文段的頭部信息找到相應的socket,並將報文段提交給該socket,socket是和應用關聯的,因而數據就提交給了應用。

          對於UDP會簡單些,UDP面向報文段。傳輸層加上UDP頭部遞交給網絡層,再加上IP頭部經路由轉發到目的主機,目的主機將分組提交給UDP,UDP根據頭部信息找到相應的socket,並將報文段提交給該socket,socket是和應用關聯的,因而數據就提交給了應用。

          1三、TCP序號的做用?怎麼保證可靠傳輸的?

          序號和確認號是實現可靠傳輸的關鍵。

          • 序號:當前數據包的首個字節的順序號。
          • 確認號:表示下一個想要接收的字節序號,同時確認號表示對發送方的一個確認迴應,表示已經正確收到確認號以前的字節了。

          通訊雙方經過序號和確認號,來判斷數據是否丟失、是否按順序到達、是否冗餘等,以此決定要不要進行重傳丟失的分組或丟棄冗餘的分組。換句話說,由於有了序號、確認號和重傳機制,保證了數據不丟失、不重複、有序到達。

          1四、瀏覽器發起HTTP請求後發生了什麼?越詳細越好。

          當在瀏覽器輸入網址www.baidu.com並敲下回車後:

          • DNS域名解析,將域名www.baidu.com解析成IP地址
          • 發起TCP三次握手,創建TCP鏈接。瀏覽器以一個隨機端口(1024~65535)向服務器的80端口發起TCP鏈接。
          • TCP鏈接創建後,發起HTTP請求。
          • 服務端響應HTTP請求,將html代碼返回給瀏覽器。
          • 瀏覽器解析html代碼,請求html中的資源
          • 瀏覽器對頁面進行渲染呈現給用戶

          964016-20160830113547246-672458721

          1五、DNS域名解析的請求過程?

          • 先在瀏覽器自身的DNS緩存中搜索
          • 如上述步驟未找到,瀏覽器搜索操做系統自己的DNS緩存
          • 若是在系統DNS緩存中未找到,則嘗試讀取hosts文件,尋找有沒有該域名對應的IP
          • 若是hosts文件中沒找到,瀏覽器會向本地配置的首選DNS服務器發起域名解析請求 。運營商的DNS服務器首先查找自身的緩存,若找到對應的條目且沒有過時,則解析成功。若是沒有找到,運營商的DNS代咱們的瀏覽器,以根域名->頂級域名->二級域名->三級域名這樣的順序發起迭代DNS解析請求。

          964016-20160830113557949-272537363

          1六、HTTP是基於TCP仍是UDP的?

          HTTP協議是基於TCP協議的,客戶端向服務端發送一個HTTP請求時,須要先與服務端創建TCP鏈接(三次握手),握手成功之後才能進行數據交互。

          1七、HTTP請求和響應的報文結構(格式)?

          HTTP請求的報文格式:

          • 請求行:包括請求方法、URL、HTTP協議版本號
          • 請求頭:若干鍵值對組成
          • 請求空行:告訴服務器請求頭的鍵值對已經發送完畢
          • 請求主體

          20170330192653242

          HTTP響應的報文格式:

          • 響應行:HTTP協議版本號、狀態碼、狀態碼描述
          • 響應頭:若干鍵值對錶示
          • 響應空行:標識響應頭的結束
          • 響應主體

          20170330192754102

          1八、HTTP常見的狀態碼?

          • 1XX:信息性狀態碼,表示接收的請求正在處理
          • 2XX:成功狀態碼,表示請求正常處理完畢
          • 3XX:重定向狀態碼,表示須要進行附加操做以完成請求
          • 4XX:客戶端錯誤狀態碼,表示服務器沒法處理請求
          • 5XX:服務端錯誤狀態碼,表示服務器處理請求出錯

          常見的狀態碼有:

          • 200 OK,請求被正常處理
          • 301 Move Permanently,永久性重定向
          • 302 Found,臨時性重定向
          • 400 Bad Request,請求報文中存在語法錯誤
          • 403 Forbidden,對請求資源的訪問被服務器拒絕
          • 404 Not Found,在服務器上不能找到請求的資源
          • 500 Internal Server Error,服務器內部錯誤
          • synchronized做了哪些優化?

            synchronized對內置鎖引入了偏向鎖、輕量級鎖、自旋鎖、鎖消除等優化。使得性能和重入鎖差很少了。

            • 偏向鎖:偏向鎖會偏向第一個得到它的線程,若是在接下來的執行過程當中,該鎖沒有被其餘線程獲取,則持有偏向鎖的線程永遠也不須要再進行同步。偏向鎖是在無競爭的狀況下把整個同步都消除掉,CAS操做也沒有了。適合於同一個線程請求同一個鎖,不適用於不一樣線程請求同一個鎖,此時會形成偏向鎖失效。
            • 輕量級鎖:若是偏向鎖失效,虛擬機不會當即掛起線程,會使用一種稱爲輕量級鎖的優化手段,輕量級鎖的加鎖和解鎖都是經過CAS操做完成的。若是線程得到輕量級鎖成功,則能夠順利進入臨界區。若是輕量級鎖加鎖失敗,表示其餘線程搶先獲得了鎖,輕量級鎖將膨脹爲重量級鎖。
            • 自旋鎖:鎖膨脹後,虛擬機爲了不線程真實地在操做系統層面掛起,虛擬機還會作最後的努力--自旋鎖。若是共享數據的鎖定狀態只有很短的一段時間,爲了這段時間去掛起和恢復線程(都須要轉入內核態)並不值得,因此此時讓後面請求鎖的那個線程稍微等待如下,但不放棄處理器的執行時間。這裏的等待其實就是執行了一個忙循環,這就是所謂的自旋。虛擬機會讓當前線程作幾個循環,若干次循環後若是獲得了鎖,就順利進入臨界區;若是仍是沒獲得,這纔將線程在操做系統層面掛起。
            • 鎖消除:虛擬機即時編譯時,對一些代碼上要求同步,但被檢測到不可能存在共享數據競爭的鎖進行消除。鎖消除的依據來源於「逃逸分析」技術。堆上的全部數據都不會逃逸出去被其餘線程訪問到,就能夠把它們當棧上的數據對待,認爲它們是線程私有的,同步加鎖就是沒有必要的。

            五、Java中線程的建立方式有哪些?

            • 繼承Thread並重寫run方法
            • 實現Runnable並重寫run方法,而後做爲參數傳入Thread
            • 實現Callable,並重寫call(),call方法有返回值。使用FutureTask包裝Callable實現類,其中FutureTask實現了Runnable和Future接口,最後將FutureTask做爲參數傳入Thread中
            • 由線程池建立並管理線程。
            • 七、BIO、NIO、AIO的區別?

              首先要搞明白在I/O中的同步、異步、阻塞、非阻塞是什麼意思。

              • 同步I/O。由用戶進程本身處理I/O的讀寫,處理過程當中不能作其餘事。須要主動去詢問I/O狀態。

              • 異步I/O。由系統內核完成I/O操做,完成後系統會通知用戶進程。

              • 阻塞。I/O請求操做須要的條件不知足,請求操做一直等待,直到條件知足。

              • 非阻塞。 I/O請求操做須要的條件不知足,會當即返回一個標誌,而不會一直等待。

              如今來看BIO、NIO、AIO的區別。

              BIO:同步並阻塞。用戶進程在發起一個I/O請求後,必須等待I/O準備就緒,I/O操做也由本身來處理,在IO操做未完成以前,用戶進程必須等待。

              NIO:同步非阻塞。用戶進程發起一個I/O請求後可當即返回去作其餘任務,當I/O準備就緒時它會收到通知。接着由這個線程自行進行I/O操做,I/O操做自己仍是同步的。

              AIO:異步非阻塞。用戶進程發起一個I/O操做之後可當即返回去作其餘任務,真正的I/O操做由內核完成後通知用戶進程。

              NIO和AIO的不一樣:NIO是操做系統通知用戶進程I/O已經準備就緒,由用戶進程自行完成I/O操做;AIO是操做系統完成I/O後通知用戶進程。

              BIO是爲每個客戶端鏈接開啓一個線程,簡單說就是一個鏈接一個線程。

              NIO主要組件有Seletor、Channel、Buffer,數據須要經過BUffer包裝後才能使用Channel進行讀取和寫入。一個Selector能夠由一個線程管理,每個Channel可看做一個客戶端鏈接。一個Selector能夠監聽多個Channel,即便用一個或極少數的線程來管理大量的客戶端鏈接。當與客戶端鏈接的數據沒有準備好時,Selector處於等待狀態,一旦某個Channel的準備好了數據,Selector就能當即獲得通知。

              八、兩個線程交替打印奇數和偶數?

              先使用synchronized實現。PrintOdd用於打印奇數;PrintEven用於打印偶數。核心就是判斷當前count若是是奇數,就讓PrintEven阻塞,PrintOdd打印後喚醒在lock對象上等待的PrintEven而且釋放鎖。此時PrintEven得到鎖打印偶數再喚醒PrintOdd,兩個線程如此交替喚醒對方就實現了交替打印奇偶數。

            • public class PrintOddEven {
                  private static final Object lock = new Object();
                  private static int count = 1;
              
                  static class PrintOdd implements Runnable {
                      @Override
                      public void run() {
                          for (int i = 0; i < 10; i++) {
                              synchronized (lock) {
                                  try {
                                      while ((count & 1) != 1) {
                                          lock.wait();
                                      }
                                      System.out.println(Thread.currentThread().getName() + " " +count);
                                      count++;
                                      lock.notify();
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              }
                          }
                      }
                  }
              
                  static class PrintEven implements Runnable {
                      @Override
                      public void run() {
                          for (int i = 0; i < 10; i++) {
                              synchronized (lock) {
                                  try {
                                      while ((count & 1) != 0) {
                                          lock.wait();
                                      }
                                      System.out.println(Thread.currentThread().getName() + " " +count);
                                      count++;
                                      lock.notify();
                                  } catch (InterruptedException e) {
                                      e.printStackTrace();
                                  }
                              }
                          }
                      }
                  }
              
                  public static void main(String[] args) {
                      new Thread(new PrintOdd()).start();
                      new Thread(new PrintEven()).start();
                  }
              }

              1六、進程和線程的區別?

              進程是資源分配的最小單位,線程是程序執行的最小單位。 進程是線程的容器,即進程裏面能夠容納多個線程,多個線程之間能夠共享數據。

              1七、線程的死鎖指什麼?如何檢測死鎖?如何解決死鎖?

              是指兩個或兩個以上的線程在執行過程當中,互相佔用着對方想要的資源但都不釋放,形成了互相等待,結果線程都沒法向前推動。

              死鎖的檢測:能夠採用等待圖(wait-for gragh)。採用深度優先搜索的算法實現,若是圖中有環路就說明存在死鎖。

              解決死鎖:

              • 破環鎖的四個必要條件之一,能夠預防死鎖。
              • 加鎖順序保持一致。不一樣的加鎖順序極可能致使死鎖,好比哲學家問題:A先申請筷子1在申請筷子2,而B先申請筷子2在申請筷子1,最後誰也得不到一雙筷子(同時擁有筷子1和筷子2)
              • 撤消或掛起進程,剝奪資源。終止參與死鎖的進程,收回它們佔有的資源,從而解除死鎖。

              1八、CPU線程調度?

              • 協同式線程調度:線程的執行時間以及線程的切換都是由線程自己來控制,線程把本身的任務執行完後,主動通知系統切換到另外一個線程。優勢是沒有線程安全的問題,缺點是線程執行的時間不可控,可能由於某一個線程不讓出CPU,而致使整個程序被阻塞。

              • 搶佔式調度模式:線程的執行時間和切換都是由系統來分配和控制的。不過能夠經過設置線程優先級,讓優先級高的線程優先佔用CPU。

                Java虛擬機默認採用搶佔式調度模型。

              1九、HashMap在多線程下有可能出現什麼問題?

              • JDK8以前,併發put下可能形成死循環。緣由是多線程下單鏈表的數據結構被破環,指向混亂,形成了鏈表成環。JDK 8中對HashMap作了大量優化,已經不存在這個問題。
              • 併發put,有可能形成鍵值對的丟失,若是兩個線程同時讀取到當前node,在鏈表尾部插入,先插入的線程是無效的,會被後面的線程覆蓋掉。

              20、ConcurrentHashMap是如何保證線程安全的?

              JDK 7中使用的是分段鎖,內部分紅了16個Segment即分段,每一個分段能夠看做是一個小型的HashMap,每次put只會鎖定一個分段,下降了鎖的粒度:

              • 首先根據key計算出一個hash值,找到對應的Segment
              • 調用Segment的lock方法(Segment繼承了重入鎖),鎖住該段內的數據,因此並無鎖住ConcurrentHashMap的所有數據
              • 根據key計算出hash值,找到Segment中數組中對應下標的鏈表,並將該數據放置到該鏈表中
              • 判斷當前Segment包含元素的數量大於閾值,則Segment進行擴容(Segment的個數是不能擴容的,可是單個Segment裏面的數組是能夠擴容的)

              多線程put的時候,只要被加入的鍵值不屬於 同一個分段,就能夠作到真正的並行put。對不一樣的Segment則無需考慮線程同步,對於同一個Segment的操做才需考慮。

              JDK 8中使用了CAS+synchronized保證線程安全,也採起了數組+鏈表/紅黑樹的結構。

              put時使用synchronized鎖住了桶中鏈表的頭結點。

              數組的擴容,被問到了我在看吧.....我只知道多個線程能夠協助數據的遷移。

              有這麼一個問題,ConcurrentHashMap,有三個線程,A先put觸發了擴容,擴容時間很長,此時B也put會怎麼樣?此時C調用get方法會怎麼樣?C讀取到的元素是舊桶中的元素仍是新桶中的

              A先觸發擴容,ConcurrentHashMap遷移是在鎖定舊桶的前提下進行遷移的,並無去鎖定新桶。

              • 在某個桶的遷移過程當中,別的線程想要對該桶進行put操做怎麼辦?一旦某個桶在遷移過程當中了,必然要獲取該桶的鎖,因此其餘線程的put操做要被阻塞。所以B被阻塞
              • 某個桶已經遷移完成(其餘桶還未完成),別的線程想要對該桶進行put操做怎麼辦?該線程會首先檢查是否還有未分配的遷移任務,若是有則先去執行遷移任務,若是沒有即所有任務已經分發出去了,那麼此時該線程能夠直接對新的桶進行插入操做(映射到的新桶必然已經完成了遷移,因此能夠放心執行操做)
              • 2一、ThreadLocal的做用和實現原理?

                對於共享變量,通常採起同步的方式保證線程安全。而ThreadLocal是爲每個線程都提供了一個線程內的局部變量,每一個線程只能訪問到屬於它的副本。

                實現原理,下面是set和get的實現

              • // set方法
                public void set(T value) {
                   Thread t = Thread.currentThread();
                   ThreadLocalMap map = getMap(t);
                   if (map != null)
                       map.set(this, value);
                   else
                       createMap(t, value);
                }
                
                // 上面的getMap方法
                ThreadLocalMap getMap(Thread t) {
                   return t.threadLocals;
                }
                
                // get方法
                public T get() {
                   Thread t = Thread.currentThread();
                   ThreadLocalMap map = getMap(t);
                   if (map != null) {
                       ThreadLocalMap.Entry e = map.getEntry(this);
                       if (e != null) {
                           @SuppressWarnings("unchecked")
                           T result = (T)e.value;
                           return result;
                       }
                   }
                   return setInitialValue();
                }

                sleep、wait、yield的區別和聯繫?

                sleep() 容許指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得CPU 時間,指定的時間一過,線程從新進入可執行狀態。調用sleep後不會釋放鎖。

                yield() 使得線程放棄CPU執行時間,可是不使線程阻塞,線程從運行狀態進入就緒狀態,隨時可能再次分得 CPU 時間。有可能當某個線程調用了yield()方法暫停以後進入就緒狀態,它又立刻搶佔了CPU的執行權,繼續執行。

                wait()是Object的方法,會使線程進入阻塞狀態,和sleep不一樣,wait會同時釋放鎖。wait/notify在調用以前必須先得到對象的鎖。

                30、Thread類中的start和run方法區別?

                run方法只是一個普通方法調用,仍是在調用它的線程裏執行。

                start纔是開啓線程的方法,run方法裏面的邏輯會在新開的線程中執行。

                JVM

                一、Java內存區域(注意不是Java內存模型JMM)的劃分?

                • 程序計數器。
                • 虛擬機棧。
                • 本地方法棧。
                • Java堆。
                • 方法區。

                前三個是線程私有的,後兩個是線程共享的。

                字節碼解釋器經過改變程序計數器的值來決定下一條要執行的指令,爲了在線程切換後每條線程都能正確回到上次執行的位置,由於每條線程都有本身的程序計數器。

                虛擬機棧是存放Java方法內存模型,每一個方法在執行時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法返回地址等信息。方法的開始調用對應着棧幀的進棧,方法執行完成對應這棧幀的出棧。位於棧頂被稱爲「當前方法」。

                本地方法棧和虛擬機棧相似,不過虛擬機棧針對Java方法,而本地方法棧針對Native方法。

                Java堆。對象實例被分配內存的地方,也是垃圾回收的主要區域。

                方法區。存放被虛擬機加載的類信息、常量(final)、靜態變量(static)、即時編譯期編譯後的代碼。方法區是用永久代實現的,這個區域的內存回收目標主要是針對常量池的回收和類型的卸載。運行時常量池是方法區的一部分,運行時常量池是Class文件中的一項信息,存放編譯期生成的各類字面量和符號引用。

                二、新生代和老年代。對象如何進入老年代,新生代怎麼變成老年代?

                Java堆分爲新生代和老年代。在新生代又被劃分爲Eden區,From Sruvivor和To Survivor區,比例是8:1:1,因此新生代可用空間其實只有其容量的90%。對象優先被分配在Eden區。

                • 不過大對象好比長字符串、數組因爲須要大量連續的內存空間,因此直接進入老年代。這是對象進入老年代的一種方式,
                • 還有就是長期存活的對象會進入老年代。在Eden區出生的對象通過一次Minor GC會若存活,且Survivor區容納得下,就會進入Survivor區且對象年齡加1,當對象年齡達到必定的值,就會進入老年代。
                • 在上述狀況中,若Survivor區不能容納存活的對象,則會經過分配擔保機制轉移到老年代。
                • 同年齡的對象達到suivivor空間的一半,大於等於該年齡的對象會直接進入老年代。

                三、新生代的GC和老年代的GC?

                發生在新生代的GC稱爲Minor GC,當Eden區被佔滿了而又須要分配內存時,會發生一次Minor GC,通常使用複製算法,將Eden和From Survivor區中還存活的對象一塊兒複製到To Survivor區中,而後一次性清理掉Eden和From Survivor中的內存,使用複製算法不會產生碎片。

                老年代的GC稱爲Full GC或者Major GC:

                • 當老年代的內存佔滿而又須要分配內存時,會發起Full GC
                • 調用System.gc()時,可能會發生Full GC,並不保證必定會執行。
                • 在Minor GC後survivor區放不下,經過擔保機制進入老年代的對象比老年代的內存空間還大,會發生Full GC;
                • 在發生Minor GC以前,會先比較歷次晉升到老年代的對象平均年齡,若是大於老年代的內存,也會觸發Full GC。若是不容許擔保失敗,直接Full GC。

                四、對象在何時能夠被回收,調用finalize方法後必定會被回收嗎?

                在通過可達性分析後,到GC Roots不可達的對象能夠被回收(但並非必定會被回收,至少要通過兩次標記),此時對象被第一次標記,並進行一次判斷:

                • 若是該對象沒有調用過或者沒有重寫finalize()方法,那麼在第二次標記後能夠被回收了;
                • 不然,該對象會進入一個FQueue中,稍後由JVM創建的一個Finalizer線程中去執行回收,此時若對象中finalize中「自救」,即和引用鏈上的任意一個對象創建引用關係,到GC Roots又可達了,在第二次標記時它會被移除「即將回收」的集合;若是finalize中沒有逃脫,那就面臨被回收。

                所以finalize方法被調用後,對象不必定會被回收。

                五、哪些對象能夠做爲GC Roots?

                • 虛擬機棧中引用的對象
                • 方法區中類靜態屬性引用的對象(static)
                • 方法區中常量引用的對象(final)
                • 本地方法棧中引用的對象

                六、講一講垃圾回收算法?

                • 複製算法,通常用於新生代的垃圾回收
                • 標記清除, 通常用於老年代的垃圾回收
                • 標記整理,通常用於老年代的垃圾回收
                • 分代收集:根據對象存活週期的不一樣把Java堆分爲新生代和老年代。新生代中又分爲Eden區、from survivor區和to survivor區,默認8:1:1,對象默認建立在Eden區,每次垃圾收集時新生代都會有大量對象死亡。此時利用複製算法將Eden區和from survivor區還存活的對象一併複製到tosurvivor區。老年代的對象存活率高,沒有額外空間進行分配擔保,所以採用標記-清除或者標記-整理的算法進行回收。前者會產生空間碎片,然後者不會。

                七、介紹下類加載器和類加載過程?

                先說類加載器

                在Java中,系統提供了三種類加載器。

                • 啓動類加載器(Bootstrap ClassLoader),啓動類加載器沒法被Java程序直接引用,用戶在編寫自定義類加載器時,若是須要委派給啓動類加載器,直接使用null。
                • 擴展類加載器(Extension ClassLoader)
                • 應用程序類加載器(Application ClassLoader),負責加載用戶類路徑(ClassPath)上鎖指定的類庫。是程序中默認的類加載器。

                固然用戶也能夠自定義類加載器。

                再說類加載的過程

                主要是如下幾個過程:

                加載 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載

                加載

                • 經過一個類的全限定名獲取定義該類的二進制字節流
                • 將字節流表示的靜態存儲結構轉化爲方法區的運行時數據結構
                • 在內存中生成這個類的Class對象,做爲方法區這個類的各類數據的訪問入口

                驗證

                • 文件格式驗證:好比檢查是否以魔數0xCAFEBABE開頭
                • 元數據驗證:對類的元數據信息進行語義校驗,保證不存在不符合Java語言規範的元數據信息。好比檢查該類是否繼承了被final修飾的類。
                • 字節碼驗證,經過數據流和控制流的分析,驗證程序語義是合法的、符合邏輯的。

                準備
                爲類變量(static)分配內存並設置默認值。好比static int a = 123在準備階段的默認值是0,可是若是有final修飾,在準備階段就會被賦值爲123了。

                解析

                將常量池中的符號引用替換成直接引用的過程。包括類或接口、字段、類方法、接口方法的解析。

                初始化

                按照程序員的計劃初始化類變量。如static int a = 123,在準備階段a的值被設置爲默認的0,而到了初始化階段其值被設置爲123。

                八、什麼是雙親委派模型,有什麼好處?如何打破雙親委派模型?

                類加載器之間知足雙親委派模型,即:除了頂層的啓動類加載器外,其餘全部類加載器都必需要本身的父類加載器。當一個類加載器收到類加載請求時,本身首先不會去加載這個類,而是不斷把這個請求委派給父類加載器完成,所以全部的加載請求最終都傳遞給了頂層的啓動類加載器。只有當父類沒法完成這個加載請求時,子類加載器纔會嘗試本身去加載。

                雙親委派模型的好處?使得Java的類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。Java的Object類是全部類的父類,所以不管哪一個類加載器都會加載這個類,由於雙親委派模型,全部的加載請求都委派給了頂層的啓動類加載器進行加載。因此Object類在任何類加載器環境中都是同一個類。

                如何打破雙親委派模型?使用OSGi能夠打破。OSGI(Open Services Gateway Initiative),或者通俗點說JAVA動態模塊系統。能夠實現代碼熱替換、模塊熱部署。在OSGi環境下,類加載器再也不是雙親委派模型中的樹狀結構,而是進一步發展爲更加複雜的網狀結構。

                九、說一說CMS和G1垃圾收集器?各有什麼特色。

                CMS(Concurrent Mark Sweep) 從名字能夠看出是能夠進行併發標記-清除的垃圾收集器。針對老年代的垃圾收集器,目的是儘量地減小用戶線程的停頓時間。

                收集過程有以下幾個步驟:

                • 初始標記:標記從GC Roots能直接關聯到的對象,會暫停用戶線程
                • 併發標記:即在堆中堆對象進行可達性分析,從GC Roots開始找出存活的對象,能夠和用戶線程一塊兒進行
                • 從新標記:修正併發標記期間因用戶程序繼續運做致使標記產生變更的對象的標記記錄
                • 併發清除:併發清除標記階段中肯定爲不可達的對象

                CMS的缺點:

                • 因爲是基於標記-清除算法,因此會產生空間碎片
                • 沒法處理浮動垃圾,即在清理期間因爲用戶線程還在運行,還會持續產生垃圾,而這部分垃圾尚未被標記,在本次沒法進行回收。
                • 對CPU資源敏感

                CMS比較相似適合用戶交互的場景,能夠得到較小的響應時間。

                G1(Garbage First),有以下特色:

                • 並行與併發
                • 分代收集
                • 空間整合 :總體上看是「標記-整理」算法,局部(兩個Region之間 )看是複製算法。確保其不會產生空間碎片。(這是和CMS的區別之一)
                • 可預測的停頓:G1除了追求低停頓外,還能創建可預測的時間模型,主要緣由是它能夠有計劃地避免在整個Java堆中進行全區域的垃圾收集。

                在使用G1收集器時,Java堆的內存劃分爲多個大小相等的獨立區域,新生代和老年代再也不是物理隔離。G1跟蹤各個區域的垃圾堆積的價值大小,在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的區域。

                G1的收集過程和CMS有些相似:

                • 初始標記:標記與GC Roots直接關聯的對象,會暫停用戶線程(Stop the World)
                • 併發標記:併發從GC Roots開始找出存活的對象,能夠和用戶線程一塊兒進行
                • 最終標記:修正併發標記期間因用戶程序繼續運做致使標記產生變更的對象的標記記錄
                • 篩選回收:清除標記階段中肯定爲不可達的對象,具體來講對各個區域的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃。

                G1的優點:可預測的停頓;實時性較強,大幅減小了長時間的gc;必定程度的高吞吐量。

                十、CMS和G1的區別?

                由上一個問題可總結出CMS和G1的區別:

                • G1堆的內存佈局和其餘垃圾收集器不一樣,它將整個Java堆劃分紅多個大小相等的獨立區域(Region)。G1依然保留了分代收集,可是新生代和老年代再也不是物理隔離的,它們都屬於一部分Region的集合,所以僅使用G1就能夠管理整個堆。
                • CMS基於標記-清除,會產生空間碎片;G1從總體看是標記-整理,從局部(兩個Region之間)看是複製算法,不會產生空間碎片。
                • G1能實現可預測的停頓。

                十一、GC必定會致使停頓嗎,爲何必定要停頓?任意時候均可以GC嗎仍是在特定的時候?

                GC進行時必須暫停全部Java執行線程,這被稱爲Stop The World。爲何要停頓呢?由於可達性分析過程當中不容許對象的引用關係還在變化,不然可達性分析的準確性就沒法獲得保證。因此須要STW以保證可達性分析的正確性。

                程序執行時並不是在全部地方都能停頓下來開始GC,只有在「安全點」才能暫停。安全點指的是:HotSpot沒有爲每一條指令都生成OopMap(Ordinary Object Pointer),而是在一些特定的位置記錄了這些信息。這些位置就叫安全點。

                數據庫

                一、數據庫設計的三大範式?

                • 第一範式1NF: 數據表中的每一列(字段),必須是不可拆分的最小單元,也就是確保每一列的原子性。如訂單信息列爲orderInfo = "DD1024 2018.5.18",必須拆分爲orderId和orderTime。
                • 第二範式2NF: 在知足第一範式的基礎上,表中的全部列都必需依賴於主鍵(和主鍵有關係),其餘和主鍵沒有關係的列能夠拆分出去。通俗點說就是:一個表只描述一件事情。好比order表中有orderId、orderTime、userId和userName,只有前兩列依賴於訂單表,後兩列須要拆分到user表中。
                • 第三範式3NF: 在知足第二範式的基礎上,要求數據不能有傳遞關係。表中的每一列都要與主鍵直接相關,而不是間接相關(表中的每一列只能依賴於主鍵)。好比order表中有orderId、orderTime、userId和userName,根據orderId能夠查出userId,根據userId又能夠查出userName,這就是數據的傳遞性,徹底能夠只留下userId這一列。

                二、MySql的事務隔離級別?推薦使用哪一種?

                • 讀未提交
                • 讀已提交
                • 可重複讀
                • 串行化

                在具體解釋上面的四個隔離級別前。有必要了解事務的四大特性(ACID)

                推薦閱讀這篇博客

                • 原子性(Atomicity):事務一旦開始,其後全部的操做要麼都作完,要麼都不作,不能被中斷。如在執行過程當中出錯,會回滾到事務開始前的狀態。
                • 一致性(Consistency):事務開始前和結束後,數據庫的完整性約束沒有被破環。好比A向B轉了錢,A扣了錢,B必須收到對應數目的錢。
                • 隔離性(Isolation):同一時間只容許一個事務請求同一個數據,其餘事務不能影響當前事務,即該事務提交前對其餘事務都不可見。
                • 持久性(Durability):事務完成後,事務對數據庫的更新被保存到數據庫,其結果是永久的。

                事務併發可能產生的問題:
                髒數據:事務對緩衝池中的行記錄進行修改,可是尚未被提交。

                • 髒讀:事務A讀取到了事務B修改但未提交的數據。若是此時B回滾到修改以前的狀態,A就讀到了髒數據。
                • 不可重複讀:事務A屢次讀取同一個數據,此時事務B在A讀取過程當中對數據修改並提交了,致使事務A在同一個事務中屢次讀取同一數據而結果不一樣。
                • 幻讀:事務A對錶進行修改,這個修改涉及到表中全部的行,但此時事務B新插入了一條數據,事務A就會發現竟然還有數據沒有被修改,就好像發生幻覺同樣。

                髒讀是讀取到事務未提交的數據,不可重複度讀讀取到的是提交提交後的數據,只不過在一次事務中讀取結果不同。

                不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表。

                821187-20160811171241606-133220585

                通常來講,數據庫隔離級別不同,可能出現的併發問題也不一樣。級別最高的是串行化,全部問題都不會出現。可是在併發下性能極低,可重複讀會只會致使幻讀。

                因此通常使用MySQL默認的可重複讀便可。MVCC(多版本併發控制)使用undo_log使得事務能夠讀取到數據的快照(某個歷史版本),從而實現了可重複讀。MySQL採用Next-Key Lock算法,對於索引的掃描不只是鎖住掃描到的索引,還鎖住了這些索引覆蓋的範圍,避免了不可重複讀和幻讀的產生。

                三、MySql數據庫在什麼狀況下出現死鎖?產生死鎖的四個必要條件?如何解決死鎖?

                死鎖是指兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象,若無外力做用兩個事務都沒法推動,這樣就產生了死鎖。下去 死鎖的四個必要條件:

                • 互斥條件:即任什麼時候刻,一個資源只能被一個進程使用。其餘進程必須等待。
                • 請求和保持條件:即當資源請求者在請求其餘的資源的同時保持對原有資源的佔有且不釋放。
                • 不剝奪條件:資源請求者不能強制從資源佔有者手中奪取資源,資源只能由資源佔有者主動釋放。
                • 環路等待條件:好比A佔有B在等待的資源(B等待A釋放),B佔有A在等待的資源(A等待B釋放)。多個進程循環等待着相鄰進程佔用着的資源。

                避免死鎖能夠經過破環四個必要條件之一。

                解決死鎖的方法:

                • 加鎖順序保持一致。不一樣的加鎖順序極可能致使死鎖,好比哲學家問題:A先申請筷子1在申請筷子2,而B先申請筷子2在申請筷子1,最後誰也得不到一雙筷子(同時擁有筷子1和筷子2)
                • 超時,爲其中一個事務設置等待時間,若超過這個閾值事務就回滾,另外一個等待的事務就能得以繼續執行。
                • 及時檢測出死鎖,回滾undo量最小的事務。通常是採用等待圖(wait-for gragh)。採用深度優先搜索的算法實現,若是圖中有環路就說明存在死鎖。

                四、如今發現sql查詢很慢,如何分析哪裏出了問題,應該如何優化?

                開啓慢查詢,查找哪些sql語句執行得慢。使用explain查看語句的執行計劃,好比有沒有使用到索引,是否啓用了全表掃描等。查詢慢,很大多是由於沒有使用索引或者索引沒有被命中。還有其餘的緣由,好比發生了死鎖,硬件、網速等緣由。

                優化手段:爲相關列添加索引,而且確保索引能夠被命中。優化sql語句的編寫。

                五、索引的好處?

                索引是對數據庫表中一個或多個列的值進行排序的結構。MySql中索引是B+樹,在查找時能夠利用二分查找等高效率的查找方式,以O(lg n)的時間找到。所以索引能夠加快查詢速度。

                六、哪些狀況須要創建索引?

                • 在常常要搜索的列上
                • 常常出如今where後面的列上
                • 在做爲主鍵的列上
                • 做爲外鍵的列上
                • 常常須要排序、分組和聯合操做的字段創建索引

                哪些狀況不適合創建索引?

                • 查詢中不多使用的字段
                • 數值太少的字段
                • 惟一性不太差的字段
                • 更新頻繁的字段
                • 不會出如今where後的字段
                • 索引適合創建在小字段上,text和blob等大字段不適合創建索引

                七、索引的最左匹配原則瞭解嗎?

                建了一個(a,b,c)的聯合索引,那麼實際等於建了(a),(a,b),(a,b,c)三個索引,可是有時在條件查詢時只會匹配到a或者(a, b)而不會匹配到(a, b, c)。下面的例子

                SELECT * FROM table WHERE a = 1 AND c = 3; // 使用了索引a,c不走索引 SELECT * FROM table WHERE a = 1 AND b < 2 AND c = 3; // 使用到了索引(a,b),c不走索引

                創建聯合索引(a, b ,c),因此索引是按照a -> b -> c的順序進行排序的。a-b-c這樣的索引是先找a,而後在範圍裏面找b,再在範圍內找c。 因此上面的語句裏的c 會分散在不少個b裏面且不是排序的,因此沒辦法走索引。

                舉個例子好比(a, b)聯合索引,先按a排序再按b排序,獲得

                (1,1)->(1, 2)->(2, 1)  (2, 4)->(3, 1)->(3, 2)

                若是執行select a from table where b=2,就沒有使用到(a, b)這個聯合索引,由於b的值1,2,1,4,1,2顯然不是排序的。

                具體來講:MySQL會從左開始一直向右匹配直到遇到範圍查詢(>,<,BETWEEN,LIKE)就中止匹配,好比: a = 1 AND b = 2 AND c > 3 AND d = 4,若是創建 (a,b,c,d)順序的索引,使用了索引(a, b, c),可是d是沒有走索引的,若是創建(a,b,d,c)的索引,則能夠命中索引(a, b, c, d),其中a,b,d的順序能夠任意調整。

                等於(=)和in 能夠亂序。好比,a = 1 AND b = 2 AND c = 3 創建(a,b,c)索引能夠任意順序。

                八、如何創建複合索引,能夠使sql語句能儘量匹配到索引?

                • 等於條件的索引放在前面(最左),範圍查詢放在後面。a = 1 AND b = 2 AND c > 3 AND d = 4,創建(a, b, d, c)就是不錯的選擇;
                • 先過濾後排序(ORDER BY)如SELECT * FROM t WHERE c = 100 and d = 'xyz' ORDER BY b創建(c, d, b)聯合索引就是不錯的選擇
                • 對於索引列的查詢,通常不建議使用LIKE操做,像LIKE '%abc'這樣的不能命中索引;不過LIKE 'abc%'能夠命中索引。

                九、創建了索引,索引就必定會被命中嗎?或者說索引何時失效

                • 使用了not in, <>,!=則不會命中索引。注:<>是不等號
                • innoDB引擎下,若使用OR,只有先後兩個列都有索引才能命中(執行查詢計劃,type是index_merge),不然不會使用索引。
                • 模糊查詢中,通配符在最前面時,即LIKE '%abc'這樣不能命中索引
                • 對列進行函數運算的狀況(如 where md5(password) = "xxxx")
                • 聯合索引中,遇到範圍查詢時,其後的索引不會被命中
                • 存了數字的char或varchar類型,常見的如用字符串表示的手機號,在查詢時不加引號,則不會命中(如where phone=‘13340456789’能命中,where phone=13340456789不能命中)
                • 當數據量小時,MySQL發現全表掃描反而比使用索引查詢更快時不會使用索引。

                十、爲何要使用聯合索引?

                MySQL5.0以前,一個表一次只能使用一個索引,沒法同時使用多個索引分別進行條件掃描。可是從5.1開始,引入了 index merge 優化技術,對同一個表能夠使用多個索引分別進行條件掃描。

                推薦閱讀這篇博客

                • 減小開銷。建了一個(a,b,c)的聯合索引,至關於建了(a),(a,b),(a,b,c)三個索引
                • 覆蓋索引。減小了隨機IO操做。一樣的有複合索引(a,b,c),若是有以下的sql: select a,b,c from table where a=1 and b = 1。那麼MySQL能夠直接經過遍歷索引取得數據,而無需回表,這減小了不少的隨機io操做
                • 效率高。索引列越多,經過索引篩選出的數據越少。好比有1000W條數據的表,有以下sql:select * from table where a = 1 and b =2 and c = 3,假設假設每一個條件能夠篩選出10%的數據,若是隻有單值索引,那麼經過該索引能篩選出1000W10%=100w 條數據,而後再回表從100w條數據中找到符合b=2 and c= 3的數據,而後再排序,再分頁;若是是複合索引,經過索引篩選出1000w 10% 10% 10%=1w,而後再排序、分頁。

                十一、既然索引能夠加快查詢速度,索引越多越好是嗎?

                推薦閱讀這篇優博客

                大多數狀況下索引能大幅度提升查詢效率,但數據的變動(增刪改)都須要維護索引,所以更多的索引意味着更多的維護成本和更多的空間 (一本100頁的書,卻有50頁目錄?)並且太小的表,創建索引可能會更慢(讀個2頁的宣傳手冊,你還先去找目錄?)

                十二、主鍵和惟一索引的區別?

                • 主鍵是一種約束,惟一索引是索引,一種數據結構。
                • 主鍵必定是惟一索引,惟一索引不必定是主鍵。
                • 一個表中能夠有多個惟一索引,但只能有一個主鍵。
                • 主鍵不容許空值,惟一索引容許。
                • 主鍵能夠作爲外鍵,惟一索引不行;

                1三、B+樹和B-樹的區別?

                B-樹是一種平衡的多路查找樹。2-3樹和2-3-4樹都是B-樹的特例。一棵M階的B-樹,除了根結點外的其餘非葉子結點,最多含有M-1對鍵和連接,最少含有M/2對鍵和連接。根結點能夠少於M/2,可是也不能少於2對。

                • 關鍵字集合分佈在整顆樹中
                • 每一個元素在該樹中只出現一次,可能在葉子結點上,也可能在非葉子結點上。
                • 搜索有可能在非葉子結點結束。
                • 全部葉子結點位於同一層

                B+樹是B-樹的變體,也是一種多路查找樹。

                • 非葉子結點值能夠看做索引,僅含有其子樹中的最大(或)最小關鍵字。
                • 葉子結點保存了全部關鍵字,且葉子結點按照從小到大的順序排列,是一個雙向鏈表結構。
                • 只能在葉子節點命中搜索

                B+ 樹更適合用於數據庫和操做系統的文件系統中。

                假設一個結點就是一個頁面,B樹遍歷全部記錄,經過中序遍歷的方式,要屢次返回到父結點,同一個結點屢次訪問了,增長了磁盤I/O操做的次數。B+由於在葉子結點存放了全部的記錄,並且是雙向鏈表的結構,只需在葉子節點這一層就能遍歷全部記錄,大大減小了磁盤I/O操做,因此數據庫索引用B+樹結構更好。

                1四、彙集索引與非彙集索引的區別?

                • 對於彙集索引,表記錄的排列順序和與索引的排列順序是一致的;非彙集索引不是
                • 彙集索引就是按每張表的主鍵構造一棵B+樹,每張表只能擁有一個彙集索引;一張表能夠有多個非彙集索引
                • 彙集索引的葉子結點存放的是整張表的行記錄數據;非彙集索引的葉子結點並不包含行記錄的所有數據,除了包含鍵值還包含一個書籤——即相應行數據的彙集索引鍵。所以經過非彙集索引查找時,先根據葉子結點的指針得到指向主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄。

                1五、InnoDB和MyISAM引擎的區別?

                • InnoDB支持事務,MyISAM不支持
                • InnoDB是行鎖設計,MyISAM是表鎖設計
                • InnoDB支持外鍵,MyISAM不支持
                • InnoDB採用彙集的方式,每張表按照主鍵的順序進行存放。若是沒有主鍵,InnoDB會爲每一行生成一個6字節的ROWID並以此爲主鍵;MyISAM能夠不指定主鍵和索引
                • InnoDB沒有保存表的總行數,所以查詢行數時會遍歷整表;而MyISAM有一個變量存儲可表的總行數,查詢時能夠直接取出該值
                • InnoDB適合聯機事務處理(OLTP),MyISAM適合聯機分析處理(OLAP)

                1六、COUNT(*)COUNT(1)的區別?COUNT(列名)COUNT(*)的區別?

                COUNT(*)COUNT(1)沒區別。COUNT(列名)COUNT(*)區別在於前者不會統計列爲NULL的數據,後者會統計。

                1七、數據庫中悲觀鎖和樂觀鎖講一講?

                悲觀鎖:老是假設在併發下會出現問題,即假設多個事務對同一個數據的訪問會產生衝突。當其餘事務想要訪問數據時,會在臨界區提早加鎖,須要將其阻塞掛起。好比MySQL中的排他鎖(X鎖)、和共享鎖(S鎖)

                樂觀鎖: 老是假設任務在併發下是安全的,即假設多個事務對同一個數據的訪問不會發生衝突,所以不會加鎖,就對數據進行修改。當遇到衝突時,採用CAS或者版本號、時間戳的方式來解決衝突。數據庫中使用的樂觀鎖是版本號或時間戳。樂觀併發控制(OCC)是一種用來解決寫-寫衝突的無鎖併發控制,不用加鎖就嘗試對數據進行修改,在修改以前先檢查一下版本號,真正提交事務時,再檢查版本號有,若是不相同說明已經被其餘事務修改了,能夠選擇回滾當前事務或者重試;若是版本號相同,則能夠修改。

                提一下樂觀鎖和MVCC的區別,其實MVCC也利用了版本號,和樂觀鎖仍是能扯上些關係。

                MVCC主要解決了讀-寫的阻塞,由於讀只能讀到數據的歷史版本(快照);OCC主要解決了寫-寫的阻塞,多個事務對數據進行修改而不加鎖,更新失敗的事務能夠選擇回滾或者重試。

                當多個用戶/進程/線程同時對數據庫進行操做時,會出現3種衝突情形:讀-讀,不存在任何問題;讀-寫,有隔離性問題,可能遇到髒讀、不可重複讀 、幻讀等。寫-寫,可能丟失更新。多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,讀操做只讀該事務開始前的數據庫的快照,實現了一致性非鎖定讀。 這樣在讀操做不用阻塞寫操做,寫操做不用阻塞讀操做的同時,避免了髒讀和不可重複讀。樂觀併發控制(OCC)是一種用來解決寫-寫衝突的無鎖併發控制,不用加鎖就嘗試對數據進行修改,在修改以前先檢查一下版本號,真正提交事務時,再檢查版本號有,若是不相同說明已經被其餘事務修改了,能夠選擇回滾當前事務或者重試;若是版本號相同,則能夠修改。

                1八、MySQL的可重複讀是如何實現的?

                MVCC(多版本併發控制)使用undo_log使得事務能夠讀取到數據的快照(某個歷史版本),從而實現了可重複讀。MySQL採用Next-Key Lock算法,對於索引的掃描不只是鎖住掃描到的索引,還鎖住了這些索引覆蓋的範圍,避免了不可重複讀和幻讀的產生。

                具體來講:

                在可重複讀下: select....from會採用MVCC實現的一致性非鎖定讀,讀取的是事務開始的快照,避免了不可重複讀。select .....from .... for update會採用 Next-Key Locking來保證可重複讀和幻讀。

                在讀已提交下: select....from 會採用快照,讀取的是最新一份的快照數據,不可以保證不可重複讀和幻讀;select .....from .... for update會採用Record Lock,不可以保證不可重複讀/幻讀。

                1九、覆蓋索引是什麼?

                若是一個索引包含(或覆蓋)全部須要查詢的字段的值,即只需掃描索引而無須回表,這稱爲「覆蓋索引」。InnoDB的輔助索引在葉子節點中保存了部分鍵值信息以及指向彙集索引鍵的指針,若是輔助索引葉子結點中的鍵值信息已經覆蓋了要查詢的字段,就沒有必要利用指向主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄了。

                20、MySQL中JOIN和UNION什麼區別?

                UNION 操做符用於合併兩個或多個 SELECT 語句的結果集。UNION 內部的 SELECT 語句必須擁有相同數量的列。列也必須擁有相同的數據類型。同時,每條 SELECT 語句中的列的順序必須相同。默認狀況下,UNION會過濾掉重複的值。使用 UNION ALL則會包含重複的值。

                JOIN用於鏈接兩個有關聯的表,篩選兩個表中知足條件(ON後的條件)的行記錄獲得一個結果集。從結果集中SELECT的字段能夠是表A或者表B中的任意列。

                JOIN經常使用的有LEFT JOIN、RIGHT JOIN、INNER JOIN。

                • LEFT JOIN會以左表爲基礎,包含左表的全部記錄,以及右表中匹配ON條件的記錄,對於未匹配的列,會以NULL表示。
                • LEFT JOIN會以右表爲基礎,包含左表的全部記錄,以及右表匹配ON條件的記錄,對於未匹配的列,會以NULL表示。
                • INNER JOIN,產生兩個表的交集(只包含知足ON條件的記錄)

                231634008872011

                INNER JOIN

                231634016379381

                FULL OUTER JOIN

                231634027315181

                231634045743650

                LEFT JOIN

                231634072774691

                RIGHT JOIN和LEFT JOIN相似。

                241947220904425

                2一、WHERE和HAVING的區別?

                • WHERE過濾的是行,HAVING過濾分組。
                • WHERE能完成的,均可以用HAVING(只是有時候不必)
                • WHERE在分組前對數據進行過濾,HAVING在分組後對數據進行過濾
                • WHERE後不能接聚合函數,HAVING後面一般都有聚合函數

                2二、SQL注入是什麼,如何防止?

                所謂SQL注入式攻擊,就是攻擊者把SQL命令插入到Web表單的輸入域或頁面請求的查詢字符串,欺騙服務器執行惡意的SQL命令。

                好比在登陸界面,若是用戶名填入'xxx' OR 1=1 --就能構造下面的SQL語句,由於OR 1=1,password被註釋掉,所以不管name和password填入什麼都能登陸成功。

                SELECT * FROM USER WHERE NAME='xxx' OR 1=1 -- and password='xxx';

                使用PrepareStatement,能夠防止sql注入攻擊,sql的執行須要編譯,注入問題之因此出現,是由於用戶填寫 sql語句參與了編譯。使用PrepareStatement對象在執行sql語句時,會分爲兩步,第一步將sql語句 "運送" 到mysql上預編譯,再回到java端拿到參數運送到mysql端。預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計劃,也就是說,後面你輸入的參數,不管你輸入的是什麼,都不會影響該sql語句的語法結構了。用戶填寫的 sql語句,就不會參與編譯,只會當作參數來看。從而避免了sql注入問題。

                SSM

                一、Spring有什麼好處(特性),怎麼管理對象的?

                • IOC:Spring的IOC容器,將對象之間的建立和依賴關係交給Spring,下降組件之間的耦合性。即Spring來控制對象的整個生命週期。其實就是日常說的DI或者IOC。
                • AOP:面向切面編程。能夠將應用各處的功能分離出來造成可重用的組件。核心業務邏輯與安全、事務、日誌等這些非核心業務邏輯分離,使得業務邏輯更簡潔清晰。

                • 使用模板消除了樣板式的代碼。好比使用JDBC訪問數據庫。
                • 提供了對像關係映射(ORM)、事務管理、遠程調用和Web應用的支持。

                Spring使用IOC容器建立和管理對象,好比在XML中配置了類的全限定名,而後Spring使用反射+工廠來建立Bean。BeanFactory是最簡單的容器,只提供了基本的DI支持,ApplicationContext基於BeanFactory建立,提供了完整的框架級的服務,所以通常使用應用上下文。

                二、什麼是IOC?

                IOC(Inverse of Control)即控制反轉。能夠理解爲控制權的轉移。傳統的實現中,對象的建立和依賴關係都是在程序進行控制的。而如今由Spring容器來統一管理、對象的建立和依賴關係,控制權轉移到了Spring容器,這就是控制反轉。

                三、什麼是DI?DI的好處是什麼?

                DI(Dependency Injection)依賴注入。對象的依賴關係由負責協調各個對象的第三方組件在建立對象的時候進行設定,對象無需自行建立或管理它們的依賴關係。通俗點說就是Spring容器爲對象注入外部資源,設置屬性值。DI的好處是使得各個組件之間鬆耦合,一個對象若是隻用接口來代表依賴關係,這種依賴能夠在對象絕不知情的狀況下,用不一樣的具體類進行替換。

                IOC和DI實際上是對同一種的不一樣表述

                四、什麼是AOP,AOP的好處?

                AOP(Aspect-Orientid Programming)面向切面編程,能夠將遍及在應用程序各個地方的功能分離出來,造成可重用的功能組件。系統的各個功能會重複出如今多個組件中,各個組件存在於核心業務中會使得代碼變得混亂。使用AOP能夠將這些多處出現的功能分離出來,不只能夠在任何須要的地方實現重用,還能夠使得核心業務變得簡單,實現了將核心業務與日誌、安全、事務等功能的分離。

                具體來講,散佈於應用中多處的功能被稱爲橫切關注點,這些橫切關注點從概念上與應用的業務邏輯是相分離的,可是又經常會直接嵌入到應用的業務邏輯中,AOP把這些橫切關注點從業務邏輯中分離出來。安全、事務、日誌這些功能均可以被認爲是應用中的橫切關注點。

                一般要重用功能,能夠使用繼承或者委託的方式。可是繼承每每致使一個脆弱的對像體系;委託帶來了複雜的調用。面向切面編程仍然能夠在一個地方定義通用的功能,可是能夠用聲明的方法定義這個功能要在何處出現,而無需修改受到影響的類。橫切關注點能夠被模塊化爲特殊的類,這些類被稱爲切面(Aspect)。好處在於:

                • 每一個關注點都集中在一個地方,而非分散在多處代碼中;
                • 使得業務邏輯更簡潔清晰,由於這樣能夠只關注核心業務,次要的業務被分離成關注點轉移到切面中了。

                AOP術語介紹

                通知:切面所作的工做稱爲通知。通知定義了切面是什麼,以及在什麼時候使用。Spring切面能夠應用5種類型的通知

                • 前置通知(Before):在目標方法被調用之間調用通知功能;
                • 後置通知(After):在目標方法被調用或者拋出異常以後調用通知功能;
                • 返回通知(After-returning):在目標方法成功執行以後調用通知;
                • 異常通知(After-throwing):在目標方法拋出異常以後調用通知;
                • 環繞通知(Around):通知包裹了被通知的方法,在目標方法被調用以前和調用以後執行自定義的行爲。

                鏈接點:能夠被通知的方法

                切點:實際被通知的方法

                切面:即通知和切點的結合,它是什麼,在什麼時候何處完成其功能。

                引入:容許向現有的類添加新方法或屬性,從而能夠在無需修改這些現有的類狀況下,讓它們具備新的行爲和狀態。

                織入:把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期裏有多個點能夠進行織入:

                • 編譯期,切面在目標類編譯時織入。
                • 類加載期,切面在目標類加載到JVM時被織入。
                • 運行期,切面在應用運行的某個時刻被織入,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象。Spring AOP就是以這種方式織入切面的

                Spring AOP構建在動態代理基礎之上,因此Spring對AOP的支持僅限於方法攔截。

                Spring的切面是由包裹了目標對象的代理類實現的。代理類封裝了目標類,並攔截被通知方法的調用,當代理攔截到方法調用時,在調用目標bean方法以前,會執行切面邏輯。其實切面只是實現了它們所包裝bean相同接口的代理

                五、AOP的實現原理:Spring AOP使用的動態代理。

                Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理經過反射來接收被代理的類,而且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。

                若是目標類沒有實現接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,能夠在運行時動態的生成某個類的子類,注意,CGLIB是經過繼承的方式作的動態代理,所以若是某個類被標記爲final,那麼它是沒法使用CGLIB作動態代理的。

                Spring使用動態代理,代理類封裝了目標類,當代理攔截到方法調用時,在調用目標bean的方法以前,會執行切面邏輯。

                六、Spring的生命週期?

                Spring建立、管理對象。Spring容器負責建立對象,裝配它們,配置它們並管理它們的整個生命週期。

                • 實例化:Spring對bean進行實例化
                • 填充屬性:Spring將值和bean的引用注入到bean對應的屬性中
                • 調用BeanNameAware的setBeanName()方法:若bean實現了BeanNameAware接口,Spring將bean的id傳遞給setBeanName方法
                • 調用BeanFactoryAware的setBeanFactory()方法:若bean實現了BeanFactoryAware接口,Spring調用setBeanFactory方法將BeanFactory容器實例傳入
                • 調用ApplicationContextAware的setApplicationContext方法:若是bean實現了ApplicationContextAware接口,Spring將調用setApplicationContext方法將bean所在的應用上下文傳入
                • 調用BeanPostProcessor的預初始化方法:若是bean實現了BeanPostProcessor,Spring將調用它們的叛postProcessBeforeInitialization方法
                • 調用InitalizingBean的afterPropertiesSet方法:若是bean實現了InitializingBean接口,Spring將調用它們的afterPropertiesSet方法
                • 若是bean實現了BeanPostProcessor接口,Spring將調用它們的postProcessAfterInitialzation方法
                • 此時bean已經準備就緒,能夠被應用程序使用,它們將一直駐留在應用殺死那個下文中,直到該應用的上下文被銷燬。
                • 若是bean實現了DisposableBean接口,Spring將調用它的destroy方法。

                七、Spring的配置方式,如何裝配bean?bean的注入方法有哪些?

                • XML配置,如<bean id="">
                • Java配置即JavaConfig,使用@Bean註解
                • 自動裝配,組件掃描(component scanning)和自動裝配(autowiring),@ComponentScan@AutoWired註解

                bean的注入方式有:

                • 構造器注入
                • 屬性的setter方法注入

                推薦對於強依賴使用構造器注入,對於弱依賴使用屬性注入。

                八、bean的做用域?

                • 單例(Singleton):在整個應用中,只建立bean一個實例。
                • 原型(Prototype):每次注入或經過Spring應用上下文獲取時,都會建立一個新的bean實例。
                • 會話(Session):在Web應用中,爲每一個會話建立一個bean實例。
                • 請求(Request):在Web應用中,爲每一個請求建立一個bean實例。

                默認狀況下Spring中的bean都是單例的。

                九、Spring中涉及到哪些設計模式?

                • 工廠方法模式。在各類BeanFactory以及ApplicationContext建立中都用到了;
                • 單例模式。在建立bean時用到,Spring默認建立的bean是單例的;
                • 代理模式。在AOP中使用Java的動態代理;
                • 策略模式。好比有關資源訪問的Resource類
                • 模板方法。好比使用JDBC訪問數據庫,JdbcTemplate。
                • 觀察者模式。Spring中的各類Listener,如ApplicationListener
                • 裝飾者模式。在Spring中的各類Wrapper和Decorator
                • 適配器模式。Spring中的各類Adapter,如在AOP中的通知適配器AdvisorAdapter

                十、MyBatis和Hibernate的區別和應用場景?

                Hibernate :是一個標準的ORM(對象關係映射) 框架; SQL語句是本身生成的,程序員不用本身寫SQL語句。所以要對SQL語句進行優化和修改比較困難。適用於中小型項目。

                MyBatis: 程序員本身編寫SQL, SQL修改和優化比較自由。 MyBatis更容易掌握,上手更容易。主要應用於需求變化較多的項目,如互聯網項目等。

                海量數據處理

                首先要了解幾種數據結構和算法:

                • HashMap,記住對於同一個鍵,哈希出來的值必定是同樣的,不一樣的鍵哈希出來也可能同樣,這就是發生了衝突(或碰撞)。
                • BitMap,能夠當作是bit數組,數組的每一個位置只有0或1兩種狀態。Java中能夠使用int數組表示位圖,arr[0]是一個int,一個int是32位,故能夠表示0-31的數,同理arr[1]可表示32-63...實際上就是用一個32位整型表示了32個數。
                • 大/小根堆,O(1)時間可在堆頂獲得最大值/最小值。利用小根堆可用於求Top K問題。
                • 布隆過濾器。使用長度爲m的bit數組和k個Hash函數,某個鍵通過k個哈希函數獲得k個下標,將k個下標在bit數組中對應的位置設置爲1。對於每一個鍵都重複上述過程,獲得最終設置好的布隆過濾器。對於新來的鍵,使用一樣的過程,獲得k個下標,判斷k個下標在bit數組中的值是否爲1,如有一個不爲1,說明這個鍵必定不在集合中。若全爲1,也可能不在集合中。就是說:查詢某個鍵,判斷不屬於該集合是絕對正確的;判斷屬於該集合是低機率錯誤的。由於多個位置的1多是由不一樣的鍵散列獲得。

                對上億個無重複數字的排序,或者找到沒有出現過數字,注意由於無重複數字,而BitMap的0和1正好能夠表示該數字有沒有出現過。若是要求更小的內存,能夠先分出區間,對落入區間的進行計數。必然有的區間數量未滿,再遍歷一次數組,只看該區間上的數字,使用BitMap,遍歷完成後該區間中必然有沒被設置成0的的地方,這些地方就是沒出現的數。

                數據在小範圍內波動,好比人類年齡,並且數據容許重複,可用計數排序處理數值排序或查找沒有出現過的值,計數的桶中頻次爲0的就是沒有出現過的數。

                數據是數字,要找最大的Top K,直接用大小爲K的小根堆,不斷淘汰最小元素便可。

                數據是數字或非數字,要找頻次最高的Top K。可以使用HashMap統計頻次,統計出頻次最大的前K個便可。統計頻次選出前K的過程能夠用小根堆。還能夠用Hash分流的方法,即用一個合適的hash函數將數據分到不一樣的機器或者文件中,由於對於一樣的數據,因爲hash函數的性質,必然被分配到同一個文件中,所以不存在相同的數據分佈在不一樣的文件這種狀況。對每一個文件採用HashMap統計頻次,用小根堆選出Top K,而後彙總所有文件,從全部部分結果的Top K中再利用小根堆獲得最終的Top K。

                查找數值的排名,好比找到中位數。好比將數劃分區間,對落入每一個區間的數進行計數。而後能夠得知中位數落在哪一個區間,再遍歷全部數,此次只關心落在該區間的數,不劃分區間的對其進行計數,就能夠找出中位數。

相關文章
相關標籤/搜索