深入解析String中的intern

转载自 深入解析String中的intern

引言

在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

直接使用双引号声明出来的String对象会直接存储在常量池中。 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中 接下来我们主要来谈一下String#intern方法。

一, intern 的实现原理

首先深入看一下它的实现原理。

1,JAVA 代码

/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class <code>String</code>. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this <code>String</code> object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this <code>String</code> object is added to the * pool and a reference to this <code>String</code> object is returned. * <p> * It follows that for any two strings <code>s</code> and <code>t</code>, * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> * if and only if <code>s.equals(t)</code> is <code>true</code>. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java&trade; Language Specification</cite>. * * @return  a string that has the same contents as this string, but is *          guaranteed to be from a pool of unique strings. */  
public native String intern();

String#intern方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。

2,native 代码

在 jdk7后,oracle 接管了 JAVA 的源码后就不对外开放了,根据 jdk 的主要开发人员声明 openJdk7 和 jdk7 使用的是同一分主代码,只是分支代码会有些许的变动。所以可以直接跟踪 openJdk7 的源码来探究 intern 的实现。

native实现代码:

\openjdk7\jdk\src\share\native\java\lang\String.c

Java_java_lang_String_intern(JNIEnv *env, jobject this)  
{  return JVM_InternString(env, this);  
}

\openjdk7\hotspot\src\share\vm\prims\jvm.h

/* 
* java.lang.String 
*/  
JNIEXPORT jstring JNICALL  
JVM_InternString(JNIEnv *env, jstring str);

\openjdk7\hotspot\src\share\vm\prims\jvm.cpp

// String support ///  
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))  JVMWrapper("JVM_InternString");  JvmtiVMObjectAllocEventCollector oam;  if (str == NULL) return NULL;  oop string = JNIHandles::resolve_non_null(str);  oop result = StringTable::intern(string, CHECK_NULL);return (jstring) JNIHandles::make_local(env, result);  
JVM_END

\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name,  int len, TRAPS) {  unsigned int hashValue = java_lang_String::hash_string(name, len);  int index = the_table()->hash_to_index(hashValue);  oop string = the_table()->lookup(index, name, len, hashValue);  // Found  if (string != NULL) return string;  // Otherwise, add to symbol to table  return the_table()->basic_add(index, string_or_null, name, len,  hashValue, CHECK_NULL);  
}

\openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp

oop StringTable::lookup(int index, jchar* name,  int len, unsigned int hash) {  for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) {  if (l->hash() == hash) {  if (java_lang_String::equals(l->literal(), name, len)) {  return l->literal();  }  }  }  return NULL;  
}

它的大体实现结构就是:

JAVA 使用 jni 调用c++实现的StringTableintern方法, StringTableintern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。

要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:

-XX:StringTableSize=99991

二,jdk6 和 jdk7 下 intern 的区别

相信很多 JAVA 程序员都做做类似 String s = new String("abc")这个语句创建了几个对象的题目。 这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是"abc"字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。

来看一段代码:

public static void main(String[] args) {String s = new String("1");s.intern();String s2 = "1";System.out.println(s == s2);String s3 = new String("1") + new String("1");s3.intern();String s4 = "11";System.out.println(s3 == s4);
}

打印结果是

jdk6 下false false
jdk7 下false true

具体为什么稍后再解释,然后将s3.intern();语句下调一行,放到String s4 = "11";后面。将s.intern(); 放到String s2 = "1";后面。是什么结果呢

public static void main(String[] args) {String s = new String("1");String s2 = "1";s.intern();System.out.println(s == s2);String s3 = new String("1") + new String("1");String s4 = "11";s3.intern();System.out.println(s3 == s4);
}

打印结果为:

jdk6 下false false
jdk7 下false false

1,jdk6中的解释

注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

2,jdk7中的解释

再说说 jdk7 中的情况。这里要明确一点的是,在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError: PermGen space错误的。 所以在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。

正式因为字符串常量池移动到 JAVA Heap 区域后,再来解释为什么会有上述的打印结果。

在第一段代码中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是"11",但此时常量池中是没有 “11”对象的。

接下来s3.intern();这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 "11" 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。

最后String s4 = "11"; 这句代码中"11"是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。所以 s4 引用就指向和 s3 一样了。因此最后的比较 s3 == s4 是 true。

再看 s 和 s2 对象。 String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。

接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。

来看第二段代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = "11";后了。这样,首先执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。

第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

小 结

从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:

将String常量池 从 Perm 区移动到了 Java Heap区

String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

三,使用 intern

1,intern 正确使用例子

接下来我们来看一下一个比较常见的使用String#intern方法的例子。

代码如下:

static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];public static void main(String[] args) throws Exception {Integer[] DB_DATA = new Integer[10];Random random = new Random(10 * 10000);for (int i = 0; i < DB_DATA.length; i++) {DB_DATA[i] = random.nextInt();}long t = System.currentTimeMillis();for (int i = 0; i < MAX; i++) {//arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();}System.out.println((System.currentTimeMillis() - t) + "ms");System.gc();
}

运行的参数是:-Xmx2g -Xms2g -Xmn1500M 上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。结果如下图

2160ms

826ms

通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。

细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后,然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern 占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。

2,intern 不当使用

看过了 intern 的使用和 intern 的原理等,我们来看一个不当使用 intern 操作导致的问题。

在使用 fastjson 进行接口读取的时候,我们发现在读取了近70w条数据后,我们的日志打印变的非常缓慢,每打印一次日志用时30ms左右,如果在一个请求中打印2到3条日志以上会发现请求有一倍以上的耗时。在重新启动 jvm 后问题消失。继续读取接口后,问题又重现。接下来我们看一下出现问题的过程。

1,根据 log4j 打印日志查找问题原因

在使用log4j#info打印日志的时候时间非常长。所以使用 housemd 软件跟踪 info 方法的耗时堆栈。

trace SLF4JLogger.

trace AbstractLoggerWrapper:

trace AsyncLogger

org/apache/logging/log4j/core/async/AsyncLogger.actualAsyncLog(RingBufferLogEvent)                sun.misc.Launcher$AppClassLoader@109aca82            1            1ms    org.apache.logging.log4j.core.async.AsyncLogger@19de86bb  
org/apache/logging/log4j/core/async/AsyncLogger.location(String)                                  sun.misc.Launcher$AppClassLoader@109aca82            1           30ms    org.apache.logging.log4j.core.async.AsyncLogger@19de86bb  
org/apache/logging/log4j/core/async/AsyncLogger.log(Marker, String, Level, Message, Throwable)

代码出在 AsyncLogger.location 这个方法上. 里边主要是调用了 return Log4jLogEvent.calcLocation(fqcnOfLogger);Log4jLogEvent.calcLocation()

Log4jLogEvent.calcLocation()的代码如下:

public static StackTraceElement calcLocation(final String fqcnOfLogger) {  if (fqcnOfLogger == null) {  return null;  }  final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();  boolean next = false;  for (final StackTraceElement element : stackTrace) {  final String className = element.getClassName();  if (next) {  if (fqcnOfLogger.equals(className)) {  continue;  }  return element;  }  if (fqcnOfLogger.equals(className)) {  next = true;  } else if (NOT_AVAIL.equals(className)) {  break;  }  }  return null;  
}

经过跟踪发现是 Thread.currentThread().getStackTrace(); 的问题。

2, 跟踪Thread.currentThread().getStackTrace()的 native 代码,验证String#intern

Thread.currentThread().getStackTrace();native的方法:

public StackTraceElement[] getStackTrace() {  if (this != Thread.currentThread()) {  // check for getStackTrace permission  SecurityManager security = System.getSecurityManager();  if (security != null) {  security.checkPermission(  SecurityConstants.GET_STACK_TRACE_PERMISSION);  }  // optimization so we do not call into the vm for threads that  // have not yet started or have terminated  if (!isAlive()) {  return EMPTY_STACK_TRACE;  }        StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});  StackTraceElement[] stackTrace = stackTraceArray[0];  // a thread that was alive during the previous isAlive call may have  // since terminated, therefore not having a stacktrace.  if (stackTrace == null) {  stackTrace = EMPTY_STACK_TRACE;  }  return stackTrace;  } else {  // Don't need JVM help for current thread  return (new Exception()).getStackTrace();  }  
}  private native static StackTraceElement[][] dumpThreads(Thread[] threads);

下载 openJdk7的源码查询 jdk 的 native 实现代码,列表如下【这里因为篇幅问题,不详细罗列涉及到的代码,有兴趣的可以根据文件名称和行号查找相关代码】:

\openjdk7\jdk\src\share\native\java\lang\Thread.c \openjdk7\hotspot\src\share\vm\prims\jvm.h line:294: \openjdk7\hotspot\src\share\vm\prims\jvm.cpp line:4382-4414: \openjdk7\hotspot\src\share\vm\services\threadService.cpp line:235-267: \openjdk7\hotspot\src\share\vm\services\threadService.cpp line:566-577: \openjdk7\hotspot\src\share\vm\classfile\javaClasses.cpp line:1635-[1651,1654,1658]:

完成跟踪了底层的 jvm 源码后发现,是下边的三条代码引发了整个程序的变慢问题。

oop classname = StringTable::intern((char*) str, CHECK_0);  
oop methodname = StringTable::intern(method->name(), CHECK_0);  
oop filename = StringTable::intern(source, CHECK_0);

这三段代码是获取类名、方法名、和文件名。因为类名、方法名、文件名都是存储在字符串常量池中的,所以每次获取它们都是通过String#intern方法。但没有考虑到的是默认的 StringPool 的长度是1009且不可变的。因此一旦常量池中的字符串达到的一定的规模后,性能会急剧下降。

3,fastjson 不当使用 String#intern

导致这个 intern 变慢的原因是因为 fastjson 对String#intern方法的使用不当造成的。跟踪 fastjson 中的实现代码发现,

com.alibaba.fastjson.parser.JSONScanner#scanFieldSymbol()
if (ch == '\"') {bp = index;this.ch = ch = buf[bp];strVal = symbolTable.addSymbol(buf, start, index - start - 1, hash);break;
}
com.alibaba.fastjson.parser.SymbolTable#addSymbol():
/*** Constructs a new entry from the specified symbol information and next entry reference.*/
public Entry(char[] ch, int offset, int length, int hash, Entry next){characters = new char[length];System.arraycopy(ch, offset, characters, 0, length);symbol = new String(characters).intern();this.next = next;this.hashCode = hash;this.bytes = null;
}

fastjson 中对所有的 json 的 key 使用了 intern 方法,缓存到了字符串常量池中,这样每次读取的时候就会非常快,大大减少时间和空间。而且 json 的 key 通常都是不变的。这个地方没有考虑到大量的 json key 如果是变化的,那就会给字符串常量池带来很大的负担。

这个问题 fastjson 在1.1.24版本中已经将这个漏洞修复了。程序加入了一个最大的缓存大小,超过这个大小后就不会再往字符串常量池中放了。

[1.1.24版本的com.alibaba.fastjson.parser.SymbolTable#addSymbol() Line:113]代码

public static final int MAX_SIZE           = 1024;if (size >= MAX_SIZE) {return new String(buffer, offset, len);
}

这个问题是70w 数据量时候的引发的,如果是几百万的数据量的话可能就不只是30ms 的问题了。因此在使用系统级提供的String#intern方式一定要慎重!

五,总结

本文大体的描述了 String#intern和字符串常量池的日常使用,jdk 版本的变化和String#intern方法的区别,以及不恰当使用导致的危险等内容,让大家对系统级别的 String#intern有一个比较深入的认识。让我们在使用和接触它的时候能避免出现一些 bug,增强系统的健壮性。

引用:

以下是几个比较关键的几篇博文。感谢!

Save Memory by Using String Intern in Java

Java String array: is there a size of method?

Understanding String Table Size in HotSpot

How is Java's String#intern() method implemented?

JDK7里的String.intern的变化


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

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

相关文章

URLConnection-URL连接

【README】 本文介绍了 URLConnection java类&#xff0c;通过 URLConnection 如何获取网络资源&#xff1b;本文还梳理了涉及网络编程的java类的进化过程&#xff1b;从 URL -> URLConnection -> HttpURLConnection 或 HttpClient ; URL与URI的介绍&#xff0c; refer2…

excel趋势线公式导出_如何用Excel进行预测分析?

【面试题】一个社交APP, 它的新增用户次日留存、7日留存、30日留存分别是52%、25%、14%。请模拟出来&#xff0c;每天如果日新增6万用户&#xff0c;那么第30天&#xff0c;它的日活数会达到多少&#xff1f;请使用Excel进行分析。【分析思路】第1日(次日)留存用户数第1日新增用…

ASP.NET Core 行军记 -----第一步(艰辛的 MVC Hello World)

现在ASP.NET Core还在不断成长、更新中&#xff0c;说不定到了明天又换了个模样&#xff0c;就如同一个小孩&#xff0c;从蹒跚学步&#xff0c;到奔向未来。 所以我们可以相应的去理解更新中所发生的变化&#xff0c;包容它、呵护它&#xff0c;而不是盲目的指责与批评&#x…

记录一下SpringCloud-Gateway使用lb动态路由遇到的坑

前言 前一段时间&#xff0c;学习springcloud-alibaba时候&#xff0c;学习到gateway组件与nacos注册中西一起使用时遇到了一个问题在此记录一下&#xff0c;避免以后遇到又做无用功。 问题 通过gateway配置路由时&#xff0c;uri参数使用lb://xxxx无法映射到对应的服务地址…

Java程序创建Kafka Topic,以及数据生产消费,常用的命令

转自&#xff1a; Java程序创建Kafka Topic&#xff0c;以及数据生产消费&#xff0c;常用的命令_Zyy_z_的博客-CSDN博客_java kafka创建topicKafka简介&#xff1a; Kafka是一个分布式发布——订阅消息传递系统。Kafka快速、可扩展且耐用。它保留主题中的消息源。生产者将数据…

c++ 凸包 分治算法_三维凸包

缘起众所周知&#xff0c;二维凸包可以使用 Graham 扫描 内解决.所以本文来学习一下三维空间中凸包的一种直观算法——增量算法(increment algorithm)分析有一条叫 Willy 的苹果虫一直快乐的居住在一个苹果中&#xff0c;直到有一天有一只仓鼠想吃这个苹果&#xff0c;Willy 自…

深度分析Java的ClassLoader机制(源码级别)

转载自 深度分析Java的ClassLoader机制&#xff08;源码级别&#xff09;Java中的所有类&#xff0c;必须被装载到jvm中才能运行&#xff0c;这个装载工作是由jvm中的类装载器完成的&#xff0c;类装载器所做的工作实质是把类文件从硬盘读取到内存中&#xff0c;JVM在加载类的时…

.NET跨平台之Sake和KoreBuild

最近在了解Asp.net Core 1.0&#xff08;也可称为Asp.net5\Asp.net vNext)的跨平台&#xff0c;发现了两个新的新东西&#xff1a;Sake和KoreBuild&#xff08;或者已经出了很久&#xff09;。 通过国内某度查询资料大部分都是复制黏贴来的&#xff0c;几乎没有详细的介绍。 只…

Ubuntu系统安装准备

前言 最初学习程序开发时&#xff0c;大多数都会教linux系统的基础用法&#xff0c;但是很少以linux为操作系统开发程序&#xff0c;因为windows系统的简单便利为人的提供优秀的操作基础&#xff0c;成为首选系统。 突然心血来找想使用linux操作系统作基础进行程序代码开发。 …

(转)Kafka 消费者 Java 实现

转自&#xff1a; Kafka 消费者 Java 实现 - 简书应用程序使用 KafkaConsumer向 Kafka 订阅 Topic 接收消息&#xff0c;首先理解 Kafka 中消费者&#xff08;consumer&#xff09;和消费者组&#xff08;consumer group...https://www.jianshu.com/p/1f9e18e926f6据原文作者&…

docker export_docker使用简介

一 docker服务端和客户端declare -x DOCKER_HOST"192.168.2.162export DOCKER_HOST192.168.2.162docker -H tcp://192.168.2.162:2375 images二 docker daemon配置cat /etc/sysconfig/dockerOPTIONS--selinux-enabled --log-driverjournald --signature-verificationfalse…

深度解析单例与序列化之间的爱恨情仇

转载自 深度解析单例与序列化之间的爱恨情仇本文将通过实例阅读Java源码的方式介绍序列化是如何破坏单例模式的&#xff0c;以及如何避免序列化对单例的破坏。单例模式&#xff0c;是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访…

Windows 10中国定制版完工!更专业

去年底&#xff0c;微软与中国电子科技网络信息安全有限公司成立了一个合资公司&#xff0c;该公司的主要任务是为中国政府和企业定制 Windows 10 系统。 现在&#xff0c;微软大中华区董事长兼CEO贺乐赋在接受财新网记者采访时表示&#xff0c;微软与中国电子科技集团公司&…

Spring [CVE-2022-22965]漏洞处理

问题描述 近期spring官方公布了漏洞 - [CVE-2022-22965] 参考地址&#xff1a; https://tanzu.vmware.com/security/cve-2022-22965 参考issues提到的问题答案开发人员回应&#xff1a; 可能是由于Springframework 3.x 早于 JDK9发布&#xff0c;甚至发布时还未完整的支持JDK…

kafak消费者从头开始消费(消费者组)

【README】 本文主要用于描述 kafka 消费者如何从头开始消费&#xff1b; 【1】从头开始消费 1&#xff09;从头开始消费&#xff0c;需要满足两个条件&#xff0c; 如下&#xff1a; 条件1&#xff0c; 使用一个全新的消费者组id&#xff1b;条件2&#xff0c;指定 auto.of…

jenkins安装与配置windows_Windows下Scoop安装、配置与使用

Scoop简介Scoop是Windows的命令行安装程序&#xff0c;是一个强大的包管理工具。可以在github上找到其项目的相关信息&#xff0c;项目网址。安装的起因&#xff1a;在平常生活中如果要安装像gcc、git等一些需要手动配置相关参数的工具&#xff0c;需要先去官网下载安装程序&am…

对于线程安全的集合类(例如Vector)的任何操作是不是都能保证线程安全

转载自 对于线程安全的集合类&#xff08;例如Vector&#xff09;的任何操作是不是都能保证线程安全之前在公众号中问了这个问题&#xff1a;对于线程安全的集合类&#xff08;例如Vector&#xff09;的任何操作是不是都能保证线程安全&#xff1f; 三天之内收到120回复&#x…

ASP.NET Core 1.0中的管道-中间件模式

ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline)。日志记录、用户认证、MVC等模块都以中间件(Middleware)的方式注册在管道中。显而易见这样的设计非常松耦合并且非常灵活&#xff0c;你可以自己定义任意功能的Middleware注册在管道中。这一设计非常适用于“请求-响应”…

怎么样安装Ubuntu系统,一文告诉你

前言 额滴神呐/(ㄒoㄒ)/~~&#xff0c;用惯了windows开发&#xff0c;初上手Linux桌面开发真的是举步维艰&#xff08;内心ps&#xff1a;谁让你立这个标题的&#xff0c;现在后悔了吧… 你自己想办法 怎么把这个标题栏目圆过去&#xff09; 经过跟内心戏反复的都在&#xff0…

(转) SpringBoot接入两套kafka集群

转自&#xff1a; SpringBoot接入两套kafka集群 - 风小雅 - 博客园引入依赖 compile org.springframework.kafka:spring-kafka 第一套kafka配置 package myapp.kafka; importhttps://www.cnblogs.com/ylty/p/13673357.html 引入依赖 compile org.springframework.kafka:spring…