iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >如何处理Java中的大对象
  • 520
分享到

如何处理Java中的大对象

2023-06-30 02:06:37 520人浏览 独家记忆
摘要

这篇文章主要介绍“如何处理Java中的大对象”,在日常操作中,相信很多人在如何处理Java中的大对象问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何处理Java中的大对象”的疑惑有所帮助!接下来,请跟着小编

这篇文章主要介绍“如何处理Java中的大对象”,在日常操作中,相信很多人在如何处理Java中的大对象问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何处理Java中的大对象”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

String中的substring

我们都知道,String 在 Java 中是不可变的,如果你改动了其中的内容,它就会生成一个新的字符串。如果我们想要用到字符串中的一部分数据,就可以使用 substring 方法。

下面是Java11中String的源码

public String substring(int beginIndex) {    if (beginIndex < 0) {        throw new StringIndexOutOfBoundsException(beginIndex);    }    int subLen = length() - beginIndex;    if (subLen < 0) {        throw new StringIndexOutOfBoundsException(subLen);    }    if (beginIndex == 0) {        return this;    }    return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)                      : StringUTF16.newString(value, beginIndex, subLen);}public static String newString(byte[] val, int index, int len) {    if (String.COMPACT_STRINGS) {        byte[] buf = compress(val, index, len);        if (buf != null) {            return new String(buf, LATIN1);        }    }    int last = index + len;    return new String(Arrays.copyOfRange(val, index << 1, last << 1), UTF16);}

如上述代码所示,当我们需要一个子字符串的时候,substring 生成了一个新的字符串,这个字符串通过构造函数的 Arrays.copyOfRange 函数进行构造。

这个函数在 Java7 之后是没有问题的,但在Java6 中,却有着内存泄漏的风险,我们可以学习一下这个案例,来看一下大对象复用可能会产生的问题。下面是Java6中的代码:

public String substring(int beginIndex, int endIndex) {    if (beginIndex < 0) {        throw new StringIndexOutOfBoundsException(beginIndex);    }    if (endIndex > count) {        throw new StringIndexOutOfBoundsException(endIndex);    }    if (beginIndex > endIndex) {        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);    }    return ((beginIndex == 0) && (endIndex == count)) ?             this :            new String(offset + beginIndex, endIndex - beginIndex, value);}String(int offset, int count, char value[]) {    this.value = value;    this.offset = offset;    this.count = count;}

可以看到,它在创建子字符串的时候,并不只拷贝所需要的对象,而是把整个 value 引用了起来。如果原字符串比较大,即使不再使用,内存也不会释放。

比如,一篇文章内容可能有几兆,我们仅仅是需要其中的摘要信息,也不得不维持整个的大对象。

有一些工作年限比较长的面试官,对 substring 还停留在 jdk6 的印象,但其实,Java 已经将这个 bug 给修改了。如果面试时遇到这个问题,保险起见,可以把这个改善过程答出来。

这对我们的借鉴意义是:如果你创建了比较大的对象,并基于这个对象生成了一些其他的信息,这个时候,一定要记得去掉和这个大对象的引用关系。

集合大对象扩容

对象扩容,在 Java 中是司空见惯的现象,比如 StringBuilder、StringBuffer、HashMap,ArrayList 等。概括来讲,Java 的集合,包括 List、Set、Queue、Map 等,其中的数据都不可控。在容量不足的时候,都会有扩容操作,扩容操作需要重新组织数据,所以都不是线程安全的。

我们先来看下 StringBuilder 的扩容代码:

void expandCapacity(int minimumCapacity) {    int newCapacity = value.length * 2 + 2;    if (newCapacity - minimumCapacity < 0)        newCapacity = minimumCapacity;    if (newCapacity < 0) {        if (minimumCapacity < 0) // overflow             throw new OutOfMemoryError();        newCapacity = Integer.MAX_VALUE;    }    value = Arrays.copyOf(value, newCapacity);}

容量不够的时候,会将内存翻倍,并使用 Arrays.copyOf 复制源数据。

下面是 HashMap 的扩容代码,扩容后大小也是翻倍。它的扩容动作就复杂得多,除了有负载因子的影响,它还需要把原来的数据重新进行散列,由于无法使用 native 的 Arrays.copy 方法,速度就会很慢。

void addEntry(int hash, K key, V value, int bucketIndex) {    if ((size >= threshold) && (null != table[bucketIndex])) {        resize(2 * table.length);        hash = (null != key) ? hash(key) : 0;        bucketIndex = indexFor(hash, table.length);    }    createEntry(hash, key, value, bucketIndex);}void resize(int newCapacity) {    Entry[] oldTable = table;    int oldCapacity = oldTable.length;    if (oldCapacity == MAXIMUM_CAPACITY) {        threshold = Integer.MAX_VALUE;        return;    }    Entry[] newTable = new Entry[newCapacity];    transfer(newTable, initHashSeedAsNeeded(newCapacity));    table = newTable;    threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);}

List 的代码大家可自行查看,也是阻塞性的,扩容策略是原长度的 1.5 倍。

由于集合在代码中使用的频率非常高,如果你知道具体的数据项上限,那么不妨设置一个合理的初始化大小。比如,HashMap 需要 1024 个元素,需要 7 次扩容,会影响应用的性能。面试中会频繁出现这个问题,你需要了解这些扩容操作对性能的影响。

但是要注意,像 HashMap 这种有负载因子的集合(0.75),初始化大小 = 需要的个数/负载因子+1,如果你不是很清楚底层的结构,那就不妨保持默认。

接下来,我将从数据的结构纬度和时间维度出发,讲解一下应用层面的优化

保持合适的对象粒度

给你分享一个实际案例:我们有一个并发量非常高的业务系统,需要频繁使用到用户的基本数据。

如下图所示,由于用户的基本信息,都是存放在另外一个服务中,所以每次用到用户的基本信息,都需要有一次网络交互。更加让人无法接受的是,即使是只需要用户的性别属性,也需要把所有的用户信息查询,拉取一遍。

如何处理Java中的大对象

为了加快数据的查询速度,对数据进行了初步的缓存,放入到了 Redis 中,查询性能有了大的改善,但每次还是要查询很多冗余数据。

原始的 Redis key 是这样设计的:

type: string key: user_${userid} value: JSON

这样的设计有两个问题:

查询其中某个字段的值,需要把所有 json 数据查询出来,并自行解析;

更新其中某个字段的值,需要更新整个 json 串,代价较高。

针对这种大粒度 json 信息,就可以采用打散的方式进行优化,使得每次更新和查询,都有聚焦的目标。

接下来对 Redis 中的数据进行了以下设计,采用 hash 结构而不是 json 结构:

type: hash key: user_${userid} value: {sex:f, id:1223, age:23}

这样,我们使用 hget 命令,或者 hmget 命令,就可以获取到想要的数据,加快信息流转的速度。

Bitmap 把对象变小

除了以上操作,还能再进一步优化吗?比如,我们系统中就频繁用到了用户的性别数据,用来发放一些礼品,推荐一些异性的好友,定时循环用户做一些清理动作等;或者,存放一些用户的状态信息,比如是否在线,是否签到,最近是否发送信息等,从而统计一下活跃用户等。那么对是、否这两个值的操作,就可以使用 Bitmap 这个结构进行压缩。

这里还有个高频面试问题,那就是 Java 的 Boolean 占用的是多少位?

在 Java 虚拟机规范里,描述是:将 Boolean 类型映射成的是 1 和 0 两个数字,它占用的空间是和 int 相同的 32 位。即使有的虚拟机实现把 Boolean 映射到了 byte 类型上,它所占用的空间,对于大量的、有规律的 Boolean 值来说,也是太大了。

如代码所示,通过判断 int 中的每一位,它可以保存 32 个 Boolean 值!

int a= 0b0001_0001_1111_1101_1001_0001_1111_1101;

Bitmap 就是使用 Bit 进行记录的数据结构,里面存放的数据不是 0 就是 1。还记得我们在之前 《分布式缓存系统必须要解决的四大问题》中提到的缓存穿透吗?就可以使用 Bitmap 避免,Java 中的相关结构类,就是 java.util.BitSet,BitSet 底层是使用 long 数组实现的,所以它的最小容量是 64。

10 亿的 Boolean 值,只需要 128MB 的内存,下面既是一个占用了 256MB 的用户性别的判断逻辑,可以涵盖长度为 10 亿的 ID。

static BitSet missSet = new BitSet(010_000_000_000); static BitSet sexSet = new BitSet(010_000_000_000); String getSex(int userId) {     boolean notMiss = missSet.get(userId);     if (!notMiss) {         //lazy fetch         String lazySex = dao.getSex(userId);         missSet.set(userId, true);         sexSet.set(userId, "female".equals(lazySex));     }     return sexSet.get(userId) ? "female" : "male"; }

这些数据,放在堆内内存中,还是过大了。幸运的是,Redis 也支持 Bitmap 结构,如果内存有压力,我们可以把这个结构放到 Redis 中,判断逻辑也是类似的。

再插一道面试算法题:给出一个 1GB 内存的机器,提供 60亿 int 数据,如何快速判断有哪些数据是重复的?

大家可以类比思考一下。Bitmap 是一个比较底层的结构,在它之上还有一个叫作布隆过滤器的结构(Bloom Filter),布隆过滤器可以判断一个值不存在,或者可能存在。

如何处理Java中的大对象

如图,它相比较 Bitmap,它多了一层 hash 算法。既然是 hash 算法,就会有冲突,所以有可能有多个值落在同一个 bit 上。它不像 HashMap一样,使用链表或者红黑树来处理冲突,而是直接将这个hash槽重复使用。从这个特性我们能够看出,布隆过滤器能够明确表示一个值不在集合中,但无法判断一个值确切的在集合中。

Guava 中有一个 BloomFilter 的类,可以方便地实现相关功能。

上面这种优化方式,本质上也是把大对象变成小对象的方式,在软件设计中有很多类似的思路。比如像一篇新发布的文章,频繁用到的是摘要数据,就不需要把整个文章内容都查询出来;用户的 feed 信息,也只需要保证可见信息的速度,而把完整信息存放在速度较慢的大型存储里。

数据的冷热分离

数据除了横向的结构纬度,还有一个纵向的时间维度,对时间维度的优化,最有效的方式就是冷热分离。

所谓热数据,就是靠近用户的,被频繁使用的数据;而冷数据是那些访问频率非常低,年代非常久远的数据。

同一句复杂的 sql,运行在几千万的数据表上,和运行在几百万的数据表上,前者的效果肯定是很差的。所以,虽然你的系统刚开始上线时速度很快,但随着时间的推移,数据量的增加,就会渐渐变得很慢。

冷热分离是把数据分成两份,如下图,一般都会保持一份全量数据,用来做一些耗时的统计操作。

如何处理Java中的大对象

由于冷热分离在工作中经常遇到,所以面试官会频繁问到数据冷热分离的方案。下面简单介绍三种:

数据双写

把对冷热库的插入、更新、删除操作,全部放在一个统一的事务里面。由于热库(比如 MySQL)和冷库(比如 HBase)的类型不同,这个事务大概率会是分布式事务。在项目初期,这种方式是可行的,但如果是改造一些遗留系统,分布式事务基本上是改不动的,我通常会把这种方案直接废弃掉。

写入 MQ 分发

通过 MQ 的发布订阅功能,在进行数据操作的时候,先不落库,而是发送到 MQ 中。单独启动消费进程,将 MQ 中的数据分别落到冷库、热库中。使用这种方式改造的业务,逻辑非常清晰,结构也比较优雅。像订单这种结构比较清晰、对顺序性要求较低的系统,就可以采用 MQ 分发的方式。但如果你的数据库实体量非常大,用这种方式就要考虑程序的复杂性了。

使用 Binlog 同步

针对 Mysql,就可以采用 Binlog 的方式进行同步,使用 Canal 组件,可持续获取最新的 Binlog 数据,结合 MQ,可以将数据同步到其他的数据源中。

思维发散

对于结果集的操作,我们可以再发散一下思维。可以将一个简单冗余的结果集,改造成复杂高效的数据结构。这个复杂的数据结构可以代理我们的请求,有效地转移耗时操作。

比如,我们常用的数据库索引,就是一种对数据的重新组织、加速。B+ tree 可以有效地减少数据库与磁盘交互的次数,它通过类似 B+ tree 的数据结构,将最常用的数据进行索引,存储在有限的存储空间中。

还有就是,在 rpc 中常用的序列化。有的服务是采用的 SOAP 协议的 WEBService,它是基于 XML 的一种协议,内容大传输慢,效率低下。现在的 Web 服务中,大多数是使用 json 数据进行交互的,json 的效率相比 SOAP 就更高一些。

另外,大家应该都听过 Google 的 protobuf,由于它是二进制协议,而且对数据进行了压缩,性能是非常优越的。protobuf 对数据压缩后,大小只有 json 的 1/10,xml 的 1/20,但是性能却提高了 5-100 倍。

protobuf 的设计是值得借鉴的,它通过 tag|leng|value 三段对数据进行了非常紧凑的处理,解析和传输速度都特别快。

到此,关于“如何处理Java中的大对象”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: 如何处理Java中的大对象

本文链接: https://www.lsjlt.com/news/327088.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何处理Java中的大对象
    这篇文章主要介绍“如何处理Java中的大对象”,在日常操作中,相信很多人在如何处理Java中的大对象问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何处理Java中的大对象”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-30
  • Java如何应用于大数据处理中的对象管理?
    随着大数据时代的到来,数据的处理已经成为了人们日常工作中不可或缺的一部分。在大数据处理中,对象管理是非常重要的一环。Java作为一种优秀的编程语言,它在大数据处理中的对象管理方面也发挥了重要作用。本文将会详细介绍Java在大数据处理中的对...
    99+
    2023-10-12
    大数据 对象 numpy
  • 详解处理Java中的大对象的方法
    目录String中的substring集合大对象扩容保持合适的对象粒度Bitmap 把对象变小数据的冷热分离数据双写写入 MQ 分发使用 Binlog 同步思维发散小结本文我们将讲解...
    99+
    2022-11-13
  • Java如何处理大数据中的对象操作问题?
    随着数据量的不断增大,Java在处理大数据中的对象操作问题上面也会遇到一些困难。本文将介绍Java中处理大数据中的对象操作问题,并提供一些代码示例。 一、使用缓存来优化对象操作 在处理大数据量时,Java中的对象操作会变得十分缓慢,因为内...
    99+
    2023-10-12
    大数据 对象 numpy
  • 如何在Java中高效地处理大数据对象?
    在当今数据爆炸的时代,处理大数据已经成为了各行业都需要面对的问题。而在Java开发中,如何高效地处理大数据对象,也是一个需要解决的难题。本文将从以下几个方面为您详细介绍如何在Java中高效地处理大数据对象。 一、使用流式API Java 8...
    99+
    2023-10-12
    大数据 对象 numpy
  • Java中如何处理Path对象?
    Path对象是Java中处理文件和目录路径的核心类之一,它提供了丰富的方法和功能,让我们能够轻松地操作文件和目录路径。在本文中,我们将介绍Java中如何处理Path对象,包括创建、访问、操作和处理Path对象。 一、创建Path对象 创建...
    99+
    2023-09-21
    path 对象 bash
  • 你知道Java中如何处理大数据对象的加载吗?
    Java作为一种广泛使用的编程语言,其在处理大数据对象的加载方面也有自己的一套处理方法。本文将会介绍Java中如何处理大数据对象的加载,并给出相应的示例代码。 一、内存限制 在处理大数据对象时,内存是一个非常重要的考虑因素。Java中内存的...
    99+
    2023-08-16
    大数据 对象 load
  • Python中如何处理大数据文件中的对象?
    在现代数据科学中,我们经常需要处理大型数据集。这些数据可能以多种格式存储,包括文本、CSV、JSON、数据库、图像和视频等。在这些数据集中,一些常见的数据类型是对象,如列表、元组、字典、类实例等。在本文中,我们将探讨如何在Python中处...
    99+
    2023-10-26
    对象 文件 大数据
  • Java中的对象编程算法:如何处理大量数组数据?
    在Java中,处理大量数组数据是一种常见的需求。无论是在数据分析、图像处理还是机器学习等领域,都需要高效地处理大量的数据。在这种情况下,使用对象编程算法能够有效地提高代码的可读性和可维护性,并且能够更快地处理大量数据。 本文将介绍Java...
    99+
    2023-10-31
    对象 编程算法 数组
  • Java的大数据处理,如何解决对象的加载问题?
    随着大数据时代的到来,数据量的爆炸式增长,对于Java开发人员来说,如何高效地处理大数据成为了一个重要的问题。在处理大数据时,Java中的对象加载问题是一个非常关键的问题。在这篇文章中,我们将探讨Java的大数据处理中对象加载的问题,以及...
    99+
    2023-08-16
    大数据 对象 load
  • Oracle中大对象(LOB)处理方法
    目录一、LOB数据类型分类1、按存储数据的类型分2、按存储方式分3、Null LOBs与Empty LOBs二、LOB写入三、LOB读取四、BFile文件大对象(存储在操作系统文件中...
    99+
    2022-11-13
  • 大数据处理中的Java对象管理和Numpy的数据处理技巧对比分析。
    大数据处理中的Java对象管理和Numpy的数据处理技巧对比分析 随着大数据时代的到来,数据处理技术也在不断地发展和进步。在数据处理过程中,Java和Numpy是两种非常流行的技术,它们在数据处理中都有着独特的优势和不足。本文将对Java对...
    99+
    2023-10-12
    大数据 对象 numpy
  • Numpy在大数据处理中的优势和Java的对象处理技巧对比分析?
    Numpy和Java都是在大数据处理中广泛使用的工具。Numpy是Python中的一个科学计算库,而Java则是一种面向对象的编程语言。本文将分析Numpy在大数据处理中的优势和Java的对象处理技巧,并对两者进行对比分析。 一、Numpy...
    99+
    2023-10-12
    大数据 对象 numpy
  • 如何利用Python处理大数据文件中的对象?
    随着数据量的快速增长,处理大数据已成为许多企业和组织所面临的最大挑战之一。Python语言因其简单易学、灵活和高效的特点,成为了处理大数据的首选语言之一。本文将介绍如何利用Python处理大数据文件中的对象,为读者提供一些实用的技巧和工具。...
    99+
    2023-10-26
    对象 文件 大数据
  • Java大数据处理中的对象加载问题你了解吗?
    随着大数据时代的到来,数据处理变得越来越复杂,而Java作为一种广泛应用于企业级应用程序的语言,也必须应对大数据处理方面的挑战。在处理大数据时,Java程序需要加载大量的对象,因此对象加载的效率成为了一个关键问题。本文将介绍Java大数据...
    99+
    2023-08-16
    大数据 对象 load
  • 如何使用Python处理HTTP请求中的大数据对象?
    如何使用Python处理HTTP请求中的大数据对象? 在现代互联网的世界里,HTTP请求是非常常见的一种数据交互方式,而随着数据量的不断增大,HTTP请求中的数据也越来越大。在这种情况下,如何使用Python处理HTTP请求中的大数据对象成...
    99+
    2023-10-21
    http 大数据 对象
  • 如何理解JVM中Java对象的大小与引用类型
    本篇文章为大家展示了如何理解JVM中Java对象的大小与引用类型,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。小编总结了JVM概念中的Java对象的大小,以及三种引用类型的定义与区分。基本数据的类型...
    99+
    2023-06-17
  • Java中的对象重定向和并发:如何处理?
    在Java编程中,对象重定向和并发是两个常见的问题。如果你的程序需要处理大量的数据、多个线程或者需要进行网络通信,那么你可能需要考虑这些问题。在本文中,我们将介绍Java中的对象重定向和并发问题,以及如何处理这些问题。 对象重定向 在Ja...
    99+
    2023-10-15
    对象 重定向 并发
  • Java中的对象处理技巧和Numpy的数据分析能力如何应用于大数据处理?
    随着大数据时代的到来,如何高效地处理海量数据成为了亟待解决的问题。Java作为一门面向对象的编程语言,拥有优秀的对象处理技巧,而Numpy则是一款专门用于科学计算的Python库,具有强大的数据分析能力。本文将结合Java中的对象处理技巧...
    99+
    2023-10-12
    大数据 对象 numpy
  • Java中如何使用Bash脚本处理Path对象?
    在Java中,Path类是一个非常有用的类,它可以用来表示文件或目录的路径。但是,在某些情况下,我们可能需要使用Bash脚本来处理Path对象。本文将介绍如何在Java中使用Bash脚本来处理Path对象。 获取Bash脚本路径 首先...
    99+
    2023-09-21
    path 对象 bash
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作