นากต้นฉบับกำลังเทียบลูกปัดสองแถวและชี้ไปยังคู่สีอำพันที่เป็นตำแหน่งแรกซึ่งไม่ตรงกัน

Java Arrays.mismatch: หา index แรกที่ข้อมูลสองชุดต่างกัน

พี่โชว์Engineering

Arrays.equals ตอบได้ว่าข้อมูลสองชุดเท่ากันหรือไม่ แต่เวลาตรวจ checkpoint, protocol frame หรือผลจาก serializer เรามักอยากรู้ด้วยว่า ต่างครั้งแรกตรงไหน Arrays.mismatch คืน index แรกที่ไม่ตรง หรือ -1 เมื่อทั้งค่าและความยาวเท่ากันครับ

WHY: index แรกช่วยไล่ต้นเหตุ

ให้นึกถึงลูกปัดสองแถว นากเดินเทียบคู่ซ้ายไปขวาและชี้คู่แรกที่สีต่าง จุดหลังจากนั้นอาจต่างต่อเป็นร้อย แต่ตำแหน่งแรกมักใกล้ต้นเหตุ เช่น header length ผิด, encoding เริ่มเหลื่อม หรือ serializer ใส่ field เพิ่ม

ถ้าทุกสมาชิกในช่วงร่วมเท่ากันแต่ความยาวต่าง ผลลัพธ์คือความยาวของ array ที่สั้นกว่า นี่ทำให้แยก “prefix ถูกแต่มีข้อมูลเกิน/ขาด” จาก “ค่าเสียกลางทาง” ได้โดยไม่เขียน loop เอง

HOW: สรุปความต่างโดยไม่ dump payload ทั้งก้อน

import java.util.Arrays;

public class Main {
    static String compare(byte[] expected, byte[] actual) {
        int index = Arrays.mismatch(expected, actual);
        if (index == -1) return "equal";
        if (index == expected.length) return "actual has extra bytes at " + index;
        if (index == actual.length) return "actual ends early at " + index;
        return "first different byte at " + index;
    }

    public static void main(String[] args) {
        assert compare(new byte[] {1, 2, 3}, new byte[] {1, 2, 3}).equals("equal");
        assert compare(new byte[] {1, 2, 3}, new byte[] {1, 9, 3})
                .equals("first different byte at 1");
        assert compare(new byte[] {1, 2}, new byte[] {1, 2, 3})
                .equals("actual has extra bytes at 2");
        assert compare(new byte[] {1, 2, 3}, new byte[] {1, 2})
                .equals("actual ends early at 2");
        System.out.println("Arrays.mismatch checks passed");
    }
}

ตัวอย่างตั้งใจ log เฉพาะตำแหน่ง ไม่ log byte จริง เพราะ payload อาจมี token, key หรือข้อมูลส่วนตัว ถ้าต้อง debug เพิ่ม ให้แสดง window ขนาดเล็กหลัง redact และทำเฉพาะ environment ที่สิทธิ์เหมาะสม

ช่วงย่อยและ index ที่คืน

Overload แบบ range รับ fromIndex/toIndex ของแต่ละ array และคืนตำแหน่ง สัมพัทธ์กับจุดเริ่มของช่วง ตามสัญญาในเอกสาร ไม่ควรเดาว่าเป็น absolute index ให้เขียน test ของ overload ที่ทีมใช้จริงและแปลงกลับด้วย start offset อย่างชัดเจน ตรวจช่วงก่อนเรียก เพราะ range ผิดจะโยน exception แทนค่าความต่าง

สำหรับ float[] และ double[] กติกา equality อิง semantics ที่เอกสาร Arrays กำหนด รวมรายละเอียดของ NaN และ signed zero ซึ่งอาจไม่ตรงกับ == ที่เราเขียนเอง ถ้างานมี tolerance เช่นผลคำนวณเชิงตัวเลข ไม่ควรใช้ mismatch เป็น approximate comparison ต้องเขียน policy epsilon/ULP ตามโดเมน

Performance และ concurrent mutation

การสแกนกรณีแย่สุดเป็น O(n) และหน่วยความจำเพิ่ม O(1) implementation อาจ optimize ตามชนิดและ JVM แต่ไม่ควรอ้าง SIMD หรือ throughput โดยไม่ benchmark บน JDK/เครื่องจริง งานที่เทียบบ่อยควรหลีกเลี่ยง copy payload เพียงเพื่อเรียกฟังก์ชันนี้

อย่าแก้ array จากอีก thread ระหว่างเทียบ ผลที่ได้ไม่มี snapshot guarantee และอาจชี้ตำแหน่งที่อธิบายสถานะใดสถานะหนึ่งไม่ได้ ให้ส่ง immutable snapshot หรือคุม ownership ก่อน หากเป้าหมายคือ security MAC/tag comparison อย่าใช้ Arrays.mismatch เพราะตำแหน่งและเวลาอาจรั่ว ใช้ constant-time verification API ของ primitive นั้น

Recap

  • -1 แปลว่าเท่าทั้งค่าและความยาว
  • ถ้า prefix เท่ากันแต่ยาวไม่เท่า index คือขอบของชุดสั้น
  • อย่า log payload ลับเพียงเพื่ออธิบาย mismatch
  • float/double tolerance ต้องใช้ policy เฉพาะ ไม่ใช่ mismatch
  • security tag ใช้ constant-time comparison ไม่ใช่ diff API

เริ่มใช้ใน assertion หรือ diagnostic ที่วันนี้บอกแค่ว่า “ไม่เท่ากัน” ครับ index แรกทำให้รายงานมีประโยชน์ขึ้นโดยไม่ต้องเปิดเผยข้อมูลทั้งก้อน