Clyde學習筆記二(CoordIntMap)

CoordIntMap是一個基類爲Map數據結構,是存儲遊戲地圖場景數據的基礎數據結構,在應用中一共涉及到3個類,第一個自然是CoordIntMap,另外還有Coord和Cell,Cell定義在CoordIntMap中,是一個內部類。

 

Coord用一個int來表示和存儲一個2D的座標,在存儲和表示之前分別需要encode和decode。

    /**
     * Encodes the supplied coordinates (presumed to be in [-32768, +32767]) into a single
     * integer.
     */
    public static int encode (int x, int y)
    {
        return (x << 16) | (y & 0xFFFF);
    }

    /**
     * Extracts the x component from the specified encoded coordinate pair.
     */
    public static int decodeX (int pair)
    {
        return pair >> 16;
    }

    /**
     * Extracts the y component from the specified encoded coordinate pair.
     */
    public static int decodeY (int pair)
    {
        return (pair << 16) >> 16;
    }

 一個int類型有4個字節,前2個字節表示x座標,後2個字節表示y座標。取值範圍從-32768一直到 +32767。

 

對於CoordIntMap,我們可以從它的定義中看到下面這2個接口,x、y是一個2D的座標,顯然CoordIntMap是一個以x、y2D座標爲key來存取對應的int值的這樣一個map結構。

    /**
     * Retrieves the value at the specified coordinates.
     */
    public int get (int x, int y)
    {
       ...
    }

    /**
     * Sets the value at the specified coordinates.
     *
     * @return the previously stored value.
     */
    public int put (int x, int y, int value)
    {
        ....
    }

 但是它的內部存儲是通過下面這個map來實現的

protected HashMap<Coord, Cell> _cells = new HashMap<Coord, Cell>();

 

 

可以看到,對外的接口與內部的存儲方式並不一致,那麼它是如何來轉換這兩者之間的差異的呢?

我們先來看一下CoordIntMap的構造函數:

    /**
     * Creates a new coord int map.
     *
     * @param granularity the size of the top-level cells, expressed as a power of two (e.g.,
     * 3 for a cell size of 8 by 8).
     * @param empty the value indicating the absence of an entry.
     */
    public CoordIntMap (int granularity, int empty)
    {
        _granularity = granularity;
        _empty = empty;
        initTransientFields();
    }

 

我們注意一下granularity這個參數,它的中文意思是粒度,在這裏是2的冪指數,用來指定Cell的矩陣大小,granularity值越大,Cell內包含的元素越多,粒度越小。


Cell的構造函數:

        /**
         * Creates a new cell.
         */
        public Cell ()
        {
            _values = new int[1 << _granularity << _granularity];
            Arrays.fill(_values, _empty);
        }

 1 << _granularity << _granularity這個表達式的數學含義是4的_granularity次方。默認_granularity=3,初始化一個8x8的數組,並且初始化成空值。

 

Cell內部是用一個一維數組來表示和存儲數據的,也就是說最終的數據都是存儲在下面這個數組內的。

        /** The values in the cell. */
        protected int[] _values;
 

那麼,CoordIntMap是如何通過x、y這組2D座標映射到_values數組中的值呢?我們來看一下get方法的具體實現:

 

    /**
     * Retrieves the value at the specified coordinates.
     */
    public int get (int x, int y)
    {
        Cell cell = getCell(x, y);
        return (cell == null) ? _empty : cell.get(x & _mask, y & _mask);
    }

    /**
     * Returns the cell corresponding to the specified coordinates.
     */
    protected Cell getCell (int x, int y)
    {
        _coord.set(x >> _granularity, y >> _granularity);
        return _cells.get(_coord);
    }
 

 

首先,x、y被映射成一個Coord,然後根據這個Coord的值在內部map中找到cell,最後再從cell中得到值。這裏最重要的就是對x、y的映射算法,這也是整個數據結構的核心。

 

x >> _granularity, y >> _granularity翻譯成數學語言是x除以2的_granularity次方,y除以2的_granularity次方。還記得前面提到過的對Cell構造函數的解釋嗎?每一個Cell都是一個2的_granularity次方 x(乘) 2的_granularity次方的結構,那麼這裏對x、y的映射是用來計算x、y所對應的value所在的Cell的座標

 

在得到Cell後如何再取得最終的值呢?在理解cell.get(x & _mask, y & _mask)這個表達式之前我們先來看下CoordIntMap構造函數中的調用的initTransientFields()這個方法。

 

    /**
     * Initializes the transient fields.
     */
    protected void initTransientFields ()
    {
        _mask = (1 << _granularity) - 1;
    }

 這個方法用來計算mask的值,我們剛纔已經看到,這個mask在我們從Cell中取值會被用到。那麼,在這裏的這個表達式(1 << _granularity) - 1 我們已經比較熟悉了,數學含義就是2的_granularity次方-1。如果:

 

_granularity = 2, 那麼1 << _granularity) - 1 = 3, 2進制值爲 11
_granularity = 3, 那麼1 << _granularity) - 1 = 7, 2進制值爲 111
...
 

我們回過頭再來看表達式cell.get(x & _mask, y & _mask),分別用mask對x、y做和的位運算,其數學含義等同於分別用2的_granularity次方除x、y取餘,這樣其實得到了x、y映射到Cell中的座標。

 

爲了更好的理解,特意編輯了2個實例示意圖,相信大家都能理解。

 


 

既然CoordIntMap它是一個Map,那麼它必然要提供一個接口供調用者來遍歷每一個entry。CoordIntMap對外提供了這樣的一個方法,它用一個Iterator來遍歷內部的數據。

 

    /**
     * Returns a set view of the map entries.
     */
    public Set<CoordIntEntry> coordIntEntrySet ()
    {
        return new AbstractSet<CoordIntEntry>() {
            public Iterator<CoordIntEntry> iterator () {
                return new Iterator<CoordIntEntry>() {
                	...
                };
            }
            public int size () {
                return _size;
            }
        };
    }

 

Iterator的定義:

 

public Iterator<CoordIntEntry> iterator () {
    return new Iterator<CoordIntEntry>() {
				...
        public CoordIntEntry next () {
            checkConcurrentModification();
            if (_centry == null) {
                _centry = _cit.next();
            }
            while (true) {
                int[] values = _centry.getValue().getValues();
                for (; _idx < values.length; _idx++) {
                    int value = values[_idx];
                    if (value != _empty) {
                        Coord coord = _centry.getKey();
                        _dummy.getKey().set(
                            (coord.x << _granularity) | (_idx & _mask),
                            (coord.y << _granularity) | (_idx >> _granularity));
                        _dummy._values = values;
                        _dummy._idx = _idx;
                        _idx++;
                        _count++;
                        return _dummy;
                    }
                }
                _centry = _cit.next();
                _idx = 0;
            }
        }
				...
        protected Iterator<Entry<Coord, Cell>> _cit = _cells.entrySet().iterator();
        protected Entry<Coord, Cell> _centry;
        protected int _idx;
        protected int _count;
        protected int _omodcount = _modcount;
        protected CoordIntEntry _dummy = new CoordIntEntry();
    };

 

 這裏只截取了next方法,展示了跟前相反的操作過程,即通過內部的存儲數據來取得最初的x、y的值。這裏最關鍵的兩個表達式:

(coord.x << _granularity) | (_idx & _mask)

(coord.y << _granularity) | (_idx >> _granularity)

這兩個表達式分別取得x、y的值然後存儲爲dummy中的key,這裏的key雖然也是Coord類型,但含義跟我們前邊看到的那個Coord完全不同,存儲的直接是x、y的值

 

        /** The coordinate key. */
        protected Coord _key = new Coord();

 

 如果我們跑一下下面的這段代碼

 

		CoordIntMap map = new CoordIntMap();
		map.put(5, 7, 112);
		map.put(1, 3, 57);
		map.put(8, 6, 33);
		Set<CoordIntEntry> set = map.coordIntEntrySet();
		for (CoordIntEntry entry : set) {
			System.out.println("key: " + entry.getKey());
			System.out.println("value: " + entry.getValue());
		}

 可以預期得到的輸出爲key: [1, 3]

 

value: 57
key: [5, 7]
value: 112
key: [8, 6]
value: 33

 

備註:本文中涉及到的位運算及其含義

1 << _granularity << _granularity

4的_granularity次方

 

x >> _granularity, y >> _granularity

x除以2的_granularity次方,y除以2的_granularity次方

 

 

(1 << _granularity) - 1

2的_granularity次方-1

 

 

x & _mask, y & _mask

2的_granularity次方除x、y取餘

 

(coord.x << _granularity) | (_idx & _mask)

coord.x乘以2的_granularity次方加上_idx除以_mask取餘

 

(coord.y << _granularity) | (_idx >> _granularity)

coord.y乘以2的_granularity次方加上_idx除以2的_granularity次方取整

 

 

後記:

CoordIntMap的實現中大量使用了位運算,這樣做的目的主要是爲了提高效率,畢竟這是一個基礎性的數據結構,特別是在處理地圖場景等大數據量的時候,效率還是比較關鍵的。其實類似的用法我們在JDK中也能看到,比如在HashMap中,通過hashcode定位數組index的取餘方法JDK中是這樣實現的:

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
 

在這裏,當length爲2的冪時,h & (length-1)等價於h % length,但是前者的效率要高於後者。怎麼樣,這裏的length - 1和CoordIntMap中的mask是否有異曲同工之妙呢?

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