int属于什么数据,int属于c语言关键字吗

  

  典型回答   

  

  Int就是我们常说的整数。它是Java的八种原始数据类型(boolean、byte、short、char、int、float、double、long)之一。虽然Java语言宣称一切都是对象,但原始数据类型是个例外。   

  

  Integer是对应int的包装类。它有一个int类型的字段来存储数据,并提供基本的操作,如数学运算,int和string之间的转换等。在Java 5中,引入了自动装箱和取消装箱(boxing/unboxing)。Java可以根据上下文自动转换,大大简化了相关编程。   

  

  关于Integer的值缓存,这涉及到Java 5的另一个改进。构建整数对象的传统方式是直接调用构造函数,直接创建对象。但是,根据实践,我们发现大多数数据操作都集中在有限的、很小的数值范围内。所以Java 5中增加了一个静态工厂方法valueOf,调用时使用了缓存机制,带来了明显的性能提升。根据Javadoc,该值的默认缓存在-128到127之间。   

  

  考点分析   

  

  今天的问题涵盖了Java中的两个基本元素:原始数据类型和包装类。说到这里,自然可以扩展到自动打包和解包机制,然后可以考察封装类的一些设计和实践。坦白说,了解基本原理和用法对于日常工作来说已经足够了,但是还有很多问题需要仔细考虑后才能确定。   

  

  面试官可以结合其他方面来考察面试官的掌握程度和思维逻辑,比如:   

  

  之前介绍的Java使用的不同阶段:编译阶段,运行时,自动打包/解包发生在哪个阶段?如前所述,使用静态工厂方法valueOf会使用缓存机制,那么在自动打包过程中缓存机制起作用吗?为什么我们需要原始数据类型?Java对象似乎非常高效。具体在应用上有什么区别?你看过整数源代码吗?下面分析一下类的设计要点或者一些方法。知识扩展   

  

  1. 理解自动装箱、拆箱   

  

  自动打包其实是一种语法糖。什么是语法糖?可以简单理解为Java平台自动为我们执行一些转换,保证不同的编写方法在运行时是等价的。它们发生在编译阶段,也就是说,生成的字节码是一致的。   

  

  和前面提到的integer一样,javac自动将打包转换成Integer.valueOf()并用Integer.intValue()替我们解包,似乎顺便回答了另一个问题。既然调用了Integer.valueOf,我们自然可以得到缓存的好处。   

  

  如何在程序上验证上述结论?   

  

  你可以写一个包含下面两句话的简单程序,然后反编译。当然,这是从性能上来说的落后方法。大多数情况下,我们直接参考规范文档会更可靠。毕竟,软件承诺遵循规范,而不是保持当前的行为。   

  

  整数integer=1;   

  

  int unboxing=integer   

  

  12反编译输出:   

  

  1: invokes static # 2//方法Java/lang/Integer . value of :(I)Ljava/lang/Integer;   

  

  8: invokevirtual #3 //方法Java/lang/integer . intvalue :()I   

  

  12这种缓存机制不仅适用于Integer,也存在于其他包装类中,例如:   

  

  布尔值,相应的真/假的实例被缓存,具体地说,只有两个常量实例Boolean.TRUE/FALSE被返回。短值,也是介于-128和127之间的缓存值。字节,值有限,所以全部缓存。字符,缓存范围为' \ u0000 '到' \ u007f '。自动打包/自动解包似乎很酷。编程实践有什么需要注意的吗?   

  

  原则上,建议避免无意的打包和解包,尤其是在性能敏感的情况下。创建10万个Java对象和10万个整数的成本不是一个数量级。无论是内存占用还是处理速度,光是对象头的空间占用就已经是一个数量级的差别了。   

  

  事实上,我们可以扩展这个视图,使用原始数据类型、数组甚至本地代码实现等。这通常在极其敏感的性能场景中具有很大的优势。用它们替换包装类和动态数组(比如ArrayList)可以作为性能优化的替代方法。一些追求极致性能的产品或类库会尽量避免创建过多的对象。当然,在大多数产品代码中,并不一定要这样做,开发效率才是优先考虑的。以我们经常使用的计数器实现为例。下面是一个常见的线程安全计数器实现。   

  

  类别计数器{   

  

  private final atomic long counter=new atomic long();   

  

  公共无效增加(){   

  

  counter . incrementandget();   

  

  }   

  

  }   

/p>   

123456如果利用原始数据类型,可以将其修改为

  

class CompactCounter {

  

private volatile long counter;

  

private static final AtomicLongFieldUpdater<CompactCounter>updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter");

  

public void increase() {

  

updater.incrementAndGet(this);

  

}

  

}

  

123456782. 源码分析

  

考察是否阅读过、是否理解 JDK 源代码可能是部分面试官的关注点,这并不完全是一种苛刻要求,阅读并实践高质量代码也是程序员成长的必经之路,下面我来分析下 Integer 的源码。

  

整体看一下 Integer 的职责,它主要包括各种基础的常量,比如最大值、最小值、位数等;前面提到的各种静态工厂方法 valueOf();获取环境变量数值的方法;各种转换方法,比如转换为不同进制的字符串,如 8 进制,或者反过来的解析方法等。我们进一步来看一些有意思的地方。

  

首先,继续深挖缓存,Integer 的缓存范围虽然默认是 -128 到 127,但是在特别的应用场景,比如我们明确知道应用会频繁使用更大的数值,这时候应该怎么办呢?

  

缓存上限值实际是可以根据需要调整的,JVM 提供了参数设置:

  

-XX:AutoBoxCacheMax=N

  

这些实现,都体现在 java.lang.Integer 源码之中,并实现在 IntegerCache 的静态初始化块里。

  

private static class IntegerCache {

  

static final int low = -128;

  

static final int high;

  

static final Integer cache<>;

  

static {

  

// high value may be configured by property int h = 127;

  

String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");

  

...

  

// range <-128, 127> must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127;

  

}

  

...

  

}

  

123456789101112第二,我们在分析字符串的设计实现时,提到过字符串是不可变的,保证了基本的信息安全和并发编程中的线程安全。如果你去看包装类里存储数值的成员变量“value”,你会发现,不管是 Integer 还 Boolean 等,都被声明为“private final”,所以,它们同样是不可变类型!

  

这种设计是可以理解的,或者说是必须的选择。想象一下这个应用场景,比如 Integer 提供了 getInteger() 方法,用于方便地读取系统属性,我们可以用属性来设置服务器某个服务的端口,如果我可以轻易地把获取到的 Integer 对象改变为其他数值,这会带来产品可靠性方面的严重问题。

  

第三,Integer 等包装类,定义了类似 SIZE 或者 BYTES 这样的常量,这反映了什么样的设计考虑呢?如果你使用过其他语言,比如 C、C++,类似整数的位数,其实是不确定的,可能在不同的平台,比如 32 位或者 64 位平台,存在非常大的不同。那么,在 32 位 JDK 或者 64 位 JDK 里,数据位数会有不同吗?或者说,这个问题可以扩展为,我使用 32 位 JDK 开发编译的程序,运行在 64 位 JDK 上,需要做什么特别的移植工作吗?

  

其实,这种移植对于 Java 来说相对要简单些,因为原始数据类型是不存在差异的,这些明确定义在 Java语言规范 里面,不管是 32 位还是 64 位环境,开发者无需担心数据的位数差异。

  

对于应用移植,虽然存在一些底层实现的差异,比如 64 位 HotSpot JVM 里的对象要比 32 位 HotSpot JVM 大(具体区别取决于不同 JVM 实现的选择), 但是总体来说,并没有行为差异,应用移植还是可以做到宣称的“一次书写,到处执行”,应用开发者更多需要考虑的是容量、能力等方面的差异。

  

3. 原始类型线程安全

  

前面提到了线程安全设计,你有没有想过,原始数据类型操作是不是线程安全的呢?

  

这里可能存在着不同层面的问题:

  

原始数据类型的变量,显然要使用并发相关手段,才能保证线程安全,这些我会在专栏后面的并发主题详细介绍。如果有线程安全的计算需要,建议考虑使用类似 AtomicInteger、AtomicLong 这样的线程安全类。特别的是,部分比较宽的数据类型,比如 float、double,甚至不能保证更新操作的原子性,可能出现程序读取到只更新了一半数据位的数值!4. Java 原始数据类型和引用类型局限性

  

前面我谈了非常多的技术细节,最后再从 Java 平台发展的角度来看看,原始数据类型、对象的局限性和演进。

  

对于 Java 应用开发者,设计复杂而灵活的类型系统似乎已经习以为常了。但是坦白说,毕竟这种类型系统的设计是源于很多年前的技术决定,现在已经逐渐暴露出了一些副作用,例如:

  

原始数据类型和 Java 泛型并不能配合使用这是因为 Java 的泛型某种程度上可以算作伪泛型,它完全是一种编译期的技巧,Java 编译期会自动将类型转换为对应的特定类型,这就决定了使用泛型,必须保证相应类型可以转换为 Object。

  

无法高效地表达数据,也不便于表达复杂的数据结构,比如 vector 和 tuple我们知道 Java 的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存,而对象数组则不然,数据存储的是引用,对象往往是分 散地存储在堆的不同位置。这种设计虽然带来了极大灵活性,但是也导致了数据操作的低效,尤其是无法充分利用现代 CPU 缓存机制。

  

Java 为对象内建了各种多态、线程安全等方面的支持,但这不是所有场合的需求,尤其是数据处理重要性日益提高,更加高密度的值类型是非常现实的需求。

  

针对这些方面的增强,目前正在 OpenJDK 领域紧锣密鼓地进行开发,有兴趣的话你可以关注相关工程:http://openjdk.java.net/projects/valhalla/。

  

今天,我梳理了原始数据类型及其包装类,从源码级别分析了缓存机制等设计和实现细节,并且针对构建极致性能的场景,分析了一些可以借鉴的实践。

  

相关文章