首页 文章 精选 留言 我的

精选列表

搜索[最权威安装],共10026篇文章
优秀的个人博客,低调大师

史上详细Java内存区域讲解

常见面试题 基本问题 介绍下 Java 内存区域(运行时数据区) Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么) 对象的访问定位的两种方式(句柄和直接指针两种方式) 拓展问题 String类和常量池 8种基本类型的包装类和常量池 一、概述 对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 二、运行时数据区域 Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。 JDK 1.8之前: JDK 1.8 : 线程私有的: 程序计数器 虚拟机栈 本地方法栈 线程共享的: 堆 方法区 直接内存(非运行时数据区的一部分) 2.1 程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。 另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 从上面的介绍中我们知道程序计数器主要有两个作用: 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 2.2 Java 虚拟机栈 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。 Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。) 局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。 Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。 StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。 OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。 Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。 扩展:那么方法/函数如何调用? Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。 Java方法有两种返回方式: return 语句。 抛出异常。 不管哪种返回方式都会导致栈帧被弹出。 2.3 本地方法栈 和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。 2.4 堆 Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。 上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold来设置。 2.5 方法区 方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与 Java 堆区分开来。 方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。 方法区和永久代的关系 《Java虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。方法区和永久代的关系很像Java中接口和类的关系,类实现了接口,而永久代就是HotSpot虚拟机对虚拟机规范中方法区的一种实现方式。也就是说,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久带这一说法。 常用参数 JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小 -XX:PermSize=N //方法区(永久代)初始大小 -XX:MaxPermSize=N //方法区(永久代)最大大小,超过这个值将会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError: PermGen 相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。** JDK 1.8 的时候,方法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取而代之是元空间,元空间使用的是直接内存。 下面是一些常用参数: -XX:MetaspaceSize=N //设置Metaspace的初始(和最小大小) -XX:MaxMetaspaceSize=N //设置Metaspace的最大大小 与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。 为什么要将永久代(PermGen)替换为元空间(MetaSpace)呢? 整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到java.lang.OutOfMemoryError。你可以使用-XX:MaxMetaspaceSize标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX:MetaspaceSize调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。 当然这只是其中一个原因,还有很多底层的原因,这里就不提了。 2.6 运行时常量池 运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用) 既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。 JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。 2.7 直接内存 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。 JDK1.4 中新加入的NIO(New Input/Output) 类,引入了一种基于通道(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。 本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 三、HotSpot 虚拟机对象探秘 通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。 3.1 对象的创建 下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。 ①类加载检查:虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。 ②分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有“指针碰撞”和“空闲列表”两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 内存分配的两种方式:(补充内容,需要掌握) 选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的 内存分配并发问题(补充内容,需要掌握) 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全: CAS+失败重试:CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。 TLAB:为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配 ③初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 ④设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。 ⑤执行 init 方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init>方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。 3.2 对象的内存布局 在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。 Hotspot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的自身运行时数据(哈希码、GC分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 3.3 对象的访问定位 建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有①使用句柄和②直接指针两种: 句柄:如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; 直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。 四、重点补充内容 String 类和常量池 1 String 对象的两种创建方式: String str1 = "abcd"; String str2 = new String("abcd"); System.out.println(str1==str2);//false 这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。记住:只要使用new方法,便需要创建新的对象。 2 String 类型的常量池比较特殊。它的主要使用方法有两种: 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 String s1 = new String("计算机"); String s2 = s1.intern(); String s3 = "计算机"; System.out.println(s2);//计算机 System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象, System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象 3 String 字符串拼接 String str1 = "str"; String str2 = "ing"; String str3 = "str" + "ing";//常量池中的对象 String str4 = str1 + str2; //在堆上创建的新的对象 String str5 = "string";//常量池中的对象 System.out.println(str3 == str4);//false System.out.println(str3 == str5);//true System.out.println(str4 == str5);//false 尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。 String s1 = new String("abc");这句话创建了几个对象? 创建了两个对象。 验证: String s1 = new String("abc");// 堆内存的地址值 String s2 = "abc"; System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。 System.out.println(s1.equals(s2));// 输出true 结果: false true 解释: 先有字符串"abc"放入常量池,然后 new 了一份字符串"abc"放入Java堆(字符串常量"abc"在编译期就已经确定放入常量池,而 Java 堆上的"abc"是在运行期初始化阶段才确定),然后 Java 栈的 str1 指向Java堆上的"abc"。 8种基本类型的包装类和常量池 Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。 Integer i1 = 33; Integer i2 = 33; System.out.println(i1 == i2);// 输出true Integer i11 = 333; Integer i22 = 333; System.out.println(i11 == i22);// 输出false Double i3 = 1.2; Double i4 = 1.2; System.out.println(i3 == i4);// 输出false Integer 缓存源代码: /** *此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值。 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 应用场景: Integer i1=40;Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。 Integer i1 = new Integer(40);这种情况下会创建新的对象。 Integer i1 = 40; Integer i2 = new Integer(40); System.out.println(i1==i2);//输出false Integer比较更丰富的一个例子: Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = new Integer(40); Integer i5 = new Integer(40); Integer i6 = new Integer(0); System.out.println("i1=i2 " + (i1 == i2)); System.out.println("i1=i2+i3 " + (i1 == i2 + i3)); System.out.println("i1=i4 " + (i1 == i4)); System.out.println("i4=i5 " + (i4 == i5)); System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6)); 结果: i1=i2 true i1=i2+i3 true i1=i4 false i4=i5 false i4=i5+i6 true 40=i5+i6 true 解释: 语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。

优秀的个人博客,低调大师

Excel 行列转换的简方法

问题描述 在工作中时常会遇到对 Excel 表格的处理。当编辑一张 Excel 表格时,发现表格的列数太多,而行数较少,为方便打印,这时你或许会希望将该表格行列转换;或许是为了做进一步做统计分析,当前格式不太方便,这时也会用到行列转换。 下面这种交叉式的 Excel 表是很常见的格式,用来填写和查看都比较方便: 但是,如果想做进一步的统计分析,这种格式就不方便了,需要行列转换,变成如下格式的明细表: 显然,手工操作会非常麻烦,若数据量小还可以,数据量大了会耗费大量时间,简直就是灾难。 我们就以此为例,举例说明几种常见的解决方法。 解决方法方法 1:Excel 数据透视表 Excel 可以通过数据透视表支持行列转换功能,效果如下图: 显然,这并不是我们想要的格式。Excel 的数据透视表可以满足简单格式的行列转换,但如果格式稍微复杂,转换效果往往是不尽人意。 方法 2:编程语言 以写程序来解决,思路也很简单: · 加载 excel 文件,装载需要的 sheet 工作表。 · 读取“账套名”所在行,将其转换成字符串数组。 · 读取“科目编码”所在列,将其转换成字符串数组。 · 按“科目编码”分组,与“账套名”数组构造一张表。 · 根据“账套名”对应的数据,遍历所有的明细值填充到相应的表中。 · 这样就可以构造出对应的明细表来。 如果用 Java 来实现,初步估计代码量也不会少于 200 行,若需要结果输出成 excel 文件则开发工作量会更多。虽然 Excel 自己提供了 VBA,但那个麻烦程度谁用谁知道,不提也罢。那其它的语言呢?传说 python 有处理行列转换的功能(pandas 包里有 pivot 功能),代码量相对于 java 会少很多, 我们来试一下: import pandas as pd import numpy as np df = pd.read_excel("D:\\excel\\pandas.xlsx", 0, 3) cols = df.columns.values.tolist() #获取数据头信息 #移去前两列,只保留需要行列转换的列 cols.remove('科目编码') cols.remove('科目明细') #构造一个 list. frames=[] for col in cols: df1 = df.pivot_table(index = ['科目编码','科目明细'], values = [col]) df1.rename(columns={col: '数值'}, inplace=True) df1[3]=col #转换后的数据追加到 frames 中. frames.append(df1) # concat 将相同字段的表首尾相接 result=pd.concat(frames) result.rename(columns={3: '帐套名'}, inplace=True) result.to_excel('D:\\excel\\pandas_n.xlsx', sheet_name='科目明细') 效果还不错,果然比较简洁!这是 Python 生成的 excel 文件:不过,存在一点小问题,这个 excel 格式有点特殊,想用 Python 的 pivot,我们要将“科目编码”,“科目明细”移到与转换列标题所在同一行上,变成下面的样子。否则在代码上就得特殊 “照顾”,反正只有一行,手工做一下就算了,比写代码省事。 无论如何,python 的这个细节处理的小“瑕疵”并不影响其方便性。python 确实名不虚传,虽然使用了循环,但整个代码也就只有 10 来行的样子。 还能更简单吗? 嘿嘿,能! 方法 3:集算器编程 下面我们来看集算器的代码: 代码很简单,我们把每一步的中间结果列出来看看: A1:加载 excel 文件工作表 1,提取指定范围的数据 (从 3 行到 40 行),其中选项 @ t 表示首行为标题,载入数据, 生成表格如下: A2:删除非数据行 A3:更换列名称 A4:把从第 3 列开始的列名称连成字符串,用“,”分开A5:pivot 函数将行列数据进行转换,把 A4 中对应的列数据置放到“数值”列A6:将整理好的数据另存储为 xlsx 文件 集算器脚本只有 6 行,木有啥循环、判断之类的玩意儿,也不像 Python 那样要先手工倒腾一下,就把这看似有点“乱”的数据表格处理好了。相比之下,Python 采用列优先转换多次循环 “N”字方式,集算器则用行优先一次性处理,在处理数据上,集算器对细节处理及使用习惯更专业。而且集算器的开发环境也容易调试,可以看到每一步运算的中间结果,方便挑出错误,开发更为便捷。在这种常规数据处理的任务中,集算器要比 Python 更为优越。 优势总结 就这个问题,关于 python 与集算器的差异,再说说自己的一点心得体会: 多列转换 对于需要多列行列转换并汇集成“长”列的场景时,python 需要将每个数据列构造成数组,并增加一列记录当前列名,再追加到一个大的列表中,最后合并,合并中去掉非首个数组中的 title; 集算器就容易些,它直接把想要转换的列汇集在一块就行。相对于 python 的繁琐,集算器至少能省几个脑细胞。 名称更改 python 对于需要转换列的名称不能更改, 如 cols[0]=’天津’,此时 python 找不到修改前的关键字,“哪个朋友挖的坑,别以为我发现不了”,欺负大爷眼花,给报个异常行不? 但对应的集算器来说则很方便, 如:>A1.rename(_1: 科目编码,_2: 科目明细,4 成都: 成都) 标题空值问题 Python 读取 excel 表中的转换行标题时,前面两列为空 (对应原来的 excel 中的“科目编码,科目明细”),此时标题 cols 中的空值就没有了, 这个“坑”有点隐蔽啊,我真没有发现, 把其中的两列弄丢了,真有点丢脸 ; 但集算器能识别出来,会自动加上对应的标识 _1、_2,这样处理数据时,就能找到其中对应的两列。 网格式编程 集算器使用网格 A1 这种格式,它自动与所在位置的对象关联起来,这点非常方便, 感觉很有特色;Python 就只能望洋兴叹了。

优秀的个人博客,低调大师

简单,最快方式 Mysql兼容 emoji

Mysql -> mybatis -> spring boot -> java 序: 最近写一个类似成人商城的项目 ,写了一半的时候,发现用户如果通过苹果手机发送EMOJI的表情会无法插入数据库: === Error updating database. Cause: java.sql.SQLException: Incorrect string value: 'xF0x9Fx92x9ExE5x80...' for column 'comment' at row 1=== The error may involve cc.mb18.starshow.dbdto.TPageCoreDAO.intoComment-Inline=== The error occurred while setting parameters=== SQL: INSERT INTO t_page_comment (useruuid,username, faceurl, comment, pageuuid, createDatetime) VALUES (?,?, ?, ?, ?, ?)=== Cause: java.sql.SQLException: Incorrect string value: 'xF0x9Fx92x9ExE5x80...' for column 'comment' at row 1; uncategorized SQLException; SQL state [HY000]; error code [1366]; Incorrect string value: 'xF0x9Fx92x9ExE5x80...' for column 'comment' at row 1; nested exception is java.sql.SQLException: Incorrect string value: 'xF0x9Fx92x9ExE5x80...' for column 'comment' at row 1` 类似的报错.然后网上找了一堆 需要改数据库配置的方案,但是看完以后,我就否决了这个实施的方法,虽然项目没有上线,还在开发阶段,但是,动辄修改数据库配置文件这种操作,作为一个有 自我DBA限制的人,我怎么会这样做. 正文 平时管理数据库的时候 都是使用Navicat for Mysql的所以 以12版本为例 根据文章的学习,虽然不能按照修改数据库配置的操作 但是却找到了 根本问题所在所在, 修改数据库承接字符集,但是修改可以设定范围, 数据库级修改 还是 表级修改 还是字段级修改 我最后选择了字段级修改 附图如下从 图1 变为 图2 (下图1)从 图1 变为 图2 (下图2) 打开数据库 在表上 右键 设计表 然后选择字段 就会出现图中设置 `ALTER TABLE t_page_comment MODIFY COLUMN comment varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER sername;` 因为我数据库语句不是很熟 所以我只是把软件的代码截取出来给大家参考 至此 mysql 支持 emoji的操作就完成了另外 我为了保险起见 在@Update("set names utf8mb4")void setCharsetToUtf8mb4();我在Dao里面做了一个设置当前SESSION的字符集的操作,感觉没用,也不想去证明了 最后附上校训 理论结合实践

优秀的个人博客,低调大师

windows安装jenkins(docker安装

install DockerSystem Requirements: Windows 10 64bit: Pro, Enterprise or Education (Build 15063 or later).Virtualization is enabled in BIOS. Typically, virtualization is enabled by default. This is different from having Hyper-V enabled. For more detail see Virtualization must be enabled in Troubleshooting.CPU SLAT-capable feature.At least 4GB of RAM. download DockerCE for windows install jenkins用cmd命令窗口执行下述命令 docker run ^ --rm ^ -u root ^ -p 8080:8080 ^ -v jenkins-data:/var/jenkins_home ^ -v /var/run/docker.sock:/var/run/docker.sock ^ -v "%HOMEPATH%:/home ^ jenkinsci/blueocean #"%HOMEPATH%"为用户目录 执行完毕后访问 http://127.0.0.1:8080 提示需要通过日志访问密码 使用Maven构建流水线 -u root ^ --rm ^ -d ^ -p 8080:8080 ^ -p 50000:50000 ^ -v jenkins-data:/var/jenkins_home ^ -v /var/run/docker.sock:/var/run/docker.sock ^ jenkinsci/blueocean

优秀的个人博客,低调大师

CB INSIGHT权威发布:一文看懂印度IT巨头的投资偏好

在中国,阿里、腾讯、百度等都在云计算、大数据等方面大规模地投入研发,而国外的巨头、亚马逊、微软、谷歌都纷纷攻城略地。 为了避免失去优势地位,正在紧锣密鼓投资和收购云计算、大数据、自动化等高新技术创业公司。也企图通过投资和收购,缩小技术差距、迅速转型。 雷锋网翻译CBINSIGHTS的报告,一文了解印度IT巨头的收购逻辑。 (雷锋网(公众号:雷锋网)注:绿线表示投资;橙色线表示收) 印度IT巨头最喜欢收购的四个领域: 自动化&云计算:许多专门投资和致力于企业并购的投资机构正在提高在自动化和云计算等前沿技术领域的能力。一个例子是奥地利的软件测试公司 Tricentis。Tricentis在2017年第一季度获得由Wipro Venturesd领投的1.65亿美元B轮融资,目前Tricentis正在自动化脚本测试。 另一个例子就是位于美国

优秀的个人博客,低调大师

《Cocos2D权威指南》——1.1 什么是Cocos2D

1.1 什么是Cocos2D Cocos2D是一个开源框架,用于构建2D游戏、演示程序和其他图形界面交互应用等。Cocos2D诞生于一个用于开发2D游戏的Python框架(www.cocos2d.org)。在2008年的冬天,技术爱好者把它引入到iPhone开发中,并使用Objective-C重新编写。由于Cocos2D-iPhone的巨大成功,目前Cocos2D已经衍生出了多个版本,包括Ruby、Java(Android)、C++,甚至Mono(C#/.NET)。本书介绍的Cocos2D实际上是Cocos2D-iPhone(www.cocos2d-iphone.org),它是目前Cocos2D的最主要分支(后面为了统一,提到的所有Cocos2D均指Cocos2D-iPhone)。使用Cocos2D可以轻易地开发iPhone、iPo

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。