首页 文章 精选 留言 我的

精选列表

搜索[指标分析],共10000篇文章
优秀的个人博客,低调大师

java core dump分析实战

hs_err_pid简介 hs_err_pid<pid>.log是java程序发生core的时候产生的文件,里面有当时出错时jvm的执行情况。 排查方法 头文件解读可以查看问题 头文件包含了简单的信息阐述,里面就core掉的原因 # # An unexpected error has been detected by Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x69ba3a96, pid=376, tid=1848 # # Java VM: Java HotSpot(TM) Client VM (11.0-b16 mixed mode, sharing windows-x86) # Problematic frame: # C [nvoglnt.dll+0x6a3a96] # 这里的问题是动态库的调用,这个库如果不是java自带的库,而是第三方的库,那么可以查看这个库是否和机型系统想匹配,如果不匹配换匹配的库就好,如果匹配,需要重新安装该库尝试。 # # Out of Memory Error (os_linux_x86.cpp:718), pid=29229, tid=140505843455744 # # JRE version: Java(TM) SE Runtime Environment (7.0_67-b01) (build 1.7.0_67-b01) # Java VM: Java HotSpot(TM) 64-Bit Server VM (24.65-b04 mixed mode linux-amd64 compressed oops) # Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # 这里直接指出了问题的原因是oom,后面还会有物理内存状态,可以用来印证这个错误。 头文件+栈信息 # # An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x009fcf52, pid=4344, tid=5876 # # Java VM: Java HotSpot(TM) Client VM (1.5.0_14-b03 mixed mode) # Problematic frame: # V [jvm.dll+0x9cf52] # 头文件信息告诉我们是jvm.dll这个是jvm自己的库。此时就不能单纯的依靠这个信息来换库解决。 必须看栈信息,一般会有两个模块,一个是Native frames一个是Java frames。 Stack: [0x00030000,0x00070000), sp=0x0006f934, free space=254k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [jvm.dll+0x9cf52] C [NativeCode.dll+0x148b] C [NativeCode.dll+0x1253] j com.sy.test.TestNative.sayHello()V+0 j com.sy.test.TestNative.main([Ljava/lang/String;)V+22 v ~StubRoutines::call_stub V [jvm.dll+0x875dd] V [jvm.dll+0xdfd96] V [jvm.dll+0x874ae] V [jvm.dll+0x8e6f1] C [javaw.exe+0x14c5] C [javaw.exe+0x3151] C [kernel32.dll+0x16fd7] Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) j com.sy.test.TestNative.sayHello()V+0 j com.sy.test.TestNative.main([Ljava/lang/String;)V+22 v ~StubRoutines::call_stub 上面的信息,是从java调用到c了,极大的可能是jni的形式,此时需要查看,后面的PATH,LD_LIBRARY_PATH,以及-Djava.library.path查看是否编写了JNI。 场景问题 80%的问题上面的方法都可以推断和解决,下面说说20%的情况 jit问题表征 # JRE version: Java(TM) SE Runtime Environment (8.0_25-b17) (build 1.8.0_25-b17) # Java VM: Java HotSpot(TM) 64-Bit Server VM (25.25-b02 mixed mode linux-amd64 compressed oops) # Problematic frame: # C 0x0000000764928700 Stack: [0x00002b9213782000,0x00002b9213803000], sp=0x00002b92137fe6d0, free space=497k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) C 0x0000000764928700 这种直接就是问题都是寄存器地址的,也没有栈信息的,基本问题在JIT # Problematic frame: # J org.apache.http.impl.cookie.BestMatchSpec.formatCookies(Ljava/util/List;)Ljava/util/List; 上面这种造成core的是java代码引起的,是解释器的问题,基本问题在JIT 纯jvm代码 # JRE version: 6.0_45-b06 # Java VM: Java HotSpot(TM) 64-Bit Server VM (20.45-b01 mixed mode linux-amd64 compressed oops) # Problematic frame: # v ~StubRoutines::jshort_disjoint_arraycopy Stack: [0x00007fecfc9dc000,0x00007fecfca1d000], sp=0x00007fecfca1b4b0, free space=253k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) v ~StubRoutines::jshort_disjoint_arraycopy 上图整体的信息都是jvm的代码,这种情况一般是jvm的bug,可以查jvm的bug列表并且更换jdk 引用案例出处http://bbs.csdn.net/topics/390975171http://blog.csdn.net/zct08/article/details/6333467http://m635674608.iteye.com/blog/2257650

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

ArrayList 序列化分析

0x1 摘要 相信ArrayList是Java开发过程中最常用的集合类之一,底层存储结构是数组,这篇文章不讲解底层数据结构的实现,主要讲解它的序列化机制,大家都知道ArrayList是可以序列化的,但也有一些人不知道具体是怎么序列化的,希望这篇章可以帮到大家。 0x2 ArrayList 类结构图 从类结构图可以看出实现了Serializable接口,说明是可以被序列化的。 0x3 序列化过程详解 在讲解序列过程前,先要清楚ArrayList底层数据结构,摘要中已经提到底层是通过数组实现,下面从源码看一下: /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access elementData成员变量就是用来存储元素,细心的同学可能已经发现,此成员变量用transient关键字修饰,而transient关键字的作用是不需要序列化,存储数据的数组不序列化,那ArrayList是怎么实现数据序列化的呢?继续翻源码会发现存在writeObject、readObject两个方法, /** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ 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(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ 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(); } } } 从源码上可以看出,从数组中按size大小将元素取出来挨个写入到ObjectOutputStream中,反序列化时首先从ObjectInputStream中取得size大小,再循环取得元素。正常情况下,针对要序列化的对象不需要定义writeObject、readObject方法,采用默认序列化、反序列化方式,如果有特殊需求,则可以在对象中定义writeObject、readObject方法进行扩展,详细实现原理下次写序列化文章时再介绍。那么,ArrayList为什么要采用这种机制呢?上文中已经提到ArrayList底层采用数组存储,当声明一个数组时长度是固定的,在ArrayList中遇到元素个数超过数组大小时会采用自动扩展的方式来加大数组容量,而扩展方法是在原有数组容量的基础上扩大到1.5倍,具体实现可以查看ArrayList#grow方法。这种扩展方法会带来底层数组会存在空值问题,也就是真实数组容量远大于实际元素个数,在这样的前提下,如果直接序列elementData数组会带来不必要的开销,序列化很多空值,从而降低了序列化、反序列化性能,所以通过writeObject、readObject方法来实现序列化实际的元素,从而提升性能。

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

Java-- String源码分析

本篇博文基于java8,主要探讨java中的String源码。 首先,将一个类分为几个部分,分别是类定义(继承,实现接口等),全局变量,方法,内部类等等,再分别对这几个部分进行说明,这样到最后类的全貌也就比较直观了。 一:实现接口。 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { java.io.Serializable 这个序列化接口没有任何方法和域,仅用于标识序列化的语意。 Comparable<String> 这个接口只有一个compareTo(T 0)接口,用于对两个实例化对象比较大小。 CharSequence 这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了改接口。 二:主要变量。 /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); 可以看到,value[]是存储String的内容的,即当使用String str = "abc";的时候,本质上,"abc"是存储在一个char类型的数组中的。 而hash是String实例化的hashcode的一个缓存。因为String经常被用于比较,比如在HashMap中。如果每次进行比较都重新计算hashcode的值的话,那无疑是比较麻烦的,而保存一个hashcode的缓存无疑能优化这样的操作。 最后,这个CASE_INSENSITIVE_ORDER在下面内部类中会说到,其根本就是持有一个静态内部类,用于忽略大小写得比较两个字符串。 三:内部类。 再String只有一个内部类,那就是 private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } /** Replaces the de-serialized object. */ private Object readResolve() { return CASE_INSENSITIVE_ORDER; } } 这里有一个疑惑,在String中已经有了一个compareTo的方法,为什么还要有一个CaseInsensitiveComparator的内部静态类呢? 其实这一切都是为了代码复用。 首先看一下这个类就会发现,其实这个比较和compareTo方法也是有差别的,这个方法在比较时是忽略大小写的。 而且这是一个单例,可以简单得用它来比较两个String,因为String类提供一个变量:CASE_INSENSITIVE_ORDER 来持有这个内部类,这样当要比较两个String时可以通过这个变量来调用。 其次,可以看到String类中提供的compareToIgnoreCase方法其实就是调用这个内部类里面的方法实现的。这就是代码复用的一个例子。 四:方法。 首先是一系列的初始化方法。 public String() { this.value = "".value; } String支持多种初始化方法,包括接收String,char[],byte[],StringBuffer等多种参数类型的初始化方法。但本质上,其实就是将接收到的参数传递给全局变量value[]。 public int length() { return value.length; } public boolean isEmpty() { return value.length == 0; } public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; } 知道了String其实内部是通过char[]实现的,那么就不难发现length(),isEmpty(),charAt()这些方法其实就是在内部调用数组的方法。 //返回指定索引的代码点 public int codePointAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointAtImpl(value, index, value.length); } //返回指定索引前一个代码点 public int codePointBefore(int index) { int i = index - 1; if ((i < 0) || (i >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointBeforeImpl(value, index, 0); } //返回指定起始到结束段内字符个数 public int codePointCount(int beginIndex, int endIndex) { if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) { throw new IndexOutOfBoundsException(); } return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex); } //返回指定索引加上codepointOffset后得到的索引值 public int offsetByCodePoints(int index, int codePointOffset) { if (index < 0 || index > value.length) { throw new IndexOutOfBoundsException(); } return Character.offsetByCodePointsImpl(value, 0, value.length, index, codePointOffset); } 这几个函数用得比较少,并且可以看到其本质上都是用Character这个类的一些静态方法来实现。这些功能在平常并不经常使用,个人认为,如果使用的话那应该是在对未知字符串进行处理,且重点在异常处理上。 这里说明一下,16 位unicode编码的所有 65,536 个字符并不能完全表示全世界所有正在使用或曾经使用的字符。于是,Unicode 标准已扩展到包含多达 1,112,064 个字符。那些超出原来的16 位限制的字符被称作增补字符。Java的char类型是固定16bits的。代码点在U+0000 — U+FFFF之内到是可以用一个char完整的表示出一个字符。但代码点在U+FFFF之外的,一个char无论如何无法表示一个完整字符。这样用char类型来获取字符串中的那些代码点在U+FFFF之外的字符就会出现问题。 增补字符是代码点在 U+10000 至 U+10FFFF 范围之间的字符,也就是那些使用原始的 Unicode 的 16 位设计无法表示的字符。从 U+0000 至 U+FFFF 之间的字符集有时候被称为基本多语言面 (BMP UBasic Multilingual Plane)。因此,每一个 Unicode 字符要么属于 BMP,要么属于增补字符。 //将字符串复制到dst数组中,复制到dst数组中的起始位置可以指定。值得注意的是,该方法并没有检测复制到dst数组后是否越界。 void getChars(char dst[], int dstBegin) { System.arraycopy(value, 0, dst, dstBegin, value.length); } public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } 可以看到,这个两个重载方法本质上都是调用System.arraycopy()这个函数,包括在jdk很多其他源码中都是这样,比如ThreadPoolExcuter,看似有很多个重载,其实本质上都是调用同样的一个函数,只是会给你不同的默认初始值。 //获取当前字符串的二进制 public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) { if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } Objects.requireNonNull(dst); int j = dstBegin; int n = srcEnd; int i = srcBegin; char[] val = value; /* avoid getfield opcode */ while (i < n) { dst[j++] = (byte)val[i++]; } } public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException(); return StringCoding.encode(charsetName, value, 0, value.length); } public byte[] getBytes() { return StringCoding.encode(value, 0, value.length); } 将String字符串转成二进制的几种方式,可以指定byte数组,也能让其返回一个byte数组。本质上,其实都是调用了StringCoding.encode()这个静态方法。 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } hashCode()和equals()两个方法比较重要且有所关系就放一起了,equals()是string能成为广泛用于Map[key,value]中key的关键所在。 此外除equals()外,还有只比较内容的contentEquals(); public boolean contentEquals(CharSequence cs) { // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) { if (cs instanceof StringBuffer) { synchronized(cs) { return nonSyncContentEquals((AbstractStringBuilder)cs); } } else { return nonSyncContentEquals((AbstractStringBuilder)cs); } } // Argument is a String if (cs instanceof String) { return equals(cs); } // Argument is a generic CharSequence char v1[] = value; int n = v1.length; if (n != cs.length()) { return false; } for (int i = 0; i < n; i++) { if (v1[i] != cs.charAt(i)) { return false; } } return true; } 这个主要是用来比较String和StringBuffer或者StringBuild的内容是否一样。可以看到传入参数是CharSequence ,这也说明了StringBuffer和StringBuild同样是实现了CharSequence。源码中先判断参数是从哪一个类实例化来的,再根据不同的情况采用不同的方案,不过其实大体都是采用上面那个for循环的方式来进行判断两字符串是否内容相同。 public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; } 这个就是String对Comparable接口中方法的实现了。其核心就是那个while循环,通过从第一个开始比较每一个字符,当遇到第一个较小的字符时,判定该字符串小。 但还有一种是在较小长度的字符粗每个字符都和另一个字符串的每个字符相等,那么字符串长度较大的较大。 public int compareToIgnoreCase(String str) { return CASE_INSENSITIVE_ORDER.compare(this, str); } 这个也是比较字符串大小,规则和上面那个比较方法基本相同,差别在于这个方法忽略大小写。可以看到这是通过一个String 内部一个static的内部类实现的,那么为什么还要特地写一个内部类呢,这样其实就是为了代码复用,这样在其他情况下也可以使用这个static内部类。 public boolean regionMatches(int toffset, String other, int ooffset, int len) { char ta[] = value; int to = toffset; char pa[] = other.value; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > (long)value.length - len) || (ooffset > (long)other.value.length - len)) { return false; } while (len-- > 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } 比较该字符串和其他一个字符串从分别指定地点开始的n个字符是否相等。看代码可知道,其原理还是通过一个while去循环对应的比较区域进行判断,但在比较之前会做判定,判定给定参数是否越界。 public boolean startsWith(String prefix, int toffset) { char ta[] = value; int to = toffset; char pa[] = prefix.value; int po = 0; int pc = prefix.value.length; // Note: toffset might be near -1>>>1. if ((toffset < 0) || (toffset > value.length - pc)) { return false; } while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } 判断当前字符串是否以某一段其他字符串开始的,和其他字符串比较方法一样,其实就是通过一个while来循环比较。 public int indexOf(int ch, int fromIndex) { final int max = value.length; if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= max) { // Note: fromIndex might be near -1>>>1. return -1; } if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { // handle most cases here (ch is a BMP code point or a // negative value (invalid code point)) final char[] value = this.value; for (int i = fromIndex; i < max; i++) { if (value[i] == ch) { return i; } } return -1; } else { return indexOfSupplementary(ch, fromIndex); } } public int indexOf(int ch) { return indexOf(ch, 0); } 可以看到这里在if中有一句 ch < Character.MIN_SUPPLEMENTARY_CODE_POINT而在Character中看到 public static final int MIN_SUPPLEMENTARY_CODE_POINT = 0x010000; 这表明在java中char存储的值通常都是比ox010000小的,就是BMP类型的字符。 而当比这个值大的时候,就是增补字符了,那么会调用Character先判断是否是有效的字符,再进一步处理。 public int lastIndexOf(int ch, int fromIndex) { if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { // handle most cases here (ch is a BMP code point or a // negative value (invalid code point)) final char[] value = this.value; int i = Math.min(fromIndex, value.length - 1); for (; i >= 0; i--) { if (value[i] == ch) { return i; } } return -1; } else { return lastIndexOfSupplementary(ch, fromIndex); } } 和indexOf基本一致,只是顺序反过来。 static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetCount == 0) { return fromIndex; } char first = target[targetOffset]; int max = sourceOffset + (sourceCount - targetCount); for (int i = sourceOffset + fromIndex; i <= max; i++) { /* Look for first character. */ if (source[i] != first) { while (++i <= max && source[i] != first); } /* Found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetCount - 1; for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { /* Found whole string. */ return i - sourceOffset; } } } return -1; } 这个是上面indexOf的一个重载,主要是实现找到某个子串在当前字符串的起始位置,若没找到,则返回-1。 大致说下这里的实现思路:先是进行一系列的初始判定,比如子串长度不能大于当前字符串。然后在当前字符串中找到子串的第一个字符的位置 i ,从这个位置开始,和子串每一个字符比较。若完全匹配,则返回结果,如果在这个过程中,某个字符不匹配,则从 i+1 的位置开始继续寻找子串第一个字符的位置,后继续比较。 public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } 这个方法可以返回字符串中一个子串,看最后一行可以发现,其实就是指定头尾,然后构造一个新的字符串。 public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); } concat的作用是将str拼接到当前字符串后面,通过代码也可以看出其实就是建一个新的字符串。 public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; } 替换操作,主要是将原来字符串中的oldChar全部替换成newChar。看这里实现,主要是先找到第一个所要替换的字符串的位置 i ,将i之前的字符直接复制到一个新char数组。然后从 i 开始再对每一个字符进行判断是不是所要替换的字符。 public boolean matches(String regex) { return Pattern.matches(regex, this); } public String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceFirst(replacement); } public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); } public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); } 这几个方法都是使用了正则的方式来进行处理的。包括最后一个虽然参数不用提供正则规则,但内部其实也是使用了Pattern类的正则操作。 public String[] split(String regex, int limit) { /* fastpath if the regex is a (1)one-char String and this character is not one of the RegEx's meta characters ".$|()[{^?*+\\", or (2)two-char String and the first char is the backslash and the second is not the ascii digit or ascii letter. */ char ch = 0; if (((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) && (ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE)) { int off = 0; int next = 0; boolean limited = limit > 0; ArrayList<String> list = new ArrayList<>(); while ((next = indexOf(ch, off)) != -1) { if (!limited || list.size() < limit - 1) { list.add(substring(off, next)); off = next + 1; } else { // last one //assert (list.size() == limit - 1); list.add(substring(off, value.length)); off = value.length; break; } } // If no match was found, return this if (off == 0) return new String[]{this}; // Add remaining segment if (!limited || list.size() < limit) list.add(substring(off, value.length)); // Construct result int resultSize = list.size(); if (limit == 0) { while (resultSize > 0 && list.get(resultSize - 1).length() == 0) { resultSize--; } } String[] result = new String[resultSize]; return list.subList(0, resultSize).toArray(result); } return Pattern.compile(regex).split(this, limit); } 这个方法看起来比较复杂,但其实我们一般都不会用到那一大串的内容,一般我们用到最后那一句return Pattern.compile(regex).split(this, limit); 即同样是使用Pattern的正则方式去解析并拆分成字符串数组。 那么进到那些复杂的代码里面需要什么条件呢,看那个if: 1. 如果regex只有一位,且不为列出的特殊字符; 2.如regex有两位,第一位为转义字符且第二位不是数字或字母,“|”表示或,即只要ch小于0或者大于9任一成立,小于a或者大于z任一成立,小于A或大于Z任一成立 3.第三个是不属于utf-16之间的字符 其中的关系为( (1 || 2) && 3 ),光看第三点就知道这是为了应对特殊情况的。其实也就是使用一个ArrayList<String>存放每一段找到分割点的字符串,不断循环。 public String trim() { int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ while ((st < len) && (val[st] <= ' ')) { st++; } while ((st < len) && (val[len - 1] <= ' ')) { len--; } return ((st > 0) || (len < value.length)) ? substring(st, len) : this; } 这个函数平时用的应该比较多,删除字符串前后的空格,原理是通过找出前后第一个不是空格的字符串,返回原字符串的该子串。 总结: 在String中,其实最底层的实现就是通过一个final char value[] 来保存String字符串的,抓住这一点,其实很多设计方法,方法的实现方式就显而易见了。

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

spring bean scope 用法分析

Spring 容器是应用组件 bean 创建和组装专家,支持 InitializingBean, AbstractBeanDefinition.setInitMethodName(String), @PostConstruct 等多种方法初始化(及销毁)bean。 单例(singleton)bean 最常使用的是单例(singleton)bean,Spring 自动帮我们管理其生命周期。 代码示例如下: class Rick { @PostConstruct void init() { System.out.println("rick init"); } @PreDestroy void destroy() { System.out.println("rick des

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

php内核分析(五)-zval

这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux 实际上,从这个函数开始,就已经进入到了zend引擎的范围了。 zend_eval_string_ex(exec_direct, NULL, "Command line code", 1) 实际上是调用Zend/zend_execute_API.c zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions); 再进去是调用 result = zend_eval_stringl(str, str_len, retval_ptr, string_name); 这里的retval_ptr为NULL,string_name为"Command line code", str为"echo 12;" zend_eval_stringl 其实这个函数主流程并不复杂。简化下来就如下 ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */ { ... new_op_array = zend_compile_string(&pv, string_name); // 这个是把php代码编译成为opcode的过程 ... zend_execute(new_op_array, &local_retval); // 这个是具体的执行过程,执行opcode,把结果存储到local_retval中 ... retval = SUCCESS; return retval; } 先把php编译为opcode,然后执行这个opcode。只是这个函数有一些关键的结构需要理一下。 zval 我们会看到 zval local_retval; 这样的变量,然后会对这个变量进行如下操作: ZVAL_UNDEF(&local_retval); ZVAL_NULL(z) ZVAL_FALSE(z) ZVAL_TRUE(z) ZVAL_BOOL(z, b) ZVAL_LONG(z, l) ZVAL_DOUBLE(z, d) ZVAL_STR(z, s) ZVAL_INTERNED_STR(z, s) ZVAL_NEW_STR(z, s) ZVAL_STR_COPY(z, s) ZVAL_ARR(z, a) ZVAL_NEW_ARR(z) ZVAL_NEW_PERSISTENT_ARR(z) ZVAL_OBJ(z, o) ZVAL_RES(z, r) ZVAL_NEW_RES(z, h, p, t) ZVAL_NEW_PERSISTENT_RES(z, h, p, t) ZVAL_REF(z, r) ZVAL_NEW_EMPTY_REF(z) ZVAL_NEW_REF(z, r) ZVAL_NEW_PERSISTENT_REF(z, r) ZVAL_NEW_AST(z, a) ZVAL_INDIRECT(z, v) ZVAL_PTR(z, p) ZVAL_FUNC(z, f) ZVAL_CE(z, c) ZVAL_ERROR(z) php是一个弱类型的语言,它可以用一个$var来代表string,int,array,object等。这个就是归功于zval_struct结构 // zval的结构 struct _zval_struct { zend_value value; // 存储具体值,它的结构根据类型不同而不同 union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, // 这个位置标记了这个val是什么类型的(IS_STRING/IS_INT) zend_uchar type_flags, // 这个位置标记了这个val是什么属性 (IS_CALLABLE等) zend_uchar const_flags, // 常量的一些属性 (IS_CONSTANT_CLASS) zend_uchar reserved) // 保留的一些字段 } v; uint32_t type_info; // 类型的一些额外信息 } u1; // 保存类型的一些关键信息 union { uint32_t next; // 如果是在hash链表中,这个指针代表下一个元素的index uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ } u2; // 一些附属字段 }; 这个接口最重要的两个字段是 value,存储变量的值。另一个是u1.v.type 存储变量的类型。这里,value也是一个结构 typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ zend_refcounted *counted; zend_string *str; // string zend_array *arr; // array zend_object *obj; // object zend_resource *res; // resource zend_reference *ref; // 指针 zend_ast_ref *ast; // ast指针 zval *zv; void *ptr; zend_class_entry *ce; // class实体 zend_function *func; // 函数实体 struct { uint32_t w1; uint32_t w2; } ww; } zend_value; 如果u1.v.type == IS_STRING, 那么value.str就是指向了zend_string结构。好了,php的垃圾回收是通过引用计数来进行的,这个引用计数的计数器就放在zval.value.counted里面。 我们对zval设置的时候设置了一些宏来进行设置,比如:ZVAL_STRINGL是设置string,我们仔细看下调用堆栈: ZVAL_STRINGL(&pv, str, str_len); // 把pv设置为string类型,值为str 这个函数就是把pv设置为zend_string类型 // 带字符串长度的设置zend_sting类型的zval #define ZVAL_STRINGL(z, s, l) do { \ ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \ } while (0) 注意到,这里使用了一个写法,do {} while(0) 来设置一个宏,这个是C里面比较好的写法,这样写,能保证宏中定义的东西在for,if,等各种流程语句中不会出现语法错误。不过其实我们学习代码的时候,可以忽略掉这个框框写法。 zend_string_init(s, l, 0) ... // 从char* + 长度 + 是否是临时变量(persistent为0表示最迟这个申请的空间在请求结束的时候就进行释放),转变为zend_string* static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent) { zend_string *ret = zend_string_alloc(len, persistent); // 申请空间,申请的大小为zend_string结构大小(除了val)+ len + 1 memcpy(ZSTR_VAL(ret), str, len); ZSTR_VAL(ret)[len] = '\0'; return ret; } 这个函数可以看的点有几个: persistent 这个参数是用来代表申请的空间是不是“临时”的。这里说的临时是zend提供的一种内存管理器,相关请求数据只服务于单个请求,最迟会在请求结束的时候释放。 临时内存申请对应的函数为: void *emalloc(size_t size) 而永久内存申请对应的函数为: malloc zend_string_alloc static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent) { zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_REFCOUNT(ret) = 1; GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8); zend_string_forget_hash_val(ret); ZSTR_LEN(ret) = len; return ret; } 我们先看看zend_string的结构: // 字符串 struct _zend_string { zend_refcounted_h gc; // gc使用的被引用的次数 zend_ulong h; // 如果这个字符串作为hashtable的key在查找时候需要重复计算它的hash值,所以保存一份在这里 size_t len; // 字符串长度 char val[1]; // 柔性数组,虽然我们定义了数组只有一个元素,但是在实际分配内存的时候,会分配足够的内存 }; _ZSTR_STRUCT_SIZE(len) gc+h+len的空间,最后给了val留了len+1的长度 #define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1) ## GC_REFCOUNT(ret) = 1; #define GC_REFCOUNT(p) (p)->gc.refcount 这里就看到一个结构zend_refcounted_h typedef struct _zend_refcounted_h { uint32_t refcount; // 真正的计数 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // 冗余了zval中的类型值 zend_uchar flags, // used for strings & objects中有特定作用 uint16_t gc_info) // 在GC缓冲区中的索引位置 } v; uint32_t type_info; // 冗余zval中的type_info } u; // 类型信息 } zend_refcounted_h; 回到我们的实例,我们调用的是 zend_string_init(s, l, 0) // s=char*(echo 12;) l=8 返回的zend_string实际值为: struct _zend_string { struct { uint32_t refcount; // 1 union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, // IS_STRING zend_uchar flags, uint16_t gc_info) } v; uint32_t type_info; //IS_STRING | 0 => IS_STRING } u; } gc; zend_ulong h; // 0 size_t len; // 8 char val[1]; // echo 12;\0 }; 结合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval为 // zval的结构 struct _zval_struct { union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; // 指向到上面定义的那个zend_string中 zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } value; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) } v; uint32_t type_info; // IS_STRING_EX } u1; union { uint32_t next; uint32_t cache_slot; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; } u2; }; 这里,就对zval结构有初步了解了。 另外建议记住几个常用的类型,后续调试的时候会很有用 /* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/p/6108444.html,如需转载请自行联系原作者

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

数据分析学习笔记

row_number() row_number() over(partition by col1[, col2…] order by col1 [asc|desc][, col2[asc|desc]…]) 函数说明: 可以用于开窗,排序,计数,返回当前窗口的排序值。 参数说明:partition by col1[, col2..]:指定开窗口的列。 order by col1 [asc|desc], col2[asc|desc]:指定结果返回时的排序的值。 WM_CONCAT 函数声明:string wm_concat(string separator, string str) 函数说明:该函数用指定的 spearator 做分隔符,链接 str 中的值。可以用于多行变一行。 参数说明:separator:String 类型常量,分隔符。其他类型或非常量将引发异常。str:String 类型,若输入为 Bigint,Double 或者 Datetime 类型,会隐式转换为 String 后参与运算,其它类型报异常。返回值:返回 String 类型。

资源下载

更多资源
Mario

Mario

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

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册