在Java中将数组字符串转换为字符串并反向转换

10
我在Java中有一个String[]数组,必须先对其进行编码/转换为字符串,然后在代码中进一步将其转回String[]数组。问题是,字符串中可以包含任何字符,所以在编码时必须非常小心。并且解码所需的所有信息都必须包含在最终字符串中。我不能返回一个字符串和其他附加变量的信息。
到目前为止,我设计的算法如下:
1. 将所有字符串相连起来,例如: String[] a = {"lala", "exe", "a"} 转化为 String b = "lalaexea"
2. 在字符串末尾附加String[]中所有字符串的长度,由$符号与主文本分隔开,并用逗号分隔每个长度,如下所示: b = "lalaexea$4,3,1"
当将其转换回原始String[]数组时,我将首先从字符串末尾读取长度,然后根据长度读取实际的字符串。
但也许有更简单的方法?
4个回答

13

如果您不想花费太多时间进行字符串操作,可以使用Java序列化+commons codecs来实现:

public void stringArrayTest() throws IOException, ClassNotFoundException, DecoderException {
    String[] strs = new String[] {"test 1", "test 2", "test 3"};
    System.out.println(Arrays.toString(strs));

    // serialize
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    new ObjectOutputStream(out).writeObject(strs);

    // your string
    String yourString = new String(Hex.encodeHex(out.toByteArray()));
    System.out.println(yourString);

    // deserialize
    ByteArrayInputStream in = new ByteArrayInputStream(Hex.decodeHex(yourString.toCharArray()));
    System.out.println(Arrays.toString((String[]) new ObjectInputStream(in).readObject()));
}

这将返回以下输出:
[test 1, test 2, test 3]
aced0005757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b47020000787000000003740006746573742031740006746573742032740006746573742033
[test 1, test 2, test 3]

如果您正在使用maven,您可以使用以下依赖项来获取commons codec:
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.2</version>
</dependency>

建议使用base64(两行更改):

String yourString = new String(Base64.encodeBase64(out.toByteArray()));
ByteArrayInputStream in = new ByteArrayInputStream(Base64.decodeBase64(yourString.getBytes()));

在Base64的情况下,以下代码公开的结果字符串较短:

[test 1, test 2, test 3]
rO0ABXVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAN0AAZ0ZXN0IDF0AAZ0ZXN0IDJ0AAZ0ZXN0IDM=
[test 1, test 2, test 3]

关于每种方法的时间,我对每种方法进行了10^5次执行,结果如下:

  • 字符串操作:156毫秒
  • 十六进制:376毫秒
  • Base64编码:379毫秒

用于测试的代码:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.StringTokenizer;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;


public class StringArrayRepresentationTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException, DecoderException {

        String[] strs = new String[] {"test 1", "test 2", "test 3"};


        long t = System.currentTimeMillis();
        for (int i =0; i < 100000;i++) {
            stringManipulation(strs);
        }
        System.out.println("String manipulation: " + (System.currentTimeMillis() - t));


        t = System.currentTimeMillis();
        for (int i =0; i < 100000;i++) {
            testHex(strs);
        }
        System.out.println("Hex: " + (System.currentTimeMillis() - t));


        t = System.currentTimeMillis();
        for (int i =0; i < 100000;i++) {
            testBase64(strs);
        }
        System.out.println("Base64: " + (System.currentTimeMillis() - t));
    }

    public static void stringManipulation(String[] strs) {
        String result = serialize(strs);
        unserialize(result);
    }

    private static String[] unserialize(String result) {
        int sizesSplitPoint = result.toString().lastIndexOf('$');
        String sizes = result.substring(sizesSplitPoint+1);
        StringTokenizer st = new StringTokenizer(sizes, ";");
        String[] resultArray = new String[st.countTokens()];

        int i = 0;
        int lastPosition = 0;
        while (st.hasMoreTokens()) {
            String stringLengthStr = st.nextToken();
            int stringLength = Integer.parseInt(stringLengthStr);
            resultArray[i++] = result.substring(lastPosition, lastPosition + stringLength);
            lastPosition += stringLength;
        }
        return resultArray;
    }

    private static String serialize(String[] strs) {
        StringBuilder sizes = new StringBuilder("$");
        StringBuilder result = new StringBuilder();

        for (String str : strs) {
            if (sizes.length() != 1) {
                sizes.append(';');
            }
            sizes.append(str.length());
            result.append(str);
        }

        result.append(sizes.toString());
        return result.toString();
    }

    public static void testBase64(String[] strs) throws IOException, ClassNotFoundException, DecoderException {
        // serialize
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new ObjectOutputStream(out).writeObject(strs);

        // your string
        String yourString = new String(Base64.encodeBase64(out.toByteArray()));

        // deserialize
        ByteArrayInputStream in = new ByteArrayInputStream(Base64.decodeBase64(yourString.getBytes()));
    }

    public static void testHex(String[] strs) throws IOException, ClassNotFoundException, DecoderException {
        // serialize
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new ObjectOutputStream(out).writeObject(strs);

        // your string
        String yourString = new String(Hex.encodeHex(out.toByteArray()));

        // deserialize
        ByteArrayInputStream in = new ByteArrayInputStream(Hex.decodeHex(yourString.toCharArray()));
    }

}

1
这是比那些提出的方法更安全的方法。虽然开销较大,但使用除十六进制之外的另一种编码,如base64,是一个好主意。 - ARRG
@ARRG:谢谢你的评论,我刚刚评论了需要使用base64的更改。 - Francisco Spaeth
这两种解决方案的性能如何(字符串操作与本答案中提出的方法)? - Janek
使用Base64进行压缩(在我看来是最佳选择)对于一个长度为5的整数数组,在我的机器上大约需要17毫秒。解压缩只需要1毫秒。 - mvreijn

1
使用像Jackson这样的Json解析器,可以将其他类型的对象序列化/反序列化为字符串,例如整数/浮点数。

0

我会在单词之间使用符号,以便稍后使用String#split方法获取字符串。根据您的$符号示例,它将是

public String mergeStrings(String[] ss) {
    StringBuilder sb = new StringBuilder();
    for(String s : ss) {
        sb.append(s);
        sb.append('$');
    }
    return sb.toString();
}

public String[] unmergeStrings(String s) {
    return s.split("\\$");
}

请注意,在此示例中,我在$符号前添加了双重\,因为String#split方法接收正则表达式作为参数,而$符号是正则表达式中的特殊字符。
public String processData(String[] ss) {
    String mergedString = mergeStrings(ss);
    //process data...
    //a little example...
    for(int i = 0; i < mergedString.length(); i++) {
        if (mergedString.charAt(i) == '$') {
            System.out.println();
        } else {
            System.out.print(mergedString.charAt(i));
        }
    }
    System.out.println();
    //unmerging the data again
    String[] oldData = unmergeStrings(mergedString);
}

为了支持您的String[]中的任何字符,最好不要设置单个字符作为分隔符,而应该设置另一个String。相关方法将变成这样:
public static final String STRING_SEPARATOR = "@|$|@";
public static final String STRING_SEPARATOR_REGEX = "@\\|\\$\\|@";

public String mergeStrings(String[] ss) {
    StringBuilder sb = new StringBuilder();
    for(String s : ss) {
        sb.append(s);
        sb.append(STRING_SEPARATOR);
    }
    return sb.toString();
}

public String[] unmergeStrings(String s) {
    return s.split(STRING_SEPARATOR_REGEX);
}

原帖解释说他可以在String[]数组中有任何字符,因此您应该在连接之前转义所选分隔符,例如s.replaceAll("\\$", "\\\\\\$"); - sp00m
@sp00m 我更倾向于保持数据不变,而是提出一种新的模式来分隔每个String(以及它的正则表达式来将其拆分回去)。 - Luiggi Mendoza
但它并不能解决问题,仍然可能出现这种模式在String[]中的一个字符串中。一个想法是始终绘制该模式,但仍存在可能性,并且似乎不是非常干净的解决方案。 - Janek

0

只需使用已知的分隔符(例如@#)来附加您的字符串,然后使用yourString.split(yourSeparator)将其转换为数组。


这样做并不安全,因为这个字符序列可能出现在字符串本身中。 - Francisco Spaeth
嗯,我倾向于同意你的观点。但是你仍然可以在应用程序的其他地方使用被禁止的字符,比如数据库中被禁止的任何字符。当然,@和#只是例子... - dounyy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接