淺談Java容器及原理

學java其實學了很久了,但其實一直對容器沒有一個深入的瞭解。瞭解的東西其實都是最簡單的、常用的一些方法。

在這裏我將對java的容器做一個系統的,比較淺顯的回顧。包括容器的類型、容器的底層實現原理、具體的實例。

一、容器的原理與底層

容器:Collection收集,集合。本身也是一個對象,只不過它可以容納其他的對象。前面比較典型的容器是數組Array[]

在這裏我們主要要關注以下幾個接口:Collection(容器)、Map(鍵值對)


Collection接口:定義了存取一組對象的方法,子接口Set、List、接口
Set:無序不可重複,若重複則後面的值會把前面的覆蓋掉,子類Hashset
List:有序可重複

Map接口:(注意Map不是繼承Collection的)單獨的接口,鍵值對,子類有HashMap、TreeMap,允許值重複,鍵不能重複

Collection接口:size、isEmpty、contains、iterator、toArray、add、remove、containsAll(是否包含另一個集合裏的所有對象)、addAll、equals、hashCode

1、List的底層實現:數組實現,它的特點是每個元素都有索引,因此優點也顯而易見就是查詢速度快。但是修改刪除插入效率低。由於刪除和插入會引起元素的移動,因此效率低下。

自定義ArrayList,實現list 的獲取、增加、刪除元素。Arraylist由於是數組實現,因此在擴容的時候採取的是將容量擴大到兩倍。

import java.util.ArrayList;
import java.util.Date;

public class MyArrayList {
    private Object[] element;
    private int size;

    public MyArrayList(){
        this(10);
    }
    public MyArrayList(int capacity){
        if(capacity<0){
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        element = new Object[capacity];
    }
    public void add(Object e){
//        數組擴容
        if(size == element.length){
            Object[] newArr = new Object[size*2+1];
            System.arraycopy(element,0 ,newArr,0,element.length);
            element = newArr;
        }
        element[size++] = e;

    }
    public Object get(int index){
        changeIndex(index);
        return element[index];
    }
    public void remove(int index){
        if(index <0 || index >=size){
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //需要移動元素 ,未寫完
    }
    public void changeIndex(int index){
        if(index<0||index>=size){
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        MyArrayList ml=new MyArrayList();
        ArrayList l = new ArrayList();
        ml.add("1");
        ml.add(new Date());
        System.out.println(ml.get(1));
    }
}

2、LinkedList原理+自定義實現

採用雙向鏈表實現,他的特點是採用鏈表的原理,因此查詢較List更慢,因爲它沒有索引,因此必須遍歷整個鏈表,插入修改刪除效率較高,無需移動元素。

自定義實現add、remove、get方法

public class MyLinkedList {
    private Node first;
    private Node last;
    private int size;

    public MyLinkedList() {

    }
    public void add(Object e){
        if(null == first){
            Node n = new Node();
            n.setPre(null);
            n.setObj(e);
            n.setNext(null);
            first = n;
            last = n;
        }else{
            Node n = new Node();
            n.setPre(last);
            n.setObj(e);
            n.setNext(null);

            last.setNext(n);

            last = n;
        }

        size++;
    }
    public int size(){
        return size;
    }

    public Object get(int index){
        Node current =null;
        if(null==first ){

        }else{
            changeIndex(index);
            current = first;
            for(int i =0;i<index;i++){
                current = current.next;

            }
            return current.obj;
        }
        return current;
    }
    public void remove(int index){
        changeIndex(index);
        Node current = node(index);

        if(current!=null){
            Node up = current.pre;
            Node down = current.next;
            up.next = down;
            down.pre = up;
            size --;
        }
    }
    public void changeIndex(int index){
        if(index<0||index>=size){
            try {
                throw new Exception();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public Node node(int index){
        Node current =null;

        changeIndex(index);
        current = first;
        for(int i =0;i<index;i++){
            current = current.next;

        }
        return current;
    }
    public static void main(String[] args) {
        MyLinkedList ml= new MyLinkedList();
        ml.add(3);
        ml.add(4);
        ml.add(5);
//        System.out.println(ml.get(1));
        ml.remove(2);
        System.out.println(ml.size);
    }

}
public class Node {
    Node pre;
    Object obj;
    Node next;
    public Node(){

    }
    public Node(Node pre, Object obj, Node next) {
        this.pre = pre;
        this.obj = obj;
        this.next = next;
    }
    public Object getPre() {
        return pre;
    }
    public void setPre(Node pre) {
        this.pre = pre;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public Object getObj() {
        return obj;
    }
    public void setObj(Object obj) {
        this.obj = obj;
    }
    public Object getNext() {
        return next;
    }
}
3、Vector:

Vector:線程安全的,效率低。前兩者線程不安全,效率高

我們需要掌握手寫ArrayList的add、get、remove方法。

4、Hashmap

Hashmap在JDK1.7以前是採用數組+鏈表的形式實現的,數組存儲hashcode的地址,使用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。但是當位於一個數組中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。

JDK1.8中,HashMap採用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查找時間。

簡單說一下原理,當我們在調用hashmap的add方法,我們需要先計算插入的key的hash值,來判斷它屬於數組的哪一個位置,比如計算出來的hash值是1,那麼就存在arr[1]這個位置,但是hash值可能會重複,那麼重複的這個元素也會被放在arr[1]中,這時候建立鏈表,將同一hash值的元素放入鏈表中,但當鏈表長度過大時,效率又降低了,因此當它的長度超過某一個閾值的時候,將鏈表轉換爲紅黑樹提高效率。

圖如下:


兩個內容相同的對象的hashcode一定相等,兩個key相等,則hash碼一定相等,反之不然。hashcode重寫,則equals也必須重寫。保證兩個內容相同的對象是一樣的。hashmap裏面的鍵不能重複的原理是覆蓋,如果有重複則覆蓋掉了

自定義Map實現get和put(初級版本):

import java.util.HashMap;
import java.util.LinkedList;
/**
 * 自定義map的升級版
 * 提高查詢的效率
 */
public class Mymap01 {
//    myEntry[] arr = new myEntry[999];
    LinkedList[] arr = new LinkedList[999];//map的底層機構  數組+鏈表
    int size;

    public void put(Object key,Object value)
    {
        myEntry m = new myEntry(key,value);
        int a = key.hashCode()%999;
        if (null == arr[a]){
            LinkedList list = new LinkedList();
            arr[a] = list;
            list.add(m);
        }else{
            //需要做判斷,避免key重複
            LinkedList list= arr[a];
            for(int i =0;i<list.size();i++){
                myEntry e=(myEntry) list.get(i);
                if(e.key.equals(key)){
                    e.value = value;
                    return;
                }
            }
            arr[a].add(m);
        }

    }

    public Object get(Object key){
        int a = key.hashCode()%arr.length;
        if (null != arr[a]){
            LinkedList list = arr[a];
            for(int i =0;i<list.size();i++){
                myEntry e=(myEntry) list.get(i);
                if(e.key.equals(key)){
                    return e.value;
                }
            }

        }
        return null;
    }

    public static void main(String[] args) {
        Mymap01 map = new Mymap01();
        map.put("我","白");
        map.put("你","黑");
        System.out.println(map.get("我"));
        map.put("name",2);
        HashMap map1 = new HashMap();
    }
}

5、Set

底層是通過map實現的。無序集合,沒有重複元素,這是利用map的鍵不重複的原理,重複則覆蓋

/**
 * @author lively
 * 自己定義hashSet,不能有重複元素
 */

public class MySet {

    HashMap map;
    int size;

    private static final Object PRESENT = new Object();

    public int size(){
        return map.size();
    }
    public MySet(){
        map = new HashMap();
    }
    public void add(Object obj){
        map.put(obj,PRESENT);//Set的不可重複就是利用了hashMap裏面的鍵不可重複
        size++;
    }
    public void remove(Object obj){

    }
    public static void main(String[] args) {
//        Set s = new HashSet();
//        s.add("1");
//        s.add("2");
//        s.add("1");
//        Iterator it = s.iterator();
//        System.out.println(s.size());
//        while(it.hasNext()){
//            System.out.println(it.next());
//        }
        MySet mySet = new MySet();
        mySet.add("name");
        mySet.add("name");
        mySet.add(new String("name"));
        mySet.add(new String("name"));
        System.out.println(mySet.size());

    }
}

6、map裏面最重要的一個方法:Iterator

Iterator迭代器遍歷Map和Set,所有實現了Collection接口的容器類都有一個iterator方法以返回一個實現Iterator接口的對象。

使用迭代器遍歷List和map的兩個方法:for和while(後者用的居多)

三個方法:boolean hasNext,next方法,remove方法 自己實現

/**
 * 加入接口,提供方法
 */
public class MyIterator2 {
    String[] elem = {"a","b","c","d","e","f","g"};
    int size = elem.length;//簡化迭代器原理
    private int cursor=-1;

    public Iterator<String> iterator() {
        return new Iterator<String>() {
            @Override
            public boolean hasNext() {
                {
                    return (cursor +1!=size);
                }
            }

            @Override
            public String next() {
                cursor++;
                return elem[cursor];
            }
            public void remore(String e){

            }
        };
    }
    public static void main(String[] args) {
        MyIterator2 m = new MyIterator2();
        Iterator iterator = m.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

容器的原理和使用就說到這裏,重點要掌握容器的底層原理,自定義map、list的常用方法等。

未完待續。。。

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