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字符串的,抓住这一点,其实很多设计方法,方法的实现方式就显而易见了。
---
推荐阅读:
大数据存储的进化史 --从 RAID 到 Hdfs
贝叶斯分类算法实例 --根据姓名推测男女
从分治算法到 MapReduce
Java-- String源码分析的更多相关文章
- (转)Java中的String为什么是不可变的? -- String源码分析
背景:被问到很基础的知识点 string 自己答的很模糊 Java中的String为什么是不可变的? -- String源码分析 ps:最好去阅读原文 Java中的String为什么是不可变的 什 ...
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
- java集合源码分析(六):HashMap
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
- Java Reference 源码分析
@(Java)[Reference] Java Reference 源码分析 Reference对象封装了其它对象的引用,可以和普通的对象一样操作,在一定的限制条件下,支持和垃圾收集器的交互.即可以使 ...
- 程序兵法:Java String 源码的排序算法(一)
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- Java集合类源码分析
常用类及源码分析 集合类 原理分析 Collection List Vector 扩充容量的方法 ensureCapacityHelper很多方法都加入了synchronized同步语句,来保 ...
- Java 容器源码分析之1.7HashMap
以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储的对象是一个键值对对象(Entry<K,V>): HashMap补充说明 基于数组和链表 ...
随机推荐
- karma测试实践
karma是Google团队开发的一套前端测试运行框架,它不同于测试框架(jasmine,mocha等),它运行在这些测试框架之上,主要完成的工作有: 1.karma启动一个web服务器,生成包含js ...
- Gradle安装使用以及基本操作
这两天看到越来越多的在接触Gradle,然后发现我之前没有做过记录,而且之后下个月的一些有关SpringBoot的东西也需要用到,所以这里就来记录一下,方便以后使用. 简单介绍 Gradle是一个好用 ...
- 函数式编程之-初窥F#
大量讲解函数式编程语言的书籍最终都会用Fuctor,Monad,Monoids,范畴论等各种词汇吓退命令式语言玩家,所以我试图避开这些问题,揭开这些复杂词汇带来的具有实战意义的成果.另外我会尽量使用C ...
- systemctl enable docker.service
[root@dingyingsi ~]# systemctl start docker.service [root@dingyingsi ~]# systemctl enable docker.ser ...
- python datetime模块详解
datetime是python当中比较常用的时间模块,用于获取时间,时间类型之间转化等,下文介绍两个实用类. 一.datetime.datetime类: datetime.datetime.now() ...
- MyBatis源码解析(十二)——binding绑定模块之MapperRegisty
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...
- MyBatis源码解析(七)——DataSource数据源模块之托管数据源
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675700.html 1 回顾 之前介绍的非池型与池型数据源都是MyBatis自己定义的内 ...
- 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置
介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...
- The 15th Zhejiang Provincial Collegiate Programming Contest(部分题解)
ZOJ 4024 Peak 题意 给出n和n个数,判断该数列是否是凸形的. 解题思路 从前往后第一对逆序数,和从后往前第一队逆序数,如果都非零而且相邻,证明该数组是凸形的. 代码 #include & ...
- 第1章 Linux文件类基础命令
1. 关于路径和通配符 Linux中分绝对路径和相对路径,绝对路径一定是从/开始写的,相对路径不从根开始写,还可能使用路径符号. 路径展开符号: . :(一个点)表示当前目录 .. :(两个点)表示上 ...