当前位置: 首页 > news >正文

《Java特种兵》1.5 功底补充

1.5 功底补充

看完1.4节发现胖哥废话很多貌似没啥干货了

为了不让大家认为功底只有String那么一点点东西胖哥就再增加对原生态类型、集合类的说明这两方面的内容相信所有的Java开发者都必然会用到。

1.5.1 原生态类型

原生态类型是“神马”

原生态类型就是Java中不属于对象的那5%部分。

那到底是哪些东西呢

包含boolean、byte、short、char、int、float、long、double这8种常见的数据类型Primitive。

好麻烦为啥会用它们呢用对象不可以吗

计算机中的运算基础都来源于简单数字包括Java即使是包装后的对象Wrapper在真正计算的时候也是通过内在的数字来完成的Java失去了它们就好比鱼儿失去了水一样失去了生命力。

它与包装后的对象有什么区别呢

包装后的对象会按照对象的规则存储在堆中例如int所对应的就是Integer类的对象而“线程栈”上只存储引用地址。对象自然会占用相对较大的空间存放在堆中在原生态类型中“栈”上直接保存了它们的值而不是引用Reference。

下面看个例子。

代码清单1-5 一个Integer的简单测试

1 public static void main(String []args) {
2     Integer a = 1;
3     Integer b = 1;
4     Integer c = 200;
5     Integer d = 200;
6     System.out.println(a == b);
7     System.out.println(c == d);
8}

输出结果

true

false

这个结果在较低版本的JDK当中不会出现。

现在胖哥来解释一下这个结果。

在编译阶段若将原始类型int赋值给Integer类型就会将原始类型自动编译为Integer.valueOf(int)如果将Integer类型赋值给int类型则会自动转换调用intValue()方法如果Integer对象为空这时会在自动拆箱的时候抛空指针这个自动转换可以通过后文中介绍javap命令的方法来证明。

这些赋值操作可能不是那么明显例如一些集合类的写入、一些对比操作这就需要我们知道什么时候会自动拆装箱。换句话说它简化了代码但是并不是让我们一无所知。

即使是这样两个结果也应该一样要么都是true要么都是false但为何不一样呢这算是一个Java API的坑如果我们不知道这些坑稍微不留神就会掉进去。知道了装箱是Integer.valueOf(int)方法那么就来看看Integer.valueOf(int)方法的源码如图1-8所示。

ADR90{5E5RLQEMNZO{T0P62

图1-8 Integer.valueOf(int)方法的源码截图

根据代码可以看出当传入i的值在[-128, IntegerCache.high]区间的时候会直接读取IntegerCache.cache这个数组中的值。

在代码中为什么使用i + 128作为数组的下标呢

因为数组下标是从0开始的而表示的数字范围是从-128开始的加上128就正好对上了。

继续跟踪源码可以得到在默认情况下IntegerCache.high是127。也就是说如果传入的int值是-128127之间的数字那么通过Integer.valueOf(int)得到的对象是被cache的自然的对于同一个数字cache的对象是同一块内存地址所以第1个输出结果是true。第2个输出已经不在这个范围因此会重新new Integer(int)创建一个新对象所以得到的结果是false。

我们可以通过设置JVM启动参数-Djava.lang.Integer.IntegerCache.high=200来间接设置IntegerCache.high值也可以通过设置参数-XX:AutoBoxCacheMax来达到目的这个不用查官方资料看看源码以及源码周边的注释就懂了。如果要将这个值变得更大来满足自己的需求则可以在启动参数中增加该值缩小也是一样的道理。

这好像是做好事将我们的数据cache起来更加节约空间了但是有的同学开始认为Integer可以用“==”匹配了因为大家自己“测试”的时候发现1、2、3、4等数据都是没有问题的但是程序发布后出现了诡异的问题而这个最不容易被认为是问题的地方却真的成了问题。而Java API中没有明确地说明这一点但开发人员不会将官方文档都学习一遍再来做开发吧所以我们说它是“坑”。

有人问真正的场景中会这样吗

胖哥认为肯定会而且你遇不到的场景并不代表不会发生今天遇不到的事情并不代表明天不会发生。例如在某些工程设计中某些状态值有特殊的意义如果它们是非连续排布的那么不在-128127范围内的可能性是肯定存在的。

这个例子很简单我们学到的应该不止这些因为坑无处不在我们要学会看源码和本质否则即使是Java本身提供的API出现了“坑”也会让我们“防不胜防”在技术面前变得十分“可怜”。

我们可以说这个API写得不好没有说明详细的使用情况但是一个老A不应当被“武器”所玩弄而是要驾驭武器老A即使拿一把普通步枪也同样能战胜拿着“狙击步枪”的普通士兵因为他们除了拥有极强的战斗素质外还深知武器的脾气与秉性这是人与武器之间的驾驭和被驾驭关系。

也许自动拆装箱还有另一个很大的“坑”就是如果大家不知道自动拆装箱是怎么完成的可能就会有更多的问题发生在程序中传递参数可能一会用Integer类型一会用int类型自然的就一会在做拆箱操作一会在做装箱操作这貌似没有太大的问题但每次装箱的时候都有可能会创建一个对象因为很多时候数字不一定在cache范围内较低版本的JDK是没有cache的。另外这种装箱操作是很隐藏的。例如我们想要用一个int类型的数字来作为HashMap的Key那么在put()操作的时候就会自动发生装箱操作因为Key被认为是Object的HashMap需要获取这个对象的hashCode()方法来做离散规则所以它会自动转型为Integer。同样的如果想将许多基本类型的数据放在List里面在add()操作的时候也会自动发生装箱操作。此时如果数据取出来后变成了基本类型再用这个基本类型放入另一个集合类就又会发生装箱操作在这个过程中就会隐藏地浪费大量的空间而自己却什么也不知道。

关于对象空间的大小请参看第3章的内容。

□ 横向扩展

通过对Integer的一些了解想到了Boolean、Byte、Short、Long、Float、Double它们是否有同样的情况胖哥不想写重复的东西直接给出结果大家可以自己去看看代码看看这些类型中的valueOf()方法是如何操作的或者说自动装箱是如何操作的。

◎ Boolean的两个值true和false都是cache在内存中的无须做任何改造自己new Boolean是另外一块空间。

◎ Byte的256个值全部cache在内存中和自己new Byte操作得到的对象不是一块空间。

◎ Short、Long两种类型的cache范围为-128127无法调整最大尺寸即没有设置代码中完全写死如果要扩展需自己来做。

◎ Float、Double没有cache要在实际场景中cache需自己操作例如在做图纸尺寸时可以将每种常见尺寸记录在内存中。

□ 思维发散扩展

如果上面的操作变成Integer与int类型比较会是什么样的结果呢如果是两个Integer数据做“>”、“>=”、“<”、“<=”比较做switch case操作会得到什么结果反射的时候是否有特殊性

这个结果大家可以去论证且测试结果可以就认为是当前虚拟机的设计规范。下面胖哥直接给出结果。

◎ 当Integer与int类型进行比较的时候会将Integer转化为int类型来比较也就是通过调用intValue()方法返回数字直接比较数字在这种情况下是不会出现例子中的问题的。

◎ Integer做“>”、“>=”、“<”、“<=”比较的时候Integer会自动拆箱就是比较它们的数字值。

◎ switch case为选择语句匹配的时候不会用equals()而是直接用“==”。而在switch case语句中语法层面case部分是不能写Integer对象的只能是普通的数字如果是普通的数字就会将传入的Integer自动拆箱所以它也不会出现例子中的情况。

在JDK 1.7中支持对String对象的switch case操作这其实是语法糖在编译后的字节码中依然是if else语句并且是通过equals()实现的。

◎ 在反射当中对于Integer属性不能使用field.setInt()和field.getInt()操作。在本书的src/chapter01/AutoBoxReflect.java中用例子来说明。

1.5.2 集合类

如果读了上一节后你有所体悟那么胖哥认为你可以跳过此节因为此节知识为上一节的一个平行扩展我们不重视知识点本身而在于让大家了解到许多秘密。

集合类非常多从早期的java.utils的普通集合类到现在增加的java.util.concurrent包下面的许多并发集合类其实我们有些时候只是知道它们是很好用的东西但在遇到某些问题的时候是否会想到是它们造成的就像String一样它们的使用技巧有哪些它们的设计思想是什么

本节不讨论并发包就简单说说集合类的故事。

疑惑集合类中包含了List、Map、Set几大类基本接口而我们最常用、最简单的集合类是什么呢

答曰ArrayList、HashMap。

那么当你用ArrayList的时候是否想起了LinkedList、Vector当你用HashMap的时候是否想起了TreeMap、HashSet、HashTable当你要排序的时候是否想起了SortedSet等。

它们有何区别在什么情况下使用

在讨论String后面的部分内容中我们提到了StringBuilder它内在的数组的实现有大量的拷贝这在集合类的内存拷贝方面的体现更加明显并且占用空间更大。

占用更大空间的原因是集合类都是存储对象的引用的在32bit及64bit压缩模式下一个引用会占用4个字节在64bit非压缩模式下会占用8个字节而StringBuilder只是存储char字符的数组每个位只占用2个字节。

此时以ArrayList为例我们看看它的add(E e)方法源码如图1-9所示。

SUINFCXN7`QR[L]IB9W})ZD

图1-9 ArrayList的add(E e)方法源码截图

通过源码我们发现如果空间不够会通过Arrays.copyOf创建一个新的内存空间新空间的大小最小为原始空间的3/2 倍+1并将原始空间的内容拷贝进去。

这里所提到的新空间的大小为原始空间的3/2倍+1是最小的在add(E)方法中不会发生而在addAll()方法中会发生。addAll()允许同时写入多个数据如果写入的数据较多每次按照1.5倍数扩容可能发生多次扩容这样就会有多余的垃圾空间产生addAll()操作就会对比写入的量与1.5倍的大小谁大就用谁这个道理在StringBuilder中我们就知道因此文中提到的是“最小”。

我们知道ArrayList是基于“数组”来实现的本书3.5节会详细介绍内存结构如果遇到remove()操作add(int index)指定的位置写入操作我们有没有虑过ArrayList内部其实会移动相关的数据而且随着数组越长移动的数据会越多。如果要替换一个数据我们会不会先remove再add一个数据或者是通过set(int index , E e)将对应下标的数据替换掉。

基于数组的ArrayList是非常适合于基于下标访问的这是它擅长的地方又回到基本的数据结构与算法了。下面胖哥给出几个简单扩展希望大家去思考。

◎ 在经常做修改操作的列表中或者在数组通过下标检索并不是那么多的情况下你是否考虑过使用LinkedList呢因为ArrayList通常始终有些数组元素是空着的。

◎ 在知道List长度范围的情况下你是否在实例化 ArrayList的时候带上长度例如new ArrayList(128); 这样就降低了内存碎片和内存拷贝的次数。

◎ 当List太大的时候是否考虑过对它做分段处理而不要一次加载到内存中其实很多OOM都会在集合类中找到问题。

◎ 常见的框架中用了什么集合类在什么情况下也会出现问题

大家熟知的HashMap浪费空间更加严重它的代码里面有一个0.75因子当写入HashMap的数据个数不是说所使用的数组下标个数而是所有元素个数也就是说包含了同一个下标的链表中的所有元素个数达到数组长度的0.75后数组会自动扩展1倍并且还需要做一个rehash操作其实这个时候也许很多桶上的节点都是空的。

胖哥不想扯太多的集合类出来把读者“读晕”大家在理解这两个基本的集合类基础上再去看其他的集合类也许会简单一点。胖哥只想让你知道这些东西是可选择的在什么时候去选择如何去选择完全要看你自己的功底不论是做基础程序、做功底还是去做优化都需要深知它的细节才能做到心中有数

  • 转载自 并发编程网 - ifeve.com

相关文章:

  • 【老鸟分享】Linux命令行终端提示符多种实用技巧!
  • android studio 无法真机调试
  • 第二百六十九节,Tornado框架-Session登录判断
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • Scrapy基础(十)———同步机制将Item中的数据写在Mysql
  • ucore操作系统实验笔记 - 重新理解中断
  • 评论发布信息可插入QQ表情
  • 数据中心操作人员:艰难地在针对VM构建的基础设施上运行容器
  • Event 营销热点日历
  • 使用SuperSocket快速建立Socket服务
  • JSON中JObject和JArray的修改
  • 思维的框架
  • 《数据分析实战 基于EXCEL和SPSS系列工具的实践》一3.3 耗时耗力的数据整理过程...
  • eclipse+tomcat配置远程debug调整
  • myeclipse 10 j安装了JDK1.7,java编译器无法选择到1.7的问题
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 0基础学习移动端适配
  • Angular Elements 及其运作原理
  • C++11: atomic 头文件
  • Fundebug计费标准解释:事件数是如何定义的?
  • github从入门到放弃(1)
  • Git学习与使用心得(1)—— 初始化
  • React Transition Group -- Transition 组件
  • ubuntu 下nginx安装 并支持https协议
  • 回顾 Swift 多平台移植进度 #2
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 利用DataURL技术在网页上显示图片
  • 如何在GitHub上创建个人博客
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 微服务框架lagom
  • 微信小程序开发问题汇总
  • 鱼骨图 - 如何绘制?
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 7行Python代码的人脸识别
  • const的用法,特别是用在函数前面与后面的区别
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​2021半年盘点,不想你错过的重磅新书
  • ​卜东波研究员:高观点下的少儿计算思维
  • #Z0458. 树的中心2
  • (9)目标检测_SSD的原理
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (第一天)包装对象、作用域、创建对象
  • (二十四)Flask之flask-session组件
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (三)Honghu Cloud云架构一定时调度平台
  • (转)Windows2003安全设置/维护
  • (转)平衡树
  • .net core Swagger 过滤部分Api
  • .NET Core WebAPI中封装Swagger配置
  • .net core 控制台应用程序读取配置文件app.config
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .net分布式压力测试工具(Beetle.DT)
  • .net最好用的JSON类Newtonsoft.Json获取多级数据SelectToken
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法