本机速度文件支持的“纯” Java大数据存储

动机

所有这一切始于意识到我买不起足够大的计算机。 音频处理需要大量的内存。 Audacity是一款出色的免费音频处理器,它使用文件支持的存储系统对其进行管理。 这是解决此类问题的常用方法,在这些问题中,我们存储了大量信息,并希望随机访问这些信息。 因此,我想为Sonic Field (我的宠物音频处理/合成项目)开发一个系统,该系统提供了相同的基于磁盘的强大存储方法,但使用的是纯Java。

去年下半年,我开始使用此工具,并在Java Advent日历( http://www.javaadvent.com/2014/12/a-serpentine-path-to-music.html )概述中对此进行了简要介绍。 。 基于磁盘的内存使Sonic Field能够处理音频系统,而这些音频系统在我不起眼的16 GB笔记本电脑上需要大量内存。 例如,最近的一块创建了超过50 GB的内存:

虽然这是一个突破,但效率也很低。 诸如混合之类的内存密集型操作是该系统的瓶颈。 在这里,我通过实现相同的系统将Java变成了一个存储动力库,但效率更高得多。 我怀疑我已经接近Java不再对C ++性能不利的极限。

去年,我对该方法进行了概述。 今年,我将深入研究执行绩效的细节。 这样做时,我将说明如何消除传统Java内存访问技术的开销,然后扩展思想,以更通用的方法在JVM编程中共享和持久存储大内存系统。

什么是分段存储?

我承认这里有很多概念。 首先要弄清楚的是Java中大型内存系统的常规内存管理效率如何低下。 实际上,我要说的很清楚,我不是在谈论垃圾回收。 多年使用Java和C ++的经验告诉我,收集或显式堆管理都不有效也不容易实现。 我根本不在讨论这个。 JVM对大型内存系统进行管理的问题是由于其边界检查和对象模型。 使用内存池时,这成为了焦点。

随着延迟或吞吐量性能变得比内存使用更为关键,必须要打破内存池。 我们没有一个将所有事物混合在一起的伟大光荣的内存系统,而是拥有大小相同的对象池。 如果未充分使用池,或者如果映射到池块中的元素小于块本身,则与纯堆相比需要更多的内存。 但是,池的管理确实非常快。

在这篇文章中,我将讨论池支持的分段存储。 分段存储基于池,但是允许分配比单个池块更大的存储容器。 这个想法是,一个存储容器(例如1 GB)可以由一组块(例如每个1 MB)组成。 分段存储区域不一定由连续的块组成。 确实,这是其最重要的功能。 它由来自备用池的大小相等的块组成,但是这些块分散在虚拟地址空间中,甚至可能没有顺序。 有了这一点,我们就具有了池的请求和释放效率,但是却接近于堆的内存使用效率,并且无需担心碎片。

首先让我们看一下游泳池的样子。 然后我们可以返回细分。

在此讨论中,池包括以下部分:

  1. 大小相等的内存块的池(不一定全部在一个数据结构中)。
  2. 一个或多个已用块的列表。
  3. 一份免费的大块清单。

要从池中创建分段内存分配,我们有一个循环:

  1. 创建一个存储块(数组或类似的东西)的容器。 将该段称为分配的段列表。
  2. 从空闲列表中取出一部分内存,并将其添加到段列表中。
  3. 查看段列表包含的总内存是否等于或大于所需的总内存。
  4. 如果不是,请从2开始重复。

现在,我们有了一个分配段列表,其中至少有足够的内存来满足需求。 当我们释放该内存时,我们只需将这些块放回到空闲列表中。 从中我们可以看到,很快,空闲列表中的块将不再是有序的,即使我们按地址对它们进行排序,它们也不会是连续的。 因此,任何分配将具有足够的内存,但没有任何连续的顺序。

这是一个可行的例子

我们将考虑10个1兆字节的块,我们可以将它们称为1,2…10,它们是按顺序排列的。

Start:Free List: 1 2 3 4 5 6 7 8 9 10
Allocate a 2.5 megabyte store:Free List: 1 2 3 4 5 6 7Allocated Store A: 8 9 10
Allocate a 6 megabyte store:Free List: 1 Allocated Store A: 8 9 10Allocated Store A: 7 6 5 4 3 2
Free Allocated Store A:Free List: 10 9 8 1Allocated Store A: 7 6 5 4 3 2
Allocate a 3.1 megabyte store:Free List: Allocated Store A: 7 6 5 4 3 2Allocated Store C:10 9 8 1

可以注意到,这种方法对于某些情况(例如64位C ++)来说是好的,但是对于Java来说,它的真正威力是。 在当前的JVM中,最大可寻址数组或ByteBuffer仅包含2 ** 31个元素,分段存储提供了一种有效的方式来处理大量内存,并在需要时使用内存映射文件来支持该内存。考虑到我们需要200亿双,无法将它们分配到数组或ByteBuffer中; 但是我们可以使用分段内存来实现目标。

在Java中将匿名虚拟内存用于很大的内存对象可能效率不高。 在某些情况下,我们要处理的内存比计算机上的RAM大得多,与使用匿名映射空间相比,最好使用内存映射文件。 这意味着JVM不会(在一定程度上)与其他程序争夺交换空间,但更重要的是,垃圾收集的内存会分配对象访问权限,这对于匿名虚拟内存来说尤其糟糕。 我们想要集中访问时域中的特定页面,以便我们吸引尽可能少的硬页面错误。 我在这里讨论了该领域的其他概念: https : //jaxenter.com/high-speed-multi-threaded-virtual-memory-in-java-105629.html 。

鉴于这种。 如果将内存映射文件的要求缩小到200亿双,那么我们甚至无法在sun.misc.Unsafe(请参阅下文)中使用magic来提供帮助。 没有JNI,我们可以用Java管理的最大的内存映射文件“块”仅为2 ^ 31字节。 正是由于对内存映射文件的需求以及分段存储方法固有的分配/释放效率,才使我将其用于Sonic Field(在此我经常需要在16G机器上管理100G以上的内存)。

深入实施

现在,我们有一套清晰的想法可以实施。 我们需要映射的字节缓冲区。 每个缓冲区是池中空闲块的一个块。 当我们想要分配一个存储容器时,我们需要将一些映射的字节缓冲区块从空闲池中取出并放入我们的容器中。 释放容器后,我们将块返回到空闲池。 简单,高效,清洁。

另外,重要的一点是,映射的字节缓冲区实际上是带有文件后备内存的java.nio.DirectByteBuffer对象。 稍后我们将使用此概念。 现在我们可以将它们视为ByteBuffers。

在Sonic Field上(这是我使用映射的字节缓冲区开发分段存储技术的代码。–请参阅https://github.com/nerds-central/SonicFieldRepo )。 在该代码库中,我定义了以下内容:

private static final long  CHUNK_LEN        = 1024 * 1024;

为了获得样本,我们可以将每个块视为CHUNK_LEN ByteBuffer。 在我的加速工作之前,用于从分配的内存块访问元素的代码是:

private static final long  CHUNK_SHIFT      = 20;private static final long  CHUNK_MASK       = CHUNK_LEN - 1;
...public final double getSample(int index){long bytePos = index << 3;long pos = bytePos & CHUNK_MASK;long bufPos = (bytePos - pos) >> CHUNK_SHIFT;return chunks[(int) bufPos].getDouble((int) pos);}

因此,在这种情况下,分配的段列表是ByteBuffers的数组:

  1. 通过将所需索引除以块大小来找到列表中的索引(使用shift以获得效率)。
  2. 通过取模数找到找到的块中的索引(使用二进制以提高效率)。
  3. 使用getDouble内在方法查找实际值(看起来像一个方法,但编译器对此有所了解,而方法调用除外)。

所有这些看起来都不错,但是效果却不尽如人意,因为Java在内存中布置对象的方式存在一些基本问题,这些问题会阻止对分段访问进行适当的优化。 从表面上看,访问分段的内存区域应该是一些非常快的移位和逻辑操作以及间接查找,但这对于Java而言并不可行。 所有的问题都发生在这一行:

return chunks[(int) bufPos].getDouble((int) pos);

这是此行要做的:

  1. 从其句柄中查找块对象。
  2. 边界检查。
  3. 从其数据区域获取数据。
  4. 从该对象的ByteBuffer句柄中查找实际对象。
  5. 动态查找其长度(它可以更改,因此这是安全点和对象字段查找)。
  6. 边界检查。
  7. 检索数据。

真? 是的,JVM完成了所有痛苦的事情。 它不仅需要大量指令,而且还需要在内存中跳转,从而导致所有随后的高速缓存行刷新和内存暂停。

我们如何对此进行改进? 请记住,我们的ByteBuffers是DirectByteBuffers,这意味着它们的数据未存储在Java堆中; 在整个对象生命周期中,它位于相同的虚拟地址位置。 我敢打赌,您已经猜到这里的关键是使用sun.misc.Unsafe。 是的; 我们可以通过使用堆内存来绕过所有此对象查找。 这样做意味着弯曲一些Java和JVM规则,但是值得这样做。

从现在开始,我讨论的所有内容都与Java 1.8 x86_64有关。 将来的版本可能会破坏这种方法,因为它不符合标准。

考虑一下:

private static class ByteBufferWrapper{public long       address;public ByteBuffer buffer;public ByteBufferWrapper(ByteBuffer b) throwsNoSuchMethodException,SecurityException,IllegalAccessException,IllegalArgumentException,InvocationTargetException{Method addM = b.getClass().getMethod("address");addM.setAccessible(true);address = (long) addM.invoke(b);buffer = b;}}

我们正在做的是获取DirectByteBuffer中存储的数据在内存中的地址。 为此,我使用反射,因为DirectByteBuffer是包私有的。 DirectByteBuffer上有一个称为address()的方法,该方法返回long。 在x86_64上,地址的大小(64位)与长度相同。 虽然long的值是带符号的,但我们只能将long用作二进制数据,而忽略其数值。 因此,从address()返回的long实际上是缓冲区存储区起始位置的虚拟地址。

与“普通” JVM存储(例如,数组)不同,DirectByteBuffer的存储是“堆外”。 它是虚拟内存,与其他任何虚拟内存一样,但不属于垃圾收集器,不能被垃圾收集器移动; 这与我们访问它的速度和方式有很大的不同。 请记住,对于给定的DirectByteBuffer对象,address()返回的地址永远不会更改; 因此,我们可以“永远”使用该地址,并避免对象查找。

介绍sun.misc.Unsafe

相信在DirectByteBuffer上调用getDouble(int)是超级高效的,虽然看起来很可爱,但事实并非如此。 尽管方法是固有的,但边界检查会减慢它的运行速度(JVM JIT编译器知道并可以用机器代码代替魔术方法而不是按常规方式编译的魔术函数)。 但是,使用我们的地址,我们现在可以使用sun.misc.Unsafe来访问存储。

而不是:

b.getDouble(pos);

我们可以:

unsafe.getDouble(address+pos);

不安全的版本也是固有的,可以编译成与C编译器(如gcc)几乎相同的机器代码。 换句话说,它尽可能快。 没有对象取消引用或边界检查,它只是从地址加载双精度型。

商店等效项是:

unsafe.putDouble(address+pos,value);

这是什么“不安全”的东西? 我们通过另一个反思技巧来解决这个问题:

private static Unsafe getUnsafe(){try{Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);return (Unsafe) f.get(null);}catch (Exception e){throw new RuntimeException(e);}}private static final Unsafe unsafe = getUnsafe();

将不安全的单例加载到最终的静态字段中很重要。 这使编译器可以假设对象引用永不更改,因此将生成最佳代码。

现在,我们可以非常快速地从DirectByteBuffer中获取数据,但是我们具有分段存储模型,因此我们需要非常快速地获取正确字节缓冲区的地址。 如果将它们存储在数组中,则会冒着数组边界检查和数组对象取消引用步骤的风险。 我们可以通过进一步使用不安全和混乱的内存来摆脱这些漏洞。

private final long  chunkIndex;
...try{// Allocate the memory for the index - final so do it herelong size = (1 + ((l << 3) >> CHUNK_SHIFT)) << 3;allocked = chunkIndex = unsafe.allocateMemory(size);if (allocked == 0){throw new RuntimeException("Out of memory allocating " + size);}makeMap(l << 3l);}catch (Exception e){throw new RuntimeException(e);}

再次,我们使用“最终”技巧使编译器进行最佳优化。 这里的决赛很长,只是一个地址。 我们可以不安全地直接分配堆内存。 想象中的实现此功能的函数是allocateMemory(long)。 这将返回一个存储在chunkIndex中的long。 allocateMemory(long)实际上是分配字节,但是我们要存储有效的long类型数组(地址); 这就是位旋转逻辑在计算大小时所执行的操作。

现在,我们已经有了大块的堆外内存,足以存储我们存储容器的DirectByteBuffer段的地址,我们可以放入地址并使用不安全的地址进行检索。

在存储构建过程中,我们:

// now we have the chunks we get the address of the underlying memory// of each and place that in the off heap lookup so we no longer// reference them via objects but purely as raw memorylong offSet = 0;for (ByteBufferWrapper chunk : chunks){unsafe.putAddress(chunkIndex + offSet, chunk.address);offSet += 8;}

这意味着我们获取和设置数据的新代码确实可以非常简单:

private long getAddress(long index){long bytePos = index << 3;long pos = bytePos & CHUNK_MASK;long bufPos = (bytePos - pos) >> CHUNK_SHIFT;long address = chunkIndex + (bufPos << 3);return unsafe.getAddress(address) + pos;}/* (non-Javadoc)* @see com.nerdscentral.audio.SFSignal#getSample(int)*/@Overridepublic final double getSample(int index){return unsafe.getDouble(getAddress(index));}/* (non-Javadoc)* @see com.nerdscentral.audio.SFSignal#setSample(int, double)*/@Overridepublic final double setSample(int index, double value){unsafe.putDouble(getAddress(index), value);return value;}

这样做的妙处在于完全没有对象操作或边界检查。 好的,如果有人要求提供超出范围的样本,JVM将崩溃。 那可能不是一件好事。 这种编程对于许多Java程序员来说是非常陌生的,我们需要非常认真地对待它的危险。 但是,与原始版本相比,它确实相当快。

在我的实验中,我发现默认的JVM内联设置过于保守,无法充分利用这种方法。 通过以下命令行调整,我看到了大幅度的加速(性能提高了两倍)。

-XX:MaxInlineSize=128 -XX:InlineSmallCode=1024

这些使JVM可以更好地利用可利用的额外性能,而不必强制执行边界检查和对象查找。 通常,我不建议您摆弄JVM内联设置,但是在这种情况下,我具有真正的基准测试经验,可以显示出对复杂的堆外访问工作的好处。

测试–快多少?

我编写了以下Jython进行测试:

import math
from java.lang import Systemsf.SetSampleRate(192000)
count=1000
ncount=100def test():t1=System.nanoTime()for i in range(1,ncount):signal=sf.Mix(+signal1,+signal2)signal=sf.Realise(signal)-signalt2=System.nanoTime()d=(t2-t1)/1000000.0print "Done: " + str(d)return dsignal1=sf.Realise(sf.WhiteNoise(count))
signal2=sf.Realise(sf.WhiteNoise(count))
print "WARM"
for i in range(1,100):test()print "Real"
total=0.0
for i in range(1,10):total+=test()print "Mean " + str(total/9.0)-signal1
-signal2

这样做是创建一些存储的双打,然后创建新的双打,并一遍又一遍地从旧读入。 请记住,我们正在使用由池支持的分段存储。 因此,我们只在开始时才真正分配该存储,然后才对“块”进行回收。 这种体系结构意味着我们的执行时间主要由执行getSample和setSample而不是分配或任何其他工具决定。

我们的堆外系统快多少? 在装有Java 1.8.0的Macbook Pro Retina I7机器上,我得到了“真实”(即预热后)操作的数字(越小越好):

对于不安全的内存模型:

  • 完成:187.124
  • 完成:175.007
  • 完成:181.124
  • 完成:175.384
  • 完成:180.497
  • 完成:180.688
  • 完成:183.309
  • 完成:178.901
  • 完成:181.746
  • 均值180.42

对于传统的内存模型:

  • 完成:303.008
  • 完成:328.763
  • 完成:299.701
  • 完成:315.083
  • 完成:306.809
  • 完成:302.515
  • 完成:304.606
  • 完成:300.291
  • 完成:342.436
  • 均值311.468

因此,我们的不安全内存模型比传统Java方法快1.73倍

为什么快1.73倍

我们可以明白为什么。

如果我们回顾一下从传统的DirectByteBuffer和数组方法中读取双精度数据所需的事项列表:

  1. 从其句柄中查找块对象。
  2. 边界检查。
  3. 从其数据区域获取数据。
  4. 从该对象的ByteBuffer句柄中查找实际对象。
  5. 动态查找其长度(它可以更改,因此这是安全点和对象字段查找)。
  6. 边界检查。
  7. 检索数据。

使用新方法,我们可以:

  1. 检索块的地址
  2. 从该块中检索数据

不仅发出的机器指令减少了很多,而且存储器访问的位置也变得更加本地化,​​这几乎可以肯定地提高了数据处理期间的缓存使用率。

如此处所述,用于存储系统快速版本的源代码为: https : //github.com/nerds-central/SonicFieldRepo/blob/cf6a1b67fb8dd07126b0b1274978bd850ba76931/SonicField/src/com/nerdscentral/audio/SFData.java

我希望您(读者)发现一个我没有解决的大问题! 每当我的代码创建分段存储容器时,我的代码都会分配堆内存。 但是,垃圾回收器不会释放此内存。 我们可以尝试使用终结器来释放代码,但是有很多原因使它不是一个好主意。

我的解决方案是使用显式资源管理。 Sonic Field使用try资源,通过引用计数来管理其内存。 当特定存储容器的引用计数达到零时,该容器将被释放,从而将其存储块放回空闲列表中,并使用不安全的方法来释放地址查找内存。

其他用途和新思路

大约一年前,我发布了“ 保持相关性的Java Power功能 ”。 我猜这是一个有争议的帖子,并不是我所谈论的每个人都对我的想法感到满意(至少可以这样说)。 尽管如此,我仍然认为JVM面临着挑战。 Java和JVM本身的复杂多线程模型不一定代表人们认为它应该在多核计算领域带来的巨大好处。 使用多个通过共享内存或套接字进行通信的小进程仍然引起了人们的极大兴趣。 随着基于RDMA的网络的缓慢但不可避免的增长,这些方法对人们来说将越来越自然。

Java和JVM语言似乎设法使自己独特地无法利用这些思维上的转变。 通过开发“围墙花园”方法,JVM在内部工作中变得非常高效,但是在与其他进程一起工作时却表现不佳。 这既是性能问题,也是稳定性问题; 无论我们如何努力,JVM总是有可能崩溃或进入不稳定状态(有人遇到OutOfMemoryError吗?)。 在生产系统中,这通常需要几个小型JVM实例一起工作,因此,如果一个实例消失了,生产系统就会停下来。 内存映射文件是帮助持久保存数据的好方法,即使JVM进程消失了。

所有这些问题导致我对另一个原因感到非常感兴趣,因为我对JVM的高效偏移,映射文件体系结构非常感兴趣。 这项技术位于共享内存和映射文件技术的重叠处,这些技术现在正在推动高速,稳定的生产环境。 虽然我在这里讨论的系统是针对单个JVM的,但使用堆原子(请参见此处: http ://nerds-central.blogspot.co.uk/2015/05/synchronising-sunmiscunsafe-with-c.html),我们可以空闲列表乱用,并在进程之间共享。 然后,共享内存队列还可以对分段存储分配和利用进行进程间仲裁。 突然,分段存储模型成为JVM和其他技术(Python,C ++等)的多个进程共享大型文件持久存储系统的有效方法。

现在有一些问题。 其中最大的一点是,尽管Java通过内存映射文件支持共享内存,但它不通过纯共享内存支持。 如果我们对大面积的内存感兴趣(如本例所示),则文件映射是一个优势,但是对于不需要持久性的快速变化的小内存区域,文件映射是不必要的性能问题。 我想在JDK中看到一个真正的共享内存库; 这不太可能很快发生(请参阅我关于围墙花园的观点)。 JNI提供了一条路线,但是JNI有许多不利之处,我们对此深有体会。 也许巴拿马项目将提供所需的功能,并最终打破JVM的壁垒。

综上所述,我想尝试的下一个技巧是将文件映射到ramdisk(在此处有一个有趣的文章: http : //www.jamescoyle.net/knowledge/951-the-difference-between-a -tmpfs和Ramfs-ram磁盘) 。 这在Linux上应该非常容易,并且可以让我们在不使用JNI的情况下将进程间队列放置在纯RAM共享内存区域中。 完成此部分后,将获得纯Java高速进程间共享内存模型。 也许那将不得不等待明年的日历?

翻译自: https://www.javacodegeeks.com/2015/12/native-speed-file-backed-large-data-storage-pure-java.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/355619.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

element ui后台html_GitHub上10个开源且优秀的后台管理系统UI面板

作者&#xff1a;SevDotwww.jianshu.com/p/3bc7404af887Web 开发中几乎的平台都需要一个后台管理&#xff0c;但是从零开发一套后台控制面板并不容易&#xff0c;幸运的是有很多开源免费的后台控制面板可以给开发者使用&#xff0c;那么有哪些优秀的开源免费的控制面板呢&#…

mysql 唯一记录_mysql选择唯一记录

我有以下查询&#xff1a;select * from members,subscriptionswhere members.MemberID subscriptions.MemberIDand subscriptions.Year 2009and members.ASSCID 15and subscriptions.Untildate between $2009-01-01 and 2009-12-31order by members.Memberlastname会员支付…

JAVA第七次作业

《Java技术》第七次作业 &#xff08;一&#xff09;学习总结 1.写出事件处理模型中的几个关键词&#xff0c;并通过具体代码实例说明你对事件处理模型的理解。 WindowListener&#xff1a;窗体事件&#xff0c;专门处理窗体的事件监听口&#xff0c;窗体的所有变化都可以使用此…

java时间格式转js_使用jquery或java脚本将日期时间转换为rfc3339格式

您的要求似乎微不足道&#xff0c;还有更多吗&#xff1f;这是显而易见的答案&#xff1a;function formatTimestring(s) {var b s.split(/[\\/:]/);return b[2] b[1] b[0] \T\ b[3] b[4] \00\ \Z\}alert(formatTimestring(\08/09/2010:12:00\) //20100908T120000Z);如…

输出以下图案菱形7行_春夏格子图案超流行,三木的一款格子连衣裙,带来田园少女风...

春夏搭配中&#xff0c;增添了华丽格子图案搭配&#xff0c;从经典的格子裙子&#xff0c;到衬衫裙等。根据搭配不同而成为不同风格。所以&#xff0c;这一次&#xff0c;重点介绍格子裙和长衬衫的几种种搭配。格子裙子的春夏搭配推荐LOOK&#xff1a;1 [格子褶皱裙子棕色T恤]的…

asp.net FileUpload上传文件夹并检测所有子文件

1.在FileUpload控件添加一个属性 webkitdirectory""就可以上传文件夹了 <asp:FileUpload ID"FileUpload1" runat"server" webkitdirectory"" />2.检测文件夹下所有子文件 string DirectoryName FileUpload1.PostedFile.FileNam…

使用Project Jigsaw的JDK 9 Early Access上的Eclipse IDE

几周前&#xff0c;我写了关于在Java 9上运行Eclipse Neon的文章 &#xff08;尽管&#xff0c;我在帖子标题中错误地和令人尴尬地留下了“火星”&#xff09;。 值得注意的是&#xff0c;我列出的步骤也适用于带有Project Jigsaw &#xff08;Java模块化&#xff09;构建的JDK…

火狐配置java_java selenium+firefox环境搭建

已经成功搭建的版本关系&#xff1a;FireFox45selenium3.141.59 geckodriver 0.21.0启动浏览器后空白页&#xff1a;浏览器版本太高Firefox历史版本geckodriver驱动版本https://github.com/mozilla/geckodriver/releasesselenium maven地址org.seleniumhq.seleniumselenium-ja…

arduino蜂鸣器_板卡推荐BPIUNO32 arduino 开发板,支持webduino与arduino应用

BPI:UNO32(也称为BPI-UNO32&#xff0c;被称为BPI UNO32)是一个带有Xtensa 32位LX6的单/双核心处理器的嵌入式系统的ESP32。支持Webduino和arduino的功能。BPI-UNO32使用的是esp-WROOM32&#xff0c;MCU。ESP32是一种集成2.4 GHz Wi-Fi和蓝牙双模式的单芯片解决方案。该公司的4…

Perl sendmail

introduction of sendmailexamplesend mail to multi-receiver转载于:https://www.cnblogs.com/david-wei0810/p/6846515.html

java 7.0 特性_JDK7.0语法新特性

JDK7.0语法新特性1&#xff0c;菱形语法(泛型实例化类型自动推断)List list new ArrayList<>(); // <>这个真的很像菱形2&#xff0c;在目前版本中&#xff0c;不可具体化的泛型(任意类型)可变参数&#xff0c;在编译时&#xff0c;会在调用处产生警告&#xff0c…

Unity优化之GC——合理优化Unity的GC (难度3 推荐5)

原文链接&#xff1a;http://www.cnblogs.com/zblade/p/6445578.html 最近有点繁忙&#xff0c;白天干活晚上抽空写点翻译&#xff0c;还要运动&#xff0c;所以翻译工作进行的有点缓慢 。 本文续接前面的unity的渲染优化&#xff0c;进一步翻译Unity中的GC优化&#xff0c;英文…

centos重置系统_双系统下Linux系统无法启动及其引导丢失之解决

背景介绍: 很久很久以前, 我在 NewSurfacePro(SP5) 里插了一张 128G 内存卡, 费力九牛二虎之力在上面装了 Deepin, 后来在某次不知道是 Windows 还是 Deepin 更新后, Deepin 启动时总要发生一个极具 Linux 特色的启动错误, witch 似乎在我树莓派上出现过, 折腾了很久也没好, 就…

Java 7和Java 8之间的细微自动关闭合同更改

Java 7的try-with-resources语句和与该语句一起使用的AutoCloseable类型的一个不错的功能是&#xff0c;静态代码分析工具可以检测到资源泄漏。 例如&#xff0c;Eclipse&#xff1a; 具有以上配置并尝试运行以下程序时&#xff0c;您将收到三个警告&#xff1a; public stat…

reduce python3_更少循环?看看这3个Python函数

原标题&#xff1a;更少循环&#xff1f;看看这3个Python函数 全文共1146字&#xff0c;预计学习时长5分钟图源&#xff1a;wired 诞生于1991年的Python&#xff0c;这几年突然火了。简历上有了Python&#xff0c;就业竞争力瞬间提升&#xff0c;甚至一些小学教材上都出现了Pyt…

java list compareto_Java,如何使用compareTo对Arraylist进行排序

我试图弄清楚如何使用可比较的方式对ArrayList进行排序,我的代码如下所示&#xff1a;public class playerComparsion{public static void main(String[] args){ArrayList list new ArrayList();Player p1 new Players(1,92,Zlatan);Player p2 new Players(2,92,Hazard);Pla…

java图片上传(mvc)

最近有开始学起了java,好久没写文章了,好久没来博客园了。最近看了看博客园上次写的图片上传有很多人看&#xff0c;今天在一些篇关于java图片上传的。后台接收用的是mvc。不墨迹了&#xff0c;直接上图。 先看目录结构。idea开发。 一、图片上传还是使用的这款jq插件。前端部署…

appengine_Google AppEngine:任务队列API

appengine任务队列 com.google.appengine.api.taskqueue 使用任务队列&#xff0c;用户可以发起一个请求&#xff0c;以使应用程序执行此请求之外的工作。 它们是进行后台工作的强大工具。 此外&#xff0c;您可以将工作组织成小的离散单元&#xff08;任务&#xff09;。 然后…

python的for语句条件_Python入门基础解答条件判断语句和循环语句的新手使用教程...

无论什么语言都不会缺少条件判断语句和循环语句。我们日常中也有条件判断和循环&#xff0c;条件判断&#xff08;明天如果下雨就不出门&#xff0c;如果晴天就出门。&#xff09;&#xff1b;循环&#xff08;上学朗读课文&#xff0c;老师说&#xff1a;把文章的第几段落读三…

java安全权限_java.security.SecurityPermission

public final class SecurityPermission此类用于安全权限。SecurityPermission 包含一个名称(也称为“目标名称”)&#xff0c;但没有操作列表&#xff1b;可以使用&#xff0c;也可以不使用指定权限。目标名称就是安全配置参数的名称(见下表)。目前 SecurityPermission 对象可…