Java:无符号数

4

在Java中有没有像(My)SQL中使用无符号数字的方式?

例如:我想使用一个8位变量(byte),其范围为:0 ... 256;而不是-128 ... 127

7个回答

10

不,Java除了char(有效值为0-65535)之外没有任何无符号原始类型。这很麻烦(特别是对于byte),但这就是现实。

通常你要么保持相同的大小,并将“高”数字溢出为负数,要么使用更宽的类型(例如short 代替 byte)并处理额外的内存需求。


你确定这样做真的能节省内存吗?我觉得不行——我认为Java会使用与你的字长相同的空间来保存一个字节(除了在数组中,它会进行打包)。无论哪种情况,不使用int类型都会稍微降低一些速度。 - Bill K
Java 8现在部分支持无符号算术。请参见https://dev59.com/Sl8e5IYBdhLWcg3wnrcQ - rghome

3
您可以使用一个类来模拟一个无符号数。例如:
public class UInt8 implements Comparable<UInt8>,Serializable
   {
   public static final short MAX_VALUE=255;
   public static final short MIN_VALUE=0;
   private short storage;//internal storage in a int 16

   public UInt8(short value)
      {
      if(value<MIN_VALUE || value>MAX_VALUE) throw new IllegalArgumentException();
      this.storage=value;
      }

   public byte toByte()
      {
      //play with the shift operator ! << 
      }
  //etc...
   }

2
可以做到。但是如果我想要一个无符号变量,那是为了节省内存空间。如果不需要,我可以使用 int。 - Martijn Courteaux
2
一个对象实例的开销(除其可见字段以外需要的内存)因虚拟机而异,但通常在40个字节左右。4000%的开销……嗯,为了节省几个“&0xFF”操作可能不值得。 - erickson
1
Eric和Martin,完全赞同。但如果您想确保您的代码正在处理正确类型的数据,则它可能非常有用。例如,当您的代码正在读取/写入C程序使用的二进制结构时。 - Pierre

2

您可以将大部分有符号数视为无符号数使用。大多数操作保持不变,一些需要进行修改。请参见此帖子


2

在内部,您不应使用较小的值--只需使用int。据我所知,使用较小的单位除了减慢速度外没有任何作用。它不会节省内存,因为Java在内部使用系统的字大小进行所有存储(它不会打包字)。

但是,如果您使用较小的存储单元,则必须为每个操作进行掩码或范围检查或其他操作。

有没有注意到char(任何操作)char产生int?他们真的不希望你使用这些其他类型。

例外情况是数组(我相信将被打包)和I/O,在这些情况下,您可能会发现使用较小的类型很有用...但掩码也可以工作。


+1 是为了解释为什么使用无符号类型不会节省任何空间...这显然是原帖作者试图做的。 - Stephen C
@Stephen:担心空间问题肯定与数组有关,除非OP没有头绪。不管怎样,你会用什么类型来处理大量的字节大小采样的数组?我想你可以使用bytes并使用255进行掩码,但这个问题是Java中的一个主要缺陷。 - R.. GitHub STOP HELPING ICE

1

不行,你不能改变那个。如果你需要比127更大的东西,就选择比一个字节更大的东西。


0

我对Java和编程都很新手。然而,最近我遇到了需要使用无符号值的情况。

我花了大约两周的时间编写我所想的一切,但我是一个完全的新手,所以你可能会花费更少的时间。

总体思路是创建一个接口,我将其命名为:UnsignedNumber<Base, Shifted>,并扩展Number.class,同时实现一个抽象的AbstractUnsigned<Base, Shifted, Impl extends AbstractUnsigned<Base, Shifted, Impl>>类。

因此,Base参数化类型表示基本类型,Shifted表示实际的Java类型。Impl是这个抽象类的实现的快捷方式。

大部分时间都消耗在Java 8 Lambdas和内部私有类以及安全程序上。重要的是要在数学运算(如减法或负加法)产生零限制时实现无符号的行为:向后溢出上限签名。

最后,又花了几天时间编写工厂和实现子类。

到目前为止,我已经知道:

UByte和MUByte UShort和MUShort UInt和MUInt ...等等。

它们是AbstractUnsigned的后代: UByte或MUByte扩展AbstractUnsigned<Byte,Short,UByte>AbstractUnsigned<Byte,Short,MUByte> UShort或MUShort扩展AbstractUnsigned<Short,Integer,UShort>AbstractUnsigned<Short,Integer,MUShort> ...等等。

总体思路是将无符号上限作为移位(强制转换)类型,并将负值的代码转置为它们不是来自零,而是无符号上限。

更新: (感谢Ajeans的友好和礼貌指导)

/**
* Adds value to the current number and returns either
* new or this {@linkplain UnsignedNumber} instance based on
* {@linkplain #isImmutable()}
*
* @param value value to add to the current value
* @return new or same instance
* @see #isImmutable()
*/
public Impl plus(N value) {
    return updater(number.plus(convert(value)));
}

这是一个外部可访问的方法,属于AbstractUnsigned<N, Shifted, Impl>(或者之前被称为AbstractUnsigned<Base, Shifted, Impl>); 现在,让我们来看看底层的工作:

private Impl updater(Shifted invalidated){
    if(mutable){
        number.setShifted(invalidated);
        return caster.apply(this);
    } else {
        return shiftedConstructor.apply(invalidated);
    }
}

在上述私有方法中,mutableAbstractUnsignedprivate final booleannumber 是内部私有类之一,负责将Base 转换为 Shifted ,反之亦然。 与之前的“去年夏天我做了什么”相关的是两个内部对象:castershiftedConstructor
final private Function<UnsignedNumber<N, Shifted>, Impl> caster;
final private Function<Shifted, Impl> shiftedConstructor;

这些是参数化函数,用于将N(或Base)转换为Shifted,或者如果AbstractUnsigned<>的当前实现实例是不可变的,则创建一个新的Impl实例。

Shifted plus(Shifted value){
    return spawnBelowZero.apply(summing.apply(shifted, value));
}

在这个片段中展示了number对象的添加方法。想法是始终在内部使用Shifted,因为不确定何时会生成“原始”类型的正限制。shifted是一个参数化字段,它承载着整个AbstractUnsigned<>的值。另外两个Function<>导出对象如下:
final private BinaryOperator<Shifted> summing;
final private UnaryOperator<Shifted> spawnBelowZero;

前者执行两个Shifted值的加法。而后者执行零以下转位的生成。

现在,来自工厂样板“hell”之一的示例,特别针对先前提到的spawnBelowZeroUnaryOperator<Shifted>,针对AbstractUnsigned<Byte,Short>

...,
         v-> v >= 0
         ? v
         : (short) (Math.abs(Byte.MIN_VALUE) + Byte.MAX_VALUE + 2 + v),
...

如果“Shifted v”是正数,什么都不会发生,原始值将被返回。否则:需要计算“Base”类型的上限,即“Byte”,并将负值“v”加到该值上。例如,如果“v == -8”,那么“Math.abs(Byte.MIN_VALUE)”将产生“128”,而“Byte.MAX_VALUE”将产生“127”,这样就得到了原始上限,“255”+1,以获得被符号位截断的原始上限,我明白了,所以理想的“256”就在那里。但是,第一个负值实际上就是那个“256”,所以再加上“+1”或者总共加上“+2”。最后,“255 + 2 + v”,即“-8”,得到“255 + 2 + (-8)”和“249”。
或者以更直观的方式表达:
0 1 2 3 ... 245 246 247 248 249 250 251 252 253 254 255 256
                             -8  -7  -6  -5  -4  -3  -2  -1

最后,需要强调的是:这并不会减轻你的工作量或节省内存空间,但在需要时,你可以获得非常理想的行为。而且你可以将这种行为应用于任何其他Number.class子类。 AbstractUnsigned本身就是Number.class的子类,提供了所有方便的方法和常量,类似于其他“本地”Number.class子类,包括MIN_VALUEMAX_VALUE等等。例如,我编写了一个方便的可变子类方法makeDivisibileBy(Number n),它执行最简单的操作value - (value%n)
我的初衷是展示即使像我这样的新手也能编写它。当我编写该类时,我的初衷是获得一个方便多用的工具。

这篇文章读起来像是一篇“我去年夏天做了什么”的随笔,因此其中包含的任何答案都很难找到。我建议删除所有多余的内容,比如“我花了两个星期”的描述等。另外,不仅要解释你做了什么,还要解释如何做以及这如何为这个古老的问题添加新信息,这样会更有用。 - Ajean

0

如果您需要优化存储(例如大矩阵),可以使用负数编码更大的正数,以节省空间。然后,您需要移动数字值以在需要时获取实际值。例如,我只想处理短正数。以下是在Java中实现此操作的方法:

        short n = 32767;
        n = (short) (n + 10);
        System.out.println(n);     
        int m = (int) (n>=0?n:n+65536); 
        System.out.println(m);

当一个短整数超出范围时,它会变成负数。但是,至少你可以将这个数字存储在16位中,并通过添加移位值(可以编码的不同值的数量)来恢复其正确的值。该值应在较大的类型(在我们的情况下为int)中恢复。这可能不是非常方便,但我发现在我的情况下是这样的。


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