代码之家  ›  专栏  ›  技术社区  ›  Kamil

数字类是如何工作的(在Java中)?

  •  0
  • Kamil  · 技术社区  · 6 年前

    背景:

    我是Java新手。我在一个数据记录器项目中工作,在那里我使用了许多与预期数据范围和精度相关的数据类型,以减少存储、内存和CPU使用,因为它将在资源有限的设备上运行。

    例如,为了存储温度,我使用了8位数据类型,为了存储一些其他东西,我使用了16位短数据类型,在少数情况下,我使用了双数据类型。

    目前(为了获得最佳性能),我使用字节数组和bytebuffer,并将不同类型的度量值放入其中,逐字节。还有一个字节数组,我在其中存储字节缓冲区索引,其中包含以下信息:哪个示例在哪里,它是哪种数据类型。它执行得很好,但调试起来很复杂,我有单独的函数,它们在不同的数据类型上执行相同的操作。

    我要做的是

    我想创建具有不同数据类型的所有测量的列表:

    Double someDouble = 0.24;
    Integer someInteger = 234;
    Long someLong = 253263632L;
    
    List<Number> measurements = new ArrayList<>();
    measurements.add(someDouble);
    measurements.add(someInteger);
    measurements.add(someLong);
    
    // later I have to multiply all measurements by their multipliers
    // and do some other math stuff
    // My goal is to do all math in one loop without care about data type
    foreach(Number n: measurements) {
        doSomeMathOnNumber(n);
    }
    

    我的问题是:

    当我将这3种数据类型分配给number类时,它内部会发生什么?

    更准确地说:

    • 有内存开销吗?
    • 我能不能把双倍放在那里,当我试图把它恢复成双倍的时候,不会有精度损失?
    1 回复  |  直到 6 年前
        1
  •  1
  •   Basil Bourque    6 年前

    TL;博士

    有内存开销吗?

    • 是的,对象比原语占用更多的内存。
    • 自动装箱在内存和性能上都有很小的成本。

    当我将这3种数据类型分配给number类时,它内部会发生什么?

    当你分配一个 Integer 我是说, Long ,或 Double 反对你的 List<Number> 是的。

    但有些特别的东西 当将一个原语指派给它的包装类的对象时发生:将原语自动装箱到对象中。这发生在前三行中,您在其中填充 双倍 ,请 整数 ,和 对象,方法是指定一个基元值。

    如果您的环境受到如此严格的限制(内存有限),您应该 使用任何一个首字母大写类名: Byte ,请 Short 我是说, 整数 我是说, 我是说, Float 我是说, 双倍 ,请 Number 是的。对基元类型使用基元数组。但是 使用类型(下面讨论),而不是发明自己的逐字节管理,除非你有一个非常明确的理由。

    我能不能把双倍放在那里,当我试图把它恢复成双倍的时候,不会有精度损失?

    双倍 对象作为 数字 .一个 双倍 对象 是一个 数字 已经,通过 inheritance 是的。每 双倍 对象是 数字 ,但并非所有 数字 是一个 双倍 是的。

    细节

    Number 是一个 abstract class (见 Tutorial ,这意味着它不是直接实例化的。它被设计成 subclassed ,而这些子类又可以被实例化。

    你需要了解 primitive types object types . Java提供了两种 type systems 是的。基本值(请参见 Tutorial )是 面向对象。原语被设计成java是为了(a)不擅长oop的程序员学习新的java语言,(b)从具有类似类型系统的其他语言移植代码。( such as C )中。此外,原语的优点是占用很少的内存,并且使用起来很快。相反,对象需要更多的内存,并没有执行的速度快,但更灵活和复杂。

    有些人认为,可以设计一种编程语言,使其兼有两者的优点,只公开对象类型,而用原始类型支持其中的一些类型,但是 Java(当前)做什么,所以这里我们将把这个话题放在一边。

    Java在同一代码中混合了原语和对象类型。甚至每个数值原语都有等效的对象类型。注意字母大小写约定,其中小写表示基元类型,而大写表示对象类型。下面列出的每个初始cap类都是 数字 是的。

    • 对于原始人 byte ,我们上课了 Byte ,两者 8-bit (octet) 有符号整数持有者,范围为-128到127(含)。
    • 对于原始人 short ,我们上课了 Short ,两者 16-bit 有符号整数持有者,范围为-32768到32767(含)。
    • 对于原始人 int ,我们上课了 Integer ,两者 32-bit 有符号整数持有者,范围为-2 31个 最大值为2 31个 -1(大约±20亿)。
    • 对于原始人 long ,我们上课了 Long ,两个64位有符号整数持有者,范围为-2 63个 最大值为2 63个 -一。
    • 对于原始人 float ,我们上课了 Float ,均为32位有符号 floating-point 数字。
    • 对于原始人 double ,我们上课了 Double ,均为64位有符号 浮点 数字。

    如果我们已经有了原语,为什么还要麻烦包装类呢?与其他需要对象的代码兼容。最大的例子是 Java Collections Framework 是的。

    最近几代Java添加 auto-boxing 通过在编译时生成转换代码来弥合类型系统之间的差距。自动装箱将原始数据类型转换为它们的匹配包装器类。

    增加了自动装箱,使人类程序员的生活更容易,但在运行时为计算机提供更多的工作。装箱意味着查找匹配的类,实例化该类的对象,并将原始值的赋值赋给该对象。拆解意味着相反,必须从对象中提取值,并将其放置在原始人所在的内存中。

    在许多应用程序中,通过装箱和解包增加的开销对应用程序的整体性能来说是可以忽略不计的。但是在更极端的情况下,大量的数字经常被处理,程序员可能会决定避免拳击,避免对象,只使用原语。

    Double someDouble = 0.24;
    Integer someInteger = 234;
    Long someLong = 253263632L;
    

    在上面三行中的每一行中,右边都有一个基本值被自动装箱到左边的一个对象中。Java的自动装箱功能使这种外观几乎看不见,因为在传统的计算机环境中,我们通常不关心与自动装箱有关的性能和内存的命中。但是,如果为受约束的环境编程,您可能希望避免 数字 对象和 List 物体。但你就放弃了 polymorphism (治疗 双倍 &安培 整数 &安培 全部为 数字 )中。

    同样,我认为 doSomeMathOnNumber(n) 方法是做一些解压,从对象回到原语。使用更多的内存和CPU周期。

    (为了获得最佳性能)我使用byte array和bytebuffer,并将不同类型的测量值放入其中,

    虽然我不是在受限环境中编程的专家,但我猜你工作太辛苦了。我怀疑简单的Java数组持有 字节 ,请 短的 等类型可以满足您的需要。

    有关更多信息,请搜索堆栈溢出以查找此类问题, Why do we use autoboxing and unboxing in Java? 是的。


    顺便说一下, 浮点 类型 trade away accuracy 为了执行的速度。他们是 适用于精确性很重要的事情,如跟踪资金。对于此类问题,请使用 BigDecimal 类,较慢但准确。