在Java中比较整数和字符常量

7
我有一个二进制文件,使用4个字符来形成具有“含义”的整数。例如,4字节“0005”等于整数808464437。
我更喜欢在我的Java代码中表示这些内容为'0005'而不是808464437。 我也不想设置像final int V0005 = 808464437 这样的常量。
在C++中,我可以执行以下操作:
fileStream->read((byte*) &version, 4); //Get the next bytes and stuff them into the uint32 version

switch (version) {
    case '0005':
        //do something
    case '0006':
        //do something
    case '0007':
        //do something
}

在Java中,我的问题不是读取4个字节并将它们放入某些数据类型中。我的问题是将某些数据类型与const char数组'0005'进行比较。
我该如何在Java中将int或其他形式的数据类型与类似'0005'的const字符数组进行比较?我需要尽可能高效地完成这项任务!

1
我宁愿在我的Java代码中表示它们为“0005”,而不是808464437。我也不想设置像final int V0005 = 808464437这样的常量。这些句子要么是矛盾的,要么我不理解你的意思。或者你需要看一下Java的枚举? - Marvo
尽管0x30和'0'相等,并且只有数字时,创建一个名为short convertBytesToBCD(byte[])的函数。 - Joop Eggen
'0005' 是一个等价于 0x30 << 24 + 0x30 << 16 + 0x30 << 8 + 0x35 的整数。 - crush
@Marvo,这似乎并不比设置static final int V0005 = 808464437有什么不同。我将使用枚举,如 public enum FileVersion { V0005(808464437) } - crush
也许我在提到时没有很清楚你想要什么。但是Java枚举确实提供了一些数据封装和相关逻辑。个人而言,我不太喜欢在代码中使用字符字面量,因此我会寻找其他解决方案。 - Marvo
显示剩余6条评论
7个回答

4

感谢您解释您的答案。

以下是将4个字节转换为int,然后可以与问题中的int进行比较的方法:

    int v0005 = ByteBuffer.wrap("0005".getBytes()).asIntBuffer().get();

很抱歉,这是我能想到的唯一方法...可能没有你想要的那么高效,但也许正是你所需要的。
我建议将其中一些设置为类中的“常量”,如下所示:
public static final int V0005 = ByteBuffer.wrap("0005".getBytes()).asIntBuffer().get();
public static final int V0006 = ByteBuffer.wrap("0006".getBytes()).asIntBuffer().get();
public static final int V0007 = ByteBuffer.wrap("0007".getBytes()).asIntBuffer().get();
public static final int V0008 = ByteBuffer.wrap("0008".getBytes()).asIntBuffer().get();

然后打开V0005等,因为打开原始类型(int)是高效的。


'0005' 是 4 个字节的 ASCII 表示。请注意它们是字符。我正在使用小端字节顺序,并且它们映射到 808464437。 - crush
1
好的,我明白了。我想我有一个解决方案...正在编辑我的答案。 - vikingsteve
我认为你可能是对的,没有选择,只能使用 static final int,尽管这不是我想要的。在这种情况下,我将简单地使用另一个答案展示的0x30303035方法。+1 - crush

3

在Java中,char数组与字符串(String)没有什么区别。您可以在Java7中使用字符串进行switch-case语句,但我不知道比较的效率。

因为你的char数组中似乎只有最后一个元素有意义,所以你可以使用switch case语句来处理它。例如:

private static final int VERSION_INDEX = 3;

...
    char[] version = // get it somehow

    switch (version[VERSION_INDEX]) {
         case '5': 
            break;
         // etc
    }
...

编辑 更多面向对象的版本。

  public interface Command {
       void execute();
  }

  public class Version {
       private final Integer versionRepresentation;

       private Version (Integer versionRep) {
            this.versionRepresentation = versionRep;
       }

       public static Version get(char[] version) {
            return new Version(Integer.valueOf(new String(version, "US-ASCII")));
       }

       @Override
       public int hashCode() {
            return this.versionRepresentation.hashCode();
       }
       @Override
       public boolean equals(Object obj) {
            if (obj instanceof Version) {
                Version that = (Version) obj;

                return that.versionRepresentation.equals(this.versionRepresentation);
            }
            return false;
       }
  }

  public class VersionOrientedCommandSet {
       private final Command EMPTY_COMMAND = new Command() { public void execute() {}};
       private final Map<Version, Command> versionOrientedCommands;

       private class VersionOrientedCommandSet() {
           this.versionOrientedCommands = new HashMap<Version, Command>();
       }

       public void add(Version version, Command  command) {
           this.versionOrientedCommands.put(version, command);
       }

       public void execute(Version version) throw CommandNotFoundException {
           Command command = this.versionOrientedCommands.get(version);

           if (command != null)  {
               command.execute();
           } else {
               throw new CommandNotFoundException("No command registered for version " + version);
           }

       }
  }

  // register you commands to VersionOrientedCommandSet
  char[] versionChar = // got it from somewhere
  Version version = Version.get(versionChar);
  versionOrientedCommandSet.execute(version);

有很多的代码呢,你需要一点预热成本,但如果你的程序被执行多次,使用地图会更加高效 :P


到目前为止,这是最接近的事情,因为您似乎是唯一理解0x0005'0005'不同的人。不幸的是,当版本增长到'0010'时会发生什么。 - crush
是的,我看到了问题,但这是目前我看到的你问题的唯一快速解决方案。你可以处理字符数组并将其转换为整数。我会编辑并发布它。 - Caesar Ralf
我仔细考虑了一下,这种方法并不是很高效。它会类似于vikingsteve的解决方案。 - Caesar Ralf
我会写一些不是很高效的代码,但你以后可以使用它,这样你就不需要使用 switch case 了。 - Caesar Ralf
完成了,但我不认为你会通过我的解决方案节省时间呵呵。现在更加面向对象了。 - Caesar Ralf

2

结合其他答案(@marvo和@vikingsteve)的一些建议,我得出了以下内容:

public enum Version {

  V0005 {

    @Override
    public void doStuff() {
      // do something
    }
  },
  V0006 {
  @Override
    public void doStuff() {
    // do something else
    }

  };

private final int value;
private static final Map<Integer, Version> versions = new HashMap<>();

private Version() {
    final byte[] bytes = name().substring(1).getBytes(); //remove the initial 'V'
    this.value = ByteBuffer.wrap(bytes).asIntBuffer().get();
}

static {
    for (Version v : values()) {
        versions.put(v.value, v);
    }
}

public abstract void doStuff();


public Version valueOf(int i){
    return versions.get(i);
}
}

这种方法提供了很好的面向对象的方式,因为操作与数据表示封装在一起。每个常量在调用doStuff时决定要执行什么操作,避免了客户端代码中的switch语句。使用方法如下:

int code = readFromSomewhere();
Version.valueOf(i).doStuff();
valueOf 方法在加载类时填充的映射中查找 Version。我不确定这是否足够高效,因为 int 被装箱成了一个 Integer,但是只有在进行性能分析时才能确定,其他都是纯猜测。 此外,请注意,整数值在构造函数中为您计算,因此您只需使用正确的名称定义 enums,就可以自动完成。

我也喜欢这个地图解决方案。我本来想把它加入我的答案中,但是有点懒。而且我也不会像你的答案那样优雅地完成它。 - Marvo
它根据枚举的名称工作的事实非常美妙。 - Marvo

1
最有效的方法是使用整数 - 比如说 final int V0005 = 0x30303035(结合了这里几个不同的想法)会更容易管理。Java没有你要找的那种整数字面量。
如果你真的想使用字符串,你需要先读取字节数组中的字符,然后将其转换为字符串(默认字符集通常可以工作,因为它是ASCII的超集)。但这样会更低效。
你可以编写一个函数来计算简单数字(如5)的常量值,以获得更好的可读性,但这可能有些过头了。

这不是C++中的字符串。它是一个const字符数组,因为它是4个字节的序列,所以它会被解释为整数。在C++中,这是一个有效的文字。 - crush
我改正了 - 它会输出一个警告,但是可以工作。我已经编辑了答案。 - aditsu quit because SE is EVIL
如果int是16位而不是32位,它就无法工作,因为你当然无法将4个字节装入2个字节中。因此,在16位架构上会发出警告-这不是标准的一部分,但是这是常见做法。 - crush
我使用的是64位架构,但警告是关于使用“多字符字符常量”的。 - aditsu quit because SE is EVIL
以前从未见过这个警告。你使用的编译器是什么? - crush
显示剩余2条评论

1
你是否在寻找类似于 value1.intValue() == value2.intValue() 的东西?或者你可以直接进行类型转换。
char myChar = 'A';
int myInt = (int) myChar;

相同的规则也适用于反过来。
int myInt = 6;
char myChar = (char) myInt;

这是正确的类型转换方式,但我认为他还需要将其转换为更高位... - vikingsteve
是的,一个单独的 char 似乎可以轻松转换,这就是我感到困惑的原因,为什么一个大小为4的 char[] 不能转换成 int。我猜这只是因为 Java 中数组的处理方式不同。 - crush

1
唯一的方法就是进行一种非常低效的字节数组到字符串的转换。
switch (new String(version, "US-ASCII")) {
case "0005":
    break;
...
}

使用哈希表(Map)会使其再次加快速度。但我认为您不会对此解决方案感到满意。

所以,你真正想说的是在Java中没有诸如 '0005' 这样的 const char 数组?除非我想将其存储为 final int 中的值,否则必须使用字符串对象进行比较。 - crush
我好像没有你展示的String构造函数。你确定这是有效的格式吗? - crush
有一个构造函数 String(byte[] data, String encoding)。是的,4字节字符 'xxxx'(最初由MacOS看到)没有对应项。只有像0xcafebabe(Java魔术Cookie)这样的玩意儿。 - Joop Eggen
这很酷,但我认为它不够高效。我想最终还是选择静态常量。感谢提供的信息! - crush

1

考虑到这似乎是数据包中的版本字符串,因此值的数量是有限且适量的。因此它们可以封装在枚举中。该枚举可以封装有效的代码,用于将输入数据中的四个字节转换为代表性枚举。(我的执行该转换的代码不一定是最有效的。也许,给定少量版本,一个漂亮的内部数组或映射int到枚举的方法会起作用。)

class enum Version {

    UNKNOWN(0),
    V0005(0x30303035),  // whatever the int value is
    V0006(0x30303036);

    private int id;
    private Version(int value) {
        id = value;
    }

    public static Version valueOf(int id) {
        for (Version version : values()) {
            if (version.getId() == id) {
                return version;
            }
        }
        return UNKNOWN;
    }

    public static Version valueOf(String sId) {
        return valueOf(computeId(sId));
    }

    public static int computeId(String sId) {
        // efficiently convert from your string to its int value
    }
}

然后在我的 switch 语句中:
String s = methodThatReadsFourChars(...);
switch (Version.valueOf(s)) {
case V0005 : doV0005Stuff(); break;
case V0006 : doV0006Stuff(); break;
default :
    throw new UnknownVersionException();

1
你可以通过在枚举类中直接添加doStuff()方法来改进它,例如: V0005(0x803030d2){ void doStuff(){ ...} } 这样,您可以将操作与值封装在一起。这样,您就可以省去 switch 语句,并直接调用 Version.valueOf(s).doStuff(). - Chirlo
我很想看到一个例子。我对那种语法不熟悉。 - Marvo
1
我添加了一个带有示例的答案,如果您感兴趣,请查看。 - Chirlo

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