- 浏览: 2188691 次
- 性别:
- 来自: 北京
文章分类
- 全部博客 (682)
- 软件思想 (7)
- Lucene(修真篇) (17)
- Lucene(仙界篇) (20)
- Lucene(神界篇) (11)
- Solr (48)
- Hadoop (77)
- Spark (38)
- Hbase (26)
- Hive (19)
- Pig (25)
- ELK (64)
- Zookeeper (12)
- JAVA (119)
- Linux (59)
- 多线程 (8)
- Nutch (5)
- JAVA EE (21)
- Oracle (7)
- Python (32)
- Xml (5)
- Gson (1)
- Cygwin (1)
- JavaScript (4)
- MySQL (9)
- Lucene/Solr(转) (5)
- 缓存 (2)
- Github/Git (1)
- 开源爬虫 (1)
- Hadoop运维 (7)
- shell命令 (9)
- 生活感悟 (42)
- shell编程 (23)
- Scala (11)
- MongoDB (3)
- docker (2)
- Nodejs (3)
- Neo4j (5)
- storm (3)
- opencv (1)
最新评论
-
qindongliang1922:
粟谷_sugu 写道不太理解“分词字段存储docvalue是没 ...
浅谈Lucene中的DocValues -
粟谷_sugu:
不太理解“分词字段存储docvalue是没有意义的”,这句话, ...
浅谈Lucene中的DocValues -
yin_bp:
高性能elasticsearch ORM开发库使用文档http ...
为什么说Elasticsearch搜索是近实时的? -
hackWang:
请问博主,有用solr做电商的搜索项目?
Solr中Group和Facet的用法 -
章司nana:
遇到的问题同楼上 为什么会返回null
Lucene4.3开发之第八步之渡劫初期(八)
在分析HashMap和ArrayList的源码时,我们会发现里面存储数据的数组都是用transient关键字修饰的,如下:
HashMap里面的:
```` transient Node<K,V>[] table; ````
ArrayList里面的:
```` transient Object[] elementData ````
既然用transient修饰,那就说明这个数组是不会被序列化的,那么同时我们发现了这两个集合都自定义了独自的序列化方式:
先看HashMap自定义的序列化的代码:
```` //1 private void writeObject(java.io.ObjectOutputStream s) throws IOException { int buckets = capacity(); // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); s.writeInt(buckets); s.writeInt(size); internalWriteEntries(s); } //2 public void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { Node<K,V>[] tab; if (size > 0 && (tab = table) != null) { for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) { s.writeObject(e.key); s.writeObject(e.value); } } } } ````再看HashMap自定义的反序列化的代码:
```` //1 private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + loadFactor); s.readInt(); // Read and ignore number of buckets int mappings = s.readInt(); // Read number of mappings (size) if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (if zero, use defaults) // Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); float fc = (float)mappings / lf + 1.0f; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc)); float ft = (float)cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; table = tab; // Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } } ````
这里面我们看到HashMap的源码里面自定义了序列化和反序列化的方法,序列化方法主要是把当前HashMap的buckets数量,size和里面的k,v对一一给写到了对象输出流里面,然后在反序列化的时候,再从流里面一一的解析出来,然后又重新恢复出了HashMap的整个数据结构。
接着我们看ArrayList里面自定义的序列化的实现:
```` private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } ````然后反序列化的实现:
```` private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } } ````
ArrayList里面也是把其size和里面不为null的数据给写到流里面,然后在反序列化的时候重新使用数据把数据结构恢复出来。
那么问题来了,为什么他们明明都实现了Serializable接口,已经具备了自动序列化的功能,为啥还要重新实现序列化和反序列化的方法呢?
(1)HashMap中实现序列化和反序列化的原因:
在HashMap要定义自己的序列化和反序列化实现,有一个重要的因素是因为hashCode方法是用native修饰符修饰的,也就是用它跟jvm的运行环境有关,Object类中的hashCode源码如下:
```` public native int hashCode(); ````
也就是说不同的jvm虚拟机对于同一个key产生的hashCode可能是不一样的,所以数据的内存分布可能不相等了,举个例子,现在有两个jvm虚拟机分别是A和B,他们对同一个字符串x产生的hashCode不一样:
所以导致:
在A的jvm中它的通过hashCode计算它在table数组中的位置是3
在B的jvm中它的通过hashCode计算它在table数组中的位置是5
这个时候如果我们在A的jvm中按照默认的序列化方式,那么位置属性3就会被写入到字节流里面,然后通过B的jvm来反序列化,同样会把这条数据放在table数组中3的位置,然后我们在B的jvm中get数据,由于它对key的hashCode和A不一样,所以它会从5的位置取值,这样以来就会读取不到数据。
如何解决这个问题,首先导致上面问题的主要原因在于因为hashCode的不一样从而可能导致内存分布不一样,所以只要在序列化的时候把跟hashCode有关的因素比如上面的位置属性给排除掉,就可以解决这个问题。
最简单的办法就是在A的jvm把数据给序列化进字节流,而不是一刀切把数组给序列化,之后在B的jvm中反序列化时根据数据重新生成table的内存分布,这样就来就完美解决了这个问题。
(2)ArrayList中实现序列化和反序列化的原因:
在ArrayList中,我们知道数组的长度会随着数据的插入而不断的动态扩容,每次扩容都需要增加原数组一半的长度,这而一半的长度极端情况下都是null值,所以在序列化的时候可以把这部分数据排除出去,从而节省时间和空间:
```` for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } ````
注意ArrayList在序列化的时候用的size来遍历原数组中的元素,而并不是elementData.length也就是数组的长度,而size的大小就是数组里面非null元素的个数,所以这里才采用了自定义序列化的方式。
到这里细心的朋友可能有个疑问:HashMap中也就是采用的动态数组扩容为什么它在序列化的时候用的是table.length而不是size呢,这其实很容易回答在HashMap中table.length必须是2的n次方,而且这个值会决定了好几个参数的值,所以如果也把null值给去掉,那么必须要重新的估算table.length的值,有可能造成所有数据的重新分布,所以最好的办法就是保持原样。
注意上面的null值,指的是table里面Node元素是null,而并不是HashMap里面的key等于null,而key是Node里面的一个字段。
总结:
本文主要介绍了在HashMap和ArrayList中其核心的数据结构字段为什么用transient修饰并分别介绍了其原因,所以使用序列化时,应该谨记effective java中的一句话:当一个对象的物理表示方法与它的逻辑数据内容有实质性差别时,使用默认序列化形式有N种缺陷,所以应该尽可能的根据实际情况重写序列化方法。
有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs),在后台留言咨询。 技术债不能欠,健康债更不能欠, 求道之路,与君同行。
发表评论
-
记一次log4j不打印日志的踩坑记
2019-09-22 01:58 1588### 起因 前几天一个跑有java应用的生产集群(200多 ... -
在Java里面如何解决进退两难的jar包冲突问题?
2019-07-23 19:10 1252如上图所示: es api组件依赖guava18.0 ... -
如何轻松理解二叉树的深度遍历策略
2019-07-03 23:33 1150我们知道普通的线性数据结构如链表,数组等,遍历方式单一 ... -
为什么单线程Redis性能也很出色
2019-01-21 18:02 2221高性能的服务器,不一 ... -
如何将编程语言里面的字符串转成数字?
2019-01-11 23:23 2109将字符串转成数字在很 ... -
为什么Java里面String类是不可变的
2019-01-06 18:36 1680在Java里面String类型是不可变对象,这一点毫无疑问,那 ... -
关于Java里面volatile关键字的重排序
2019-01-04 18:49 1084Java里面volatile关键字主 ... -
多个线程如何轮流打印ABC特定的次数?
2018-12-11 20:42 6061之前的一篇文章,我给 ... -
聊聊Java里面的引用传递
2018-11-16 21:21 992长久以来,在Java语言里面一直有一个争论,就是Java语言到 ... -
理解计数排序算法的原理和实现
2018-10-11 10:03 2097计数排序(Counting sort) ... -
理解Java7和8里面HashMap+ConcurrentHashMap的扩容策略
2018-09-06 11:31 3394### 前言 理解HashMap和Con ... -
关于Java里面多线程同步的一些知识
2018-07-18 09:45 1111# 关于Java里面多线程同步的一些知识 对于任何Java开 ... -
Java单例模式之双检锁深入思考
2018-07-08 12:25 3297# Java单例模式之双检锁 ... -
关于Java里面多线程同步的一些知识
2018-07-08 12:23 1122# 关于Java里面多线程同步的一些知识 对于任何Java开 ... -
重新认识同步与异步,阻塞和非阻塞的概念
2018-07-06 14:30 1475# 重新认识同步与异步 ... -
线程的基本知识总结
2018-06-27 16:27 1063### (一)创建线程的方式 (1)实现Runnable接口 ... -
Java里面volatile关键字修饰引用变量的陷阱
2018-06-25 11:42 1389# Java里面volatile关键字修饰引用变量的陷阱 如 ... -
关于Java里面的字符串拼接,你了解多少?
2018-06-25 11:28 1373# 关于Java里面的字符串 ... -
深入理解Java内存模型的语义
2018-06-25 11:39 744### 前言 Java内存模型( ... -
如何证明Java多线程中的成员变量数据是互不可见的
2018-06-21 10:09 1509前面的几篇文章主要介绍了Java的内存模型,进程和线程的定义, ...
相关推荐
Java是一种广泛使用的面向对象的编程语言,其设计目标是具有高度...理解并熟练运用这些关键字将有助于构建正确的程序逻辑,并为后续深入学习Java的面向对象特性、异常处理、多线程、集合框架等高级主题奠定坚实的基础。
本资源摘要信息涵盖了 Java 编程语言的多个方面,涵盖了集合框架、Graphic 绘图、基本数据类型、多线程编程、文件输入/输出、swing 组件等多个领域,旨在帮助开发者更好地理解和掌握 Java 编程语言。
本章节主要讲解Java集合框架的上半部分,包括集合框架的概述、Collection接口、List接口、Set接口、Map接口等。 Java学习系列(八):Java面向对象之集合框架详解(下) 本章节主要讲解Java集合框架的下半部分,...
Java集合框架: 23. List和List区别:前者是T的子类型的集合,后者是T的父类型的集合。 Java异常处理: 24. Error和Exception的区别:Error表示严重错误,Exception表示可恢复的异常。 25. 受检查异常和非受检查...
- `ArrayList`、`List` 是Java集合框架的一部分,用于存储列表数据。 14. Java图形界面绘制: - `Graphics` 和 `Graphics2D` 是用于图形绘制的类。 15. Java文件操作: - 展示了如何读取文件内容。 16. Java...
Java作为一门广泛使用的编程语言,其高级...Java序列化中,可以通过transient关键字排除字段。 总之,Java高级资深核心知识全面解析涵盖了从基础到高级的各个层面,是Java开发者提升技能和准备面试的重要参考资料。
ArrayList作为Java中最为常用的集合之一,其设计和实现体现了Java集合框架的灵活性和效率。然而,开发者在使用ArrayList时需要格外注意其线程安全问题,尤其是在多线程环境下,必须采取措施来保证数据的一致性和安全...
9. **序列化**:介绍了如何使类支持序列化,理解transient关键字的作用,以及如何实现自己的序列化版本ID。 10. **代码优化**:探讨了性能优化的策略,如避免过度优化,理解对象创建和垃圾收集的过程,以及使用...
本书全面覆盖了Java平台的高级特性,包括多线程、网络编程、高级I/O、反射、序列化、安全、JVM内部以及Java集合框架等核心内容。 1. **多线程**:Java提供了丰富的多线程支持,包括Thread类和Runnable接口,以及...
三、集合框架 1. List、Set、Queue:理解这些接口的特性,以及ArrayList、LinkedList、HashSet、LinkedHashSet、PriorityQueue等实现类的内部工作原理。 2. Map接口:了解HashMap、TreeMap、LinkedHashMap的区别,...
在Java集合框架中,如果要实现对象的比较,通常有两种方式: - 实现`Comparable`接口:这是对象自身定义比较逻辑的方式,比如`String`类就实现了这个接口,可以比较字符串的自然顺序。 - 实现`Comparator`接口:...
在《java_性能调优.pdf》这份文档中,我们可以提取出一系列关于Java性能调优的知识点,包括但不限于Java语言基础、集合框架、异常处理、设计模式、JDBC使用、I/O操作、多线程编程、JVM内存管理、JSP和Servlet等。...
Collection API是Java集合框架的一部分,它是一组类和接口,用于操作对象集合。相比于传统的Vector、Array和Hashtable,Collection API更灵活、功能更强且更规范。主要的类包括HashSet、HashMap、ArrayList、...
在了解和使用ArrayList之前,应当熟悉Java集合框架的基础知识,以及数组的动态增长机制。 知识点一:ArrayList的定义和基本特性 ArrayList是一个可以动态增长和缩减的数组实现。它允许所有的元素,包括null。...
答:Java集合框架是一个接口和类的集合,提供了一种组织和管理对象的统一方式。主要组件包括接口(如List、Set和Map)和实现这些接口的类(如ArrayList、HashSet和HashMap)。它还包括工具类(如Collections和...
9. **动态数组**:ArrayList是Java集合框架中的动态数组,可以在程序运行时调整大小。 10. **GUI组件**:在Java图形用户界面编程中,Label通常用于显示不可修改的文本信息,而Button用于交互操作,TextArea用于显示...
3. **Java标准库**:Java的标准库包括多个包,如`java.lang`(基础类库,如`String`和`System`)、`java.util`(提供集合框架、日期和时间设施等)、`java.io`(输入/输出功能)、`java.sql`(数据库连接)、`java....
【Java基础细节】是程序员面试过程中至关重要的一部分,涵盖了语言特性、数据结构、算法、集合框架、多线程、网络编程等多个领域。以下是一些关键的知识点: 1. **Java语言特性**:理解基本语法,包括类、对象、...
以上只是Java面试中的一部分常见问题和知识点,面试通常还会涉及异常处理、集合框架、IO流、设计模式、JVM内存模型、并发控制(synchronized、Lock等)、Spring框架、数据库操作、性能优化等多个方面。理解并掌握...