تحويل تمثيل سلسلة لتفريغ سداسي عشري إلى صفيف بايت باستخدام Java؟

StackOverflow https://stackoverflow.com/questions/140131

  •  02-07-2019
  •  | 
  •  

سؤال

أنا أبحث عن طريقة لتحويل سلسلة طويلة (من ملف تفريغ)، تمثل القيم السداسية إلى مصفوفة بايت.

لم أستطع أن أصيغها بشكل أفضل من الشخص الذي نشرها نفس السؤال هنا.

ولكن لكي أبقيه أصليًا، سأصيغه بطريقتي الخاصة:لنفترض أن لدي سلسلة "00A0BF" الذي أود تفسيره على أنه

byte[] {0x00,0xA0,0xBf}

ماذا علي أن أفعل؟

أنا مبتدئ في Java وانتهى بي الأمر باستخدام BigInteger واحترس من الأصفار السداسية البادئة.لكنني أعتقد أنه قبيح وأنا متأكد من أنني أفتقد شيئًا بسيطًا.

هل كانت مفيدة؟

المحلول

إليك الحل الذي أعتقد أنه أفضل من أي حل تم نشره حتى الآن:

public static byte[] hexStringToByteArray(String s) {
    int len = s.length();
    byte[] data = new byte[len / 2];
    for (int i = 0; i < len; i += 2) {
        data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                             + Character.digit(s.charAt(i+1), 16));
    }
    return data;
}

أسباب التحسن:

  • آمن مع الأصفار البادئة (على عكس BigInteger) ومع قيم البايت السالبة (على عكس Byte.parseByte)

  • لا يحول السلسلة إلى ملف char[], أو قم بإنشاء كائنات StringBuilder وString لكل بايت واحد.

  • لا توجد تبعيات مكتبة قد لا تكون متاحة

لا تتردد في إضافة التحقق من الوسيطة عبر assert أو استثناءات إذا لم تكن الوسيطة معروفة بأنها آمنة.

نصائح أخرى

بطانات واحدة:

import javax.xml.bind.DatatypeConverter;

public static String toHexString(byte[] array) {
    return DatatypeConverter.printHexBinary(array);
}

public static byte[] toByteArray(String s) {
    return DatatypeConverter.parseHexBinary(s);
}

تحذيرات:

  • في Java 9 Jigsaw ، لم يعد هذا جزءًا من مجموعة جافا.eckes)
  • غير متوفر على Android (بفضل Fabian لملاحظة ذلك)، ولكن يمكنك فقط خذ كود المصدر إذا كان النظام الخاص بك يفتقر javax.xml لسبب ما.شكرا ل @Bert Regelink لاستخراج المصدر .

يجب أن تقوم فئة Hex في برنامج الترميز العمومي بذلك نيابةً عنك.

http://commons.Apache.org/codec/

import org.apache.commons.codec.binary.Hex;
...
byte[] decoded = Hex.decodeHex("00A0BF");
// 0x00 0xA0 0xBF

يمكنك الآن استخدام BaseEncoding في guava لانجاز هذا.

BaseEncoding.base16().decode(string);

لعكس ذلك استخدم

BaseEncoding.base16().encode(bytes);

في الواقع، أعتقد أن BigInteger هو الحل الجميل جدًا:

new BigInteger("00A0BF", 16).toByteArray();

يحرر: غير آمن للأصفار البادئة, ، كما أشار الملصق.

ال HexBinaryAdapter يوفر القدرة على التنظيم وإلغاء التنظيم بين String و byte[].

import javax.xml.bind.annotation.adapters.HexBinaryAdapter;

public byte[] hexToBytes(String hexString) {
     HexBinaryAdapter adapter = new HexBinaryAdapter();
     byte[] bytes = adapter.unmarshal(hexString);
     return bytes;
}

هذا مجرد مثال كتبته...أنا في الواقع أستخدمه كما هو ولا أحتاج إلى إنشاء طريقة منفصلة لاستخدامه.

بطانات واحدة:

import javax.xml.bind.DatatypeConverter;

public static String toHexString(byte[] array) {
    return DatatypeConverter.printHexBinary(array);
}

public static byte[] toByteArray(String s) {
    return DatatypeConverter.parseHexBinary(s);
}

لأولئك منكم المهتمين بالرمز الفعلي وراء بطانات واحدة من كسوريةR (كنت بحاجة إلى ذلك نظرًا لأن javax.xml.bind غير متاح لنظام Android (افتراضيًا))، فهذا يأتي من com.sun.xml.internal.bind.DatatypeConverterImpl.java :

public byte[] parseHexBinary(String s) {
    final int len = s.length();

    // "111" is not a valid hex encoding.
    if( len%2 != 0 )
        throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);

    byte[] out = new byte[len/2];

    for( int i=0; i<len; i+=2 ) {
        int h = hexToBin(s.charAt(i  ));
        int l = hexToBin(s.charAt(i+1));
        if( h==-1 || l==-1 )
            throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);

        out[i/2] = (byte)(h*16+l);
    }

    return out;
}

private static int hexToBin( char ch ) {
    if( '0'<=ch && ch<='9' )    return ch-'0';
    if( 'A'<=ch && ch<='F' )    return ch-'A'+10;
    if( 'a'<=ch && ch<='f' )    return ch-'a'+10;
    return -1;
}

private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

public String printHexBinary(byte[] data) {
    StringBuilder r = new StringBuilder(data.length*2);
    for ( byte b : data) {
        r.append(hexCode[(b >> 4) & 0xF]);
        r.append(hexCode[(b & 0xF)]);
    }
    return r.toString();
}

إليك طريقة تعمل بالفعل (استنادًا إلى عدة إجابات شبه صحيحة سابقة):

private static byte[] fromHexString(final String encoded) {
    if ((encoded.length() % 2) != 0)
        throw new IllegalArgumentException("Input string must contain an even number of characters");

    final byte result[] = new byte[encoded.length()/2];
    final char enc[] = encoded.toCharArray();
    for (int i = 0; i < enc.length; i += 2) {
        StringBuilder curr = new StringBuilder(2);
        curr.append(enc[i]).append(enc[i + 1]);
        result[i/2] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
}

المشكلة الوحيدة المحتملة التي يمكنني رؤيتها هي ما إذا كانت سلسلة الإدخال طويلة للغاية؛يؤدي استدعاء toCharArray() إلى إنشاء نسخة من المصفوفة الداخلية للسلسلة.

يحرر:وبالمناسبة، يتم تسجيل البايتات في Java، لذا يتم تحويل سلسلة الإدخال الخاصة بك إلى [0، -96، -65] بدلاً من [0، 160، 191].ولكن ربما كنت على علم بذلك بالفعل.

في Android، إذا كنت تعمل مع Hex، فيمكنك المحاولة okio.

استخدام بسيط:

byte[] bytes = ByteString.decodeHex("c000060000").toByteArray();

وستكون النتيجة

[-64, 0, 6, 0, 0]

يحرر:كما أشارmmyers، فإن هذه الطريقة لا تعمل على الإدخال الذي يحتوي على سلاسل فرعية تتوافق مع البايتات ذات مجموعة البت العالية ("80" - "FF").الشرح موجود في معرف الخطأ:6259307 Byte.parseByte لا يعمل كما هو معلن في وثائق SDK.

public static final byte[] fromHexString(final String s) {
    byte[] arr = new byte[s.length()/2];
    for ( int start = 0; start < s.length(); start += 2 )
    {
        String thisByte = s.substring(start, start+2);
        arr[start/2] = Byte.parseByte(thisByte, 16);
    }
    return arr;
}

ال BigInteger() الطريقة من java.math بطيئة جدًا ولا ينصح بها.

Integer.parseInt(HEXString, 16)

يمكن أن يسبب مشاكل مع بعض الشخصيات دون التحويل إلى رقم / عدد صحيح

طريقة العمل الجيد:

Integer.decode("0xXX") .byteValue()

وظيفة:

public static byte[] HexStringToByteArray(String s) {
    byte data[] = new byte[s.length()/2];
    for(int i=0;i < s.length();i+=2) {
        data[i/2] = (Integer.decode("0x"+s.charAt(i)+s.charAt(i+1))).byteValue();
    }
    return data;
}

استمتع، حظا سعيدا

إن المدونة التي قدمها بيرت ريجيلينك لا تعمل ببساطة.حاول القيام بما يلي:

import javax.xml.bind.DatatypeConverter;
import java.io.*;

public class Test
{  
    @Test
    public void testObjectStreams( ) throws IOException, ClassNotFoundException
    {     
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);

            String stringTest = "TEST";
            oos.writeObject( stringTest );

            oos.close();
            baos.close();

            byte[] bytes = baos.toByteArray();
            String hexString = DatatypeConverter.printHexBinary( bytes);
            byte[] reconvertedBytes = DatatypeConverter.parseHexBinary(hexString);

            assertArrayEquals( bytes, reconvertedBytes );

            ByteArrayInputStream bais = new ByteArrayInputStream(reconvertedBytes);
            ObjectInputStream ois = new ObjectInputStream(bais);

            String readString = (String) ois.readObject();

            assertEquals( stringTest, readString);
        }
    }

للحصول على ما يستحق، إليك إصدار آخر يدعم السلاسل ذات الطول الفردي، دون اللجوء إلى تسلسل السلاسل.

public static byte[] hexStringToByteArray(String input) {
    int len = input.length();

    if (len == 0) {
        return new byte[] {};
    }

    byte[] data;
    int startIdx;
    if (len % 2 != 0) {
        data = new byte[(len / 2) + 1];
        data[0] = (byte) Character.digit(input.charAt(0), 16);
        startIdx = 1;
    } else {
        data = new byte[len / 2];
        startIdx = 0;
    }

    for (int i = startIdx; i < len; i += 2) {
        data[(i + 1) / 2] = (byte) ((Character.digit(input.charAt(i), 16) << 4)
                + Character.digit(input.charAt(i+1), 16));
    }
    return data;
}

لقد استخدمت دائمًا طريقة مثل

public static final byte[] fromHexString(final String s) {
    String[] v = s.split(" ");
    byte[] arr = new byte[v.length];
    int i = 0;
    for(String val: v) {
        arr[i++] =  Integer.decode("0x" + val).byteValue();

    }
    return arr;
}

تنقسم هذه الطريقة على قيم سداسية محددة بمسافات ولكن لن يكون من الصعب جعلها تقسم السلسلة بناءً على أي معايير أخرى مثل مجموعات من حرفين.

يعجبني حل Character.digit، ولكن إليك كيفية حله

public byte[] hex2ByteArray( String hexString ) {
    String hexVal = "0123456789ABCDEF";
    byte[] out = new byte[hexString.length() / 2];

    int n = hexString.length();

    for( int i = 0; i < n; i += 2 ) {
        //make a bit representation in an int of the hex value 
        int hn = hexVal.indexOf( hexString.charAt( i ) );
        int ln = hexVal.indexOf( hexString.charAt( i + 1 ) );

        //now just shift the high order nibble and add them together
        out[i/2] = (byte)( ( hn << 4 ) | ln );
    }

    return out;
}

لقد وجدت أن Kernel Panic هو الحل الأكثر فائدة بالنسبة لي، ولكنني واجهت مشاكل إذا كانت السلسلة السداسية رقمًا فرديًا.حلها بهذه الطريقة:

boolean isOdd(int value)
{
    return (value & 0x01) !=0;
}

private int hexToByte(byte[] out, int value)
{
    String hexVal = "0123456789ABCDEF"; 
    String hexValL = "0123456789abcdef";
    String st = Integer.toHexString(value);
    int len = st.length();
    if (isOdd(len))
        {
        len+=1; // need length to be an even number.
        st = ("0" + st);  // make it an even number of chars
        }
    out[0]=(byte)(len/2);
    for (int i =0;i<len;i+=2)
    {
        int hh = hexVal.indexOf(st.charAt(i));
            if (hh == -1)  hh = hexValL.indexOf(st.charAt(i));
        int lh = hexVal.indexOf(st.charAt(i+1));
            if (lh == -1)  lh = hexValL.indexOf(st.charAt(i+1));
        out[(i/2)+1] = (byte)((hh << 4)|lh);
    }
    return (len/2)+1;
}

أقوم بإضافة عدد من الأرقام السداسية إلى مصفوفة، لذلك أقوم بتمرير المرجع إلى المصفوفة التي أستخدمها، وقيمة int التي أحتاجها لتحويلها وإرجاع الموضع النسبي للرقم السداسي التالي.لذا فإن مصفوفة البايت النهائية تحتوي على [0] عدد الأزواج السداسية، [1...] أزواج سداسية، ثم عدد الأزواج...

بناءً على الحل الذي تم التصويت عليه، يجب أن يكون ما يلي أكثر كفاءة قليلاً:

  public static byte [] hexStringToByteArray (final String s) {
    if (s == null || (s.length () % 2) == 1)
      throw new IllegalArgumentException ();
    final char [] chars = s.toCharArray ();
    final int len = chars.length;
    final byte [] data = new byte [len / 2];
    for (int i = 0; i < len; i += 2) {
      data[i / 2] = (byte) ((Character.digit (chars[i], 16) << 4) + Character.digit (chars[i + 1], 16));
    }
    return data;
  }

لأن:يؤدي التحويل الأولي إلى مصفوفة char إلى توفير عمليات التحقق من الطول في charAt

إذا كان لديك تفضيل لتدفقات Java 8 كنمط الترميز الخاص بك، فيمكن تحقيق ذلك باستخدام أساسيات JDK فقط.

String hex = "0001027f80fdfeff";

byte[] converted = IntStream.range(0, hex.length() / 2)
    .map(i -> Character.digit(hex.charAt(i * 2), 16) << 4 | Character.digit(hex.charAt((i * 2) + 1), 16))
    .collect(ByteArrayOutputStream::new,
             ByteArrayOutputStream::write,
             (s1, s2) -> s1.write(s2.toByteArray(), 0, s2.size()))
    .toByteArray();

ال , 0, s2.size() يمكن حذف المعلمات الموجودة في وظيفة تسلسل المُجمِّع إذا كنت لا تمانع في التقاطها IOException.

public static byte[] hex2ba(String sHex) throws Hex2baException {
    if (1==sHex.length()%2) {
        throw(new Hex2baException("Hex string need even number of chars"));
    }

    byte[] ba = new byte[sHex.length()/2];
    for (int i=0;i<sHex.length()/2;i++) {
        ba[i] = (Integer.decode(
                "0x"+sHex.substring(i*2, (i+1)*2))).byteValue();
    }
    return ba;
}

الحل الرسمي الخاص بي:

/**
 * Decodes a hexadecimally encoded binary string.
 * <p>
 * Note that this function does <em>NOT</em> convert a hexadecimal number to a
 * binary number.
 *
 * @param hex Hexadecimal representation of data.
 * @return The byte[] representation of the given data.
 * @throws NumberFormatException If the hexadecimal input string is of odd
 * length or invalid hexadecimal string.
 */
public static byte[] hex2bin(String hex) throws NumberFormatException {
    if (hex.length() % 2 > 0) {
        throw new NumberFormatException("Hexadecimal input string must have an even length.");
    }
    byte[] r = new byte[hex.length() / 2];
    for (int i = hex.length(); i > 0;) {
        r[i / 2 - 1] = (byte) (digit(hex.charAt(--i)) | (digit(hex.charAt(--i)) << 4));
    }
    return r;
}

private static int digit(char ch) {
    int r = Character.digit(ch, 16);
    if (r < 0) {
        throw new NumberFormatException("Invalid hexadecimal string: " + ch);
    }
    return r;
}

هو مثل وظيفة PHP hex2bin() ولكن بأسلوب جافا.

مثال:

String data = new String(hex2bin("6578616d706c65206865782064617461"));
// data value: "example hex data"

إلى حد بعيد ليس الحل الأنظف.لكنه يعمل بالنسبة لي ومنسق بشكل جيد:

private String createHexDump(byte[] msg, String description) {
    System.out.println();
    String result = "\n" + description;
    int currentIndex = 0;
    for(int i=0 ; i<msg.length ; i++){
        currentIndex++;
        if(i == 0){
            result += String.format("\n  %04x ", i);
        }
        if(i % 16 == 0 && i != 0){
            result += " | ";
            for(int j=(i-16) ; j<msg.length && j<i ; j++) {
                char characterToAdd = (char) msg[j];
                if (characterToAdd == '\n') {
                    characterToAdd = ' ';
                }
                result += characterToAdd;
            }

            result += String.format("\n  %04x ", i);
        }

        result += String.format("%02x ", msg[i]);
    }

    if(currentIndex % 16 != 0){
        int fitIns = msg.length / 16;
        int leftOvers = msg.length - (fitIns * 16);
        for(int i=0 ; i<16-leftOvers ; i++){
            result += "   ";
        }

        result += " | ";

        for(int i=msg.length-leftOvers ; i<msg.length ; i++){
            char characterToAdd = (char) msg[i];
            if (characterToAdd == '\n') {
                characterToAdd = ' ';
            }
            result += characterToAdd;
        }
    }

    result += "\n";

    return result;
}

الإخراج:

  S -> C
    0000 0b 00 2e 06 4d 6f 72 69 74 7a 53 6f 6d 65 20 54  |  .Heyyy Some T
    0010 43 50 20 73 74 75 66 66 20 49 20 63 61 70 74 75  | CP stuff I captu
    0020 72 65 64 2e 2e 77 65 6c 6c 20 66 6f 72 6d 61 74  | red..well format
    0030 3f                                               | ?

لقد تأخرت في الحفلة، ولكنني قمت بدمج الإجابة أعلاه بواسطة DaveL في فصل دراسي مع الإجراء العكسي - فقط في حالة أن ذلك يساعد.

public final class HexString {
    private static final char[] digits = "0123456789ABCDEF".toCharArray();

    private HexString() {}

    public static final String fromBytes(final byte[] bytes) {
        final StringBuilder buf = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            buf.append(HexString.digits[(bytes[i] >> 4) & 0x0f]);
            buf.append(HexString.digits[bytes[i] & 0x0f]);
        }
        return buf.toString();
    }

    public static final byte[] toByteArray(final String hexString) {
        if ((hexString.length() % 2) != 0) {
            throw new IllegalArgumentException("Input string must contain an even number of characters");
        }
        final int len = hexString.length();
        final byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                    + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }
}

وفئة اختبار JUnit:

public class TestHexString {

    @Test
    public void test() {
        String[] tests = {"0FA1056D73", "", "00", "0123456789ABCDEF", "FFFFFFFF"};

        for (int i = 0; i < tests.length; i++) {
            String in = tests[i];
            byte[] bytes = HexString.toByteArray(in);
            String out = HexString.fromBytes(bytes);
            System.out.println(in); //DEBUG
            System.out.println(out); //DEBUG
            Assert.assertEquals(in, out);

        }

    }

}

أعلم أن هذا موضوع قديم جدًا، ولكني لا أزال أرغب في إضافة قيمة قرشي.

إذا كنت حقًا بحاجة إلى ترميز سلسلة سداسية عشرية بسيطة لتحويلها إلى محول ثنائي، فأنا أرغب في القيام بذلك على النحو التالي.

public static byte[] hexToBinary(String s){

  /*
   * skipped any input validation code
   */

  byte[] data = new byte[s.length()/2];

  for( int i=0, j=0; 
       i<s.length() && j<data.length; 
       i+=2, j++)
  {
     data[j] = (byte)Integer.parseInt(s.substring(i, i+2), 16);
  }

  return data;
}

أعتقد أنه سيفعل ذلك من أجلك.لقد قمت بتجميعها معًا من وظيفة مشابهة أعادت البيانات كسلسلة:

private static byte[] decode(String encoded) {
    byte result[] = new byte[encoded/2];
    char enc[] = encoded.toUpperCase().toCharArray();
    StringBuffer curr;
    for (int i = 0; i < enc.length; i += 2) {
        curr = new StringBuffer("");
        curr.append(String.valueOf(enc[i]));
        curr.append(String.valueOf(enc[i + 1]));
        result[i] = (byte) Integer.parseInt(curr.toString(), 16);
    }
    return result;
}

بالنسبة لي كان هذا هو الحل، HEX='FF01' ثم انقسم إلى FF(255) و01(01)

private static byte[] BytesEncode(String encoded) {
    //System.out.println(encoded.length());
    byte result[] = new byte[encoded.length() / 2];
    char enc[] = encoded.toUpperCase().toCharArray();
    String curr = "";
    for (int i = 0; i < encoded.length(); i=i+2) {
        curr = encoded.substring(i,i+2);
        System.out.println(curr);
        if(i==0){
            result[i]=((byte) Integer.parseInt(curr, 16));
        }else{
            result[i/2]=((byte) Integer.parseInt(curr, 16));
        }

    }
    return result;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top