Arrays.equals與ArraysSupport.vectorizedMismatch方法

博客發(fā)表于:Ghamster Blog
轉(zhuǎn)載請(qǐng)注明出處

概述

  • Arrays類是Java中用于數(shù)組操作的工具類,實(shí)現(xiàn)了比較臂港、復(fù)制森枪、查找和排序等常用操作
  • equals方法用于判斷數(shù)組是否相等视搏,包含了各種基本類型和Object類型的重載版本,對(duì)于Object[]會(huì)判斷對(duì)象非空疲恢,并調(diào)用Object.equals判斷對(duì)象是否相等
  • ArraysSupport類是jdk11(似乎是在jdk9引入)的工具類凶朗,提供了mismatch方法和vectorizedMismatchmismatch方法可以返回兩個(gè)被判定的數(shù)組對(duì)象中显拳,首個(gè)不相等元素的下標(biāo)
  • jdk11中使用mismatch方法修改了equals方法的實(shí)現(xiàn)邏輯

Arrays類的equals方法實(shí)現(xiàn)

jdk8中棚愤,數(shù)組判等的邏輯是:遍歷數(shù)組中的元素,依次對(duì)每個(gè)元素判等
int[]為例杂数,源碼如下:

    public static boolean equals(int[] a, int[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;

        return true;
    }

jdk11中宛畦,修改了所有用于基本數(shù)據(jù)類型的數(shù)組的equals方法,使用mismatch方法實(shí)現(xiàn)
int[]為例揍移,源碼如下:

    public static boolean equals(int[] a, int[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;

        int length = a.length;
        if (a2.length != length)
            return false;

        return ArraysSupport.mismatch(a, a2, length) < 0;
    }

mismatch和vectorizedMismatch

為了探究jdk11equals的魔法次和,必須看看mismatch方法到底做了什么。mismatch方法也擁有各種基本數(shù)據(jù)類型數(shù)組的重載版本那伐,以int[]版本為例

mismatch方法

mismatch方法返回給定的兩個(gè)數(shù)組中踏施,首個(gè)不相同元素的下標(biāo),當(dāng)完全匹配(0到length-1)時(shí)罕邀,返回-1畅形,源碼如下:

    public static int mismatch(int[] a,
                               int[] b,
                               int length) {
        int i = 0;
        if (length > 1) {
            if (a[0] != b[0])
                return 0;
            i = vectorizedMismatch(
                    a, Unsafe.ARRAY_INT_BASE_OFFSET,
                    b, Unsafe.ARRAY_INT_BASE_OFFSET,
                    length, LOG2_ARRAY_INT_INDEX_SCALE);
            if (i >= 0)
                return i;
            i = length - ~i;
        }
        for (; i < length; i++) {
            if (a[i] != b[i])
                return i;
        }
        return -1;
    }

方法主要可以分為兩個(gè)部分:

  1. 調(diào)用vectorizedMismatch方法,對(duì)兩個(gè)數(shù)組對(duì)象匹配诉探,若方法返回正值日熬,則表示找到了未匹配的元素,返回值為該元素在數(shù)組中的索引肾胯;若方法返回負(fù)值竖席,則表示未找到不相等元素,但需要注意的是敬肚,vectorizedMismatch方法可能沒有處理完數(shù)組中的所有元素毕荐,對(duì)返回值取反,得到還需處理的數(shù)組元素個(gè)數(shù)
  2. 對(duì)vectorizedMismatch返回值取反艳馒,對(duì)剩余元素逐個(gè)驗(yàn)證

vectorizedMismatch

該方法標(biāo)注了 @HotSpotIntrinsicCandidate 注解东跪,即jvm可以有自己的優(yōu)化實(shí)現(xiàn)方式,所以...懂吧...?

該方法的核心思想是以long數(shù)據(jù)格式對(duì)比對(duì)象(不一定是數(shù)組)中的數(shù)據(jù)鹰溜,即每次循環(huán)處理數(shù)據(jù)長(zhǎng)度為8個(gè)字節(jié),而不考慮對(duì)象是long[]還是int[]short[]等丁恭。方法的源碼如下:

@HotSpotIntrinsicCandidate
    public static int vectorizedMismatch(Object a, long aOffset,
                                         Object b, long bOffset,
                                         int length,
                                         int log2ArrayIndexScale) {
        // assert a.getClass().isArray();
        // assert b.getClass().isArray();
        // assert 0 <= length <= sizeOf(a)
        // assert 0 <= length <= sizeOf(b)
        // assert 0 <= log2ArrayIndexScale <= 3

        int log2ValuesPerWidth = LOG2_ARRAY_LONG_INDEX_SCALE - log2ArrayIndexScale;
        int wi = 0;
        for (; wi < length >> log2ValuesPerWidth; wi++) {
            long bi = ((long) wi) << LOG2_ARRAY_LONG_INDEX_SCALE;
            long av = U.getLongUnaligned(a, aOffset + bi);
            long bv = U.getLongUnaligned(b, bOffset + bi);
            if (av != bv) {
                long x = av ^ bv;
                int o = BIG_ENDIAN
                        ? Long.numberOfLeadingZeros(x) >> (LOG2_BYTE_BIT_SIZE + log2ArrayIndexScale)
                        : Long.numberOfTrailingZeros(x) >> (LOG2_BYTE_BIT_SIZE + log2ArrayIndexScale);
                return (wi << log2ValuesPerWidth) + o;
            }
        }

        // Calculate the tail of remaining elements to check
        int tail = length - (wi << log2ValuesPerWidth);

        if (log2ArrayIndexScale < LOG2_ARRAY_INT_INDEX_SCALE) {
            int wordTail = 1 << (LOG2_ARRAY_INT_INDEX_SCALE - log2ArrayIndexScale);
            // Handle 4 bytes or 2 chars in the tail using int width
            if (tail >= wordTail) {
                long bi = ((long) wi) << LOG2_ARRAY_LONG_INDEX_SCALE;
                int av = U.getIntUnaligned(a, aOffset + bi);
                int bv = U.getIntUnaligned(b, bOffset + bi);
                if (av != bv) {
                    int x = av ^ bv;
                    int o = BIG_ENDIAN
                            ? Integer.numberOfLeadingZeros(x) >> (LOG2_BYTE_BIT_SIZE + log2ArrayIndexScale)
                            : Integer.numberOfTrailingZeros(x) >> (LOG2_BYTE_BIT_SIZE + log2ArrayIndexScale);
                    return (wi << log2ValuesPerWidth) + o;
                }
                tail -= wordTail;
            }
            return ~tail;
        }
        else {
            return ~tail;
        }
    }

首先簡(jiǎn)單看一下方法的入?yún)ⅲ?/p>

  • Object a, Object b:需要對(duì)比的對(duì)象
  • long aOffset, long bOffset:起始Offset曹动,即從對(duì)象的第幾個(gè)字節(jié)開始對(duì)比。比如本例中牲览,被mismatch方法調(diào)用時(shí)墓陈,傳入的參數(shù)為Unsafe.ARRAY_INT_BASE_OFFSET,通過debug發(fā)現(xiàn)在當(dāng)前平臺(tái)上該值為16,因?yàn)閺牡?6字節(jié)開始贡必,才是數(shù)組中的數(shù)據(jù)(0-15字節(jié)存儲(chǔ)的應(yīng)該是數(shù)組的length等信息)
  • length:需要對(duì)比的長(zhǎng)度兔港。該方法并不提供數(shù)組下標(biāo)越界檢查,因此length必須是合理的數(shù)值
  • log2ArrayIndexScale:數(shù)組下標(biāo)縮放仔拟,與length共同確定需要對(duì)比的字節(jié)長(zhǎng)度衫樊。以int[]為例,int型數(shù)據(jù)占4個(gè)字節(jié)利花,即1<<2科侈,所以log2ArrayIndexScale為2,方法需要處理的字節(jié)長(zhǎng)度為length*(1<<2)=4*length

方法操作邏輯如下(逐行炒事;接上節(jié)臀栈,以int[]為例):

  • 12行: log2ValuesPerWidth可以理解為相對(duì)縮放。LOG2_ARRAY_LONG_INDEX_SCALE長(zhǎng)整型數(shù)組下標(biāo)縮放為3挠乳,即長(zhǎng)8字節(jié)权薯;int[]縮放為2,即長(zhǎng)4字節(jié)睡扬;所以相對(duì)縮放為1
  • 13行:wi盟蚣,將對(duì)象視為long[]遍歷時(shí),當(dāng)前數(shù)組下標(biāo)
  • 14行: for循環(huán)威蕉,每次wi++刁俭,即每次循環(huán)步進(jìn)8個(gè)字節(jié)。length為數(shù)組包含的int數(shù)據(jù)個(gè)數(shù)韧涨,將素組視為long時(shí)牍戚,循環(huán)length>>log2ArrayIndexScale即可遍歷數(shù)組
  • 15行: bi數(shù)組下標(biāo)為wi的元素在long數(shù)組中的相對(duì)位置(字節(jié))
  • 16-17行: 調(diào)用UnSafe.getLongUnaligned方法,讀取一個(gè)long值。
    在Java中业岁,數(shù)據(jù)是對(duì)齊存放的岁诉,即long類型的數(shù)據(jù)存放時(shí),起始地址只能是8的整數(shù)倍第晰,int類型數(shù)據(jù)存放的起始地址只能是4的整數(shù)倍……
    getLongUnaligned方法顧名思義,可以讀取非對(duì)齊存放的數(shù)據(jù)彬祖。該方法接收一個(gè)Object對(duì)象和一個(gè)long類型的offset:若offset為8的整數(shù)倍茁瘦,即(offset & 7) == 0,則直接調(diào)用getLong返回長(zhǎng)整型數(shù)據(jù);若offset為4的整數(shù)倍储笑,則直接分別對(duì)offsetoffset+4調(diào)用getInt返回兩個(gè)整型數(shù)據(jù)甜熔,然后通過<<4&操作,將兩個(gè)int型數(shù)據(jù)合并成一個(gè)long突倍;若offset為2的整數(shù)倍……
  • 18-24行: 判定兩個(gè)數(shù)組的值av==bv是否成立腔稀,相同則繼續(xù)下一次循環(huán)盆昙;不同是使用^按位與或,并根據(jù)系統(tǒng)采用大端存儲(chǔ)還是小端存儲(chǔ)決定從頭(numberOfLeadingZeros)還是尾(numberOfTrailingZeros)找到第一個(gè)不匹配的位焊虏。然后根據(jù)log2ArrayIndexScale的值淡喜,還原這個(gè)位在原數(shù)組int[]中對(duì)應(yīng)的索引。LOG2_BYTE_BIT_SIZE值為3诵闭,表示一個(gè)字節(jié)共8位
  • 27-50行: 用于處理數(shù)組類型為byte[]char[]炼团,且剩余未處理長(zhǎng)度大于4字節(jié)(一個(gè)int)的情況,原理與前面類似涂圆。
    tail表示剩余未處理的元素個(gè)數(shù)们镜,由于UnSafe.getIntUnaligned只能一次處理4個(gè)字節(jié),因此可能剩余1-3個(gè)byte或1個(gè)char

性能測(cè)試

下面通過簡(jiǎn)單代碼測(cè)試一下相對(duì)于jdk8润歉,jdk11是否有性能的提升模狭,測(cè)試代碼如下:

package main.test;

import java.util.Arrays;
import java.util.Random;

public class TestLegacyArrays {

    private static Random r = new Random();

    private static class LegacyArrays {
        private LegacyArrays() {}

        public static boolean equals(int[] a, int[] a2) {
            if (a==a2)
                return true;
            if (a==null || a2==null)
                return false;

            int length = a.length;
            if (a2.length != length)
                return false;

            for (int i=0; i<length; i++)
                if (a[i] != a2[i])
                    return false;

            return true;
        }
    }

    private static void test(int size, int fill){
        int[] a = new int[size];
        Arrays.fill(a, fill);
        int[] b = new int[size];
        Arrays.fill(b, fill);

        boolean isEqual;
        long now, finish;
        System.out.println("=> TestLegacyArrays: array size "+size);

        now = System.nanoTime();
        isEqual = LegacyArrays.equals(a,b);
        finish = System.nanoTime();
        System.out.println("  LegacyArrays: "+isEqual+" time: "+(finish-now));

        now = System.nanoTime();
        isEqual = Arrays.equals(a,b);
        finish = System.nanoTime();
        System.out.println("  Arrays: "+isEqual+" time: "+(finish-now));
        System.out.println();
    }

    public static void main(String[] args) {
        
        test(10, r.nextInt());
        test(100, r.nextInt());
        test(1000, r.nextInt());
        test(10000, r.nextInt());
        test(100000, r.nextInt());

    }
}

代碼輸出如下:

=> TestLegacyArrays: array size 10
  LegacyArrays: true time: 373700
  Arrays: true time: 193700

=> TestLegacyArrays: array size 100
  LegacyArrays: true time: 2000
  Arrays: true time: 10200

=> TestLegacyArrays: array size 1000
  LegacyArrays: true time: 15400
  Arrays: true time: 186500

=> TestLegacyArrays: array size 10000
  LegacyArrays: true time: 148900
  Arrays: true time: 286800

=> TestLegacyArrays: array size 100000
  LegacyArrays: true time: 1233700
  Arrays: true time: 2424000


Process finished with exit code 0

emmmmmmmmmmmmmmm...................
所以這次的更新,或許是出于流式計(jì)算或者多線程或者其他因素的考量踩衩,又或者我的測(cè)試方法有問題嚼鹉?。驱富。锚赤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褐鸥,隨后出現(xiàn)的幾起案子线脚,更是在濱河造成了極大的恐慌,老刑警劉巖叫榕,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑侥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晰绎,警方通過查閱死者的電腦和手機(jī)寓落,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荞下,“玉大人伶选,你說我怎么就攤上這事〖饣瑁” “怎么了仰税?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抽诉。 經(jīng)常有香客問我肖卧,道長(zhǎng),這世上最難降的妖魔是什么掸鹅? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任塞帐,我火速辦了婚禮,結(jié)果婚禮上巍沙,老公的妹妹穿的比我還像新娘葵姥。我一直安慰自己,他們只是感情好句携,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布榔幸。 她就那樣靜靜地躺著,像睡著了一般矮嫉。 火紅的嫁衣襯著肌膚如雪削咆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天蠢笋,我揣著相機(jī)與錄音拨齐,去河邊找鬼。 笑死昨寞,一個(gè)胖子當(dāng)著我的面吹牛瞻惋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播援岩,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼歼狼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了享怀?” 一聲冷哼從身側(cè)響起羽峰,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎添瓷,沒想到半個(gè)月后梅屉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仰坦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年履植,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悄晃。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玫霎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妈橄,到底是詐尸還是另有隱情庶近,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布眷蚓,位于F島的核電站鼻种,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沙热。R本人自食惡果不足惜叉钥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一罢缸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧投队,春花似錦枫疆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扒披,卻和暖如春值依,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碟案。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工愿险, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蟆淀。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓拯啦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親熔任。 傳聞我的和親對(duì)象是個(gè)殘疾皇子褒链,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容