知识零碎
Java和Go是值传递还是引用传递
结论:Java和Go都是值传递。
值传递:
当一个函数被调用时,函数参数接收到的是
传入表达式的值的拷贝,函数内部对这个参数的修改不会影响调用者作用域的原始变量。简单来说:对形参的修改不会影响到实参被。
Go
func change(u *User) {
(*u).Name = "C" // u -> 0x1234 -> C
u = &User{} // u -> 0x5678 -> ""
}
func main() {
a := &User{
Name: "A",
} // a -> 0x1234 -> jack
change(a)
fmt.Printf("name: %s\n", a.Name) // a -> 0x1234 -> C
}
- 基本数据是对
值本身的拷贝。指针类型则是对地址的拷贝。- map、slice、chan这些本质上是struct,传递结构体的拷贝,但因为里面包含了指针,所以内部修改会影响到外部。
Java
static class User {
String name;
public User(String name) {
this.name = name;
}
}
public static void main(String[] args) {
User a = new User("A"); // a -> 0x1234 -> A
change(a);
System.out.println("name: " + a.name); // a -> 0x1234 -> C
}
static void change(User u) {
u.name = "C"; // u -> 0x1234 -> C
u = new User("B"); // u -> 0x5678 -> B
}
- 基本数据类型,是将数据复制一份传递给方法。
- 对象为参数时,是将
对象的引用值复制一份传递给方法,引用指向堆中的对象,且jvm屏蔽了具体的内存地址值(与go的指针存储内存地址不同)。
堆栈结构示意图
栈内存(main栈帧)
+------------------------+
| a(引用变量) | → 堆地址0x1234(User{name: "A"})
+------------------------+
堆内存
+------------------------+
| 0x1234: User对象 |
| name: "A" |
+------------------------+
↓
栈内存(main栈帧) 栈内存(change栈帧)
+------------------------+ +------------------------+
| a → 0x1234 | | u → 0x1234 | (u是a的拷贝,初始指向相同)
+------------------------+ +------------------------+
堆内存
+------------------------+
| 0x1234: User对象 | (被a和u同时指向)
| name: "A" |
+------------------------+
↓
栈内存(main栈帧) 栈内存(change栈帧)
+------------------------+ +------------------------+
| a → 0x1234 | | u → 0x1234 | (指向未变)
+------------------------+ +------------------------+
堆内存
+------------------------+
| 0x1234: User对象 | (内容被修改)
| name: "C" |
+------------------------+
↓
栈内存(main栈帧) 栈内存(change栈帧)
+------------------------+ +------------------------+
| a → 0x1234 | | u → 0x5678 | (u指向新地址,a仍指向原地址)
+------------------------+ +------------------------+
堆内存
+------------------------+ +------------------------+
| 0x1234: User对象 | | 0x5678: User对象 | (新创建的对象)
| name: "C" | | name: "" |
+------------------------+ +------------------------+
↓
栈内存(main栈帧)
+------------------------+
| a → 0x1234 | (a的指向始终未变)
+------------------------+
堆内存
+------------------------+ +------------------------+
| 0x1234: User对象 | | 0x5678: User对象 | (u已销毁,该对象后续可能被GC回收)
| Name: "C" | | Name: "" |
+------------------------+ +------------------------+Integer中的缓存池
Integer x = 123;
Integer y = 123;
System.out.println(x == y); // true
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
Integer z = Integer.valueOf(123);
Integer w = new Integer(123);
System.out.println(x == z); // true
System.out.println(z == w); // false
- Integer类型的缓存池的范围是
[-128, 127],只要是这个范围的值自动装箱就会返回相同的对象。- Integer类型中的equals()方法是对
包装的值进行了比较,而不是比较对象。- valueOf()方法利用了缓存,符合第一条的规则。
- 如果通过new关键字创建对象,是没用利用缓存,并不符合第一条规则。
多层嵌套循环跳出问题
c中可以通过goto语句跳出多层嵌套循环,java保留了goto关键字,但没有任何作用。
public static void main(String[] args) {
int[] a = new int[]{1, 2};
int[] b = new int[]{4, 5};
loop_a:
for (int j : a) {
for (int k : b) {
if (k == 5) {
break loop_a; // 跳出最外层循环
}
System.out.println(j + " -> " + k); // 1 -> 4
}
}
System.out.println("------------------------");
loop_b:
for (int j : a) {
for (int k : b) {
if (k == 5) {
continue loop_b; // 跳过内层循环值为5的,继续从外层的下一个数开始执行
}
System.out.println(j + " -> " + k); // 1 -> 4; 2 -> 4
}
}
}
}String对象的创建
字符串常量池(String Common Pool):因为字符串的大量创建会影响程序的性能,JVM引入了
字符串常量池来减少字符串性能的开销(也基于字符串的不可变性才能实现)。
String a = "abc";
String b = new String("abc");
String c = "a" + "b" + "c";
String d = a.intern();
String e = b.intern();
System.out.println(a == b); // false
System.out.println(a == c); // true
System.out.println(a == d); // true
System.out.println(d == e); // true字面量创建对象

new关键字创建对象

String.intern()
当调用 intern 方法时,如果池中已经包含一个等于该String对象的字符串,
由equals(Object)方法确定,则返回池中的字符串。否则,将此String对象添加到池中并返回对该String对象的引用。

最近在阅读Spring AOP的源码(基于Spring 5.2.8.RELEASE)中,发现
@EnableAspectJAutoProxy注解中的proxyTargetClass参数并不如注释(是否创建基于子类的CGLIB代理)中说所的哪样生效。无论我设置成true/false都会使用CGLIB代理。
自定义配置代码
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.springframework.chapter13")
public class AopConfiguration {
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
AopConfiguration.class);
// ....
}
}HashMap的几点注意事项
数组的创建时机
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 省略代码
}
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4
final Node<K,V>[] resize() {
// 省略代码
else {
// 初始容量16
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 省略代码
return newTab;
}
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 扩容因子赋值
}当我们通过
new HashMap<>()创建HashMap对象时,它只是对扩容因子进行赋值,并没有创建Node<K,V>[],只有在第一次执行putVal()才会创建。
节点转换为红黑树的时机
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class BaseTest {
@Test
public void tree() {
HashMap<Key, Integer> map = new HashMap<>();
map.put(new Key(), 1);
map.put(new Key(), 2);
map.put(new Key(), 3);
map.put(new Key(), 4);
map.put(new Key(), 5);
map.put(new Key(), 6);
map.put(new Key(), 7);
map.put(new Key(), 8); // ①
map.put(new Key(), 9); // ②
System.out.println("map 转换为红黑树了吗?");
}
static class Key {
@Override
public int hashCode() {
return 1;
}
}
}根据以上代码问:第几步时会执行方法treeifyBin将Node节点转换为红黑树?
先说答案:执行第二步的时候会转换为红黑树。
static final int TREEIFY_THRESHOLD = 8;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 代码省略
// 执行到此说明:tab已经初始化,对应的tab位置不为null
// 且新增的key和原有的key,hash相同,equals不同
for (int binCount = 0; ; ++binCount) { // 遍历该tab[i]下的所有node依次进行比较
// 如果遍历到最后一个节点且不和之前的节点value相同。
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// binCount初始为0,只要binCount=7时就触发转换为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 如果hash,equals都相同需要替换
break;
// 否则继续下一个节点
p = e;
}
}
}
// 代码省略
return null;
}
static final int MIN_TREEIFY_CAPACITY = 64;
// 转换为红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 核心:只有当当前容量大于64时才会转换为红黑树,否则只是扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 扩容
else if ((e = tab[index = (n - 1) & hash]) != null) {
// 省略转换为红黑树代码
}
}核心在于
binCount初始值为0,与TREEIFY_THRESHOLD - 1进行比较,并且只有当指定位置的tab[i]已经有值之后才会进入上述方法,所以当插入第九个值的时候且就会触发红黑树转换方法。但是!!!它只有在
HashMap的容量大于MIN_TREEIFY_CAPACITY(64)的时候才会转换为红黑树,否则只是扩容!
数组的扩容时机
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class BaseTest {
static class Key2 {
}
@Test
public void map() {
HashMap<Key2, Integer> map = new HashMap<>();
map.put(new Key2(), 1);
map.put(new Key2(), 2);
map.put(new Key2(), 3);
map.put(new Key2(), 4);
map.put(new Key2(), 5);
map.put(new Key2(), 6);
map.put(new Key2(), 7);
map.put(new Key2(), 8);
map.put(new Key2(), 9);
map.put(new Key2(), 10);
map.put(new Key2(), 11);
map.put(new Key2(), 12); // ①
System.out.println("map 扩容了吗?");
map.put(new Key2(), 13); // ②
}
}根据上述代码问:第几步进行了HashMap扩容?
先说答案:第二步。
我们知道扩容因子是0.75,即当HashMap当前的容量为16,它的扩容阈值是16 * 0.75 = 12。
transient int size;
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 默认情况下是capcity * 0.75
// size初始值为0
if (++size > threshold)
resize(); // 扩容操作
}size的初始值是0,但是进行比较的时候是
++size先加1再比较。所以当容量是16时,扩容阈值是12,那么插入第13个值就会触发resize()进行扩容。
SpringMVC相关问题
拦截器中@value注解不生效
原因在于:当我们继承WebMvcConfigurationSupport中的addInterceptors方法,并添加自定义的拦截器时,如果我们使用new的方式创建,那么该拦截器不会被IOC容器管理,所以无法给通过@value注解注入配置,推荐@Bean注解注入。
public class LoginInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("request is coming in ...");
return false;
}
}
public class SpringCloudEurekaServerApplication implements WebMvcConfigurer {
@Bean
public LoginInterceptor loginInterceptor() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor()).addPathPatterns("/**");
}
}WebMvcConfigurer 和 WebMvcConfigurationSupport
- 存在
WebMvcConfigurationSupport则WebMvcConfigurer不生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
}- 实现方式不同
// 实现方式
public class SpringCloudEurekaServerApplication implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
// 继承方式
public class SpringCloudEurekaServerApplication extends WebMvcConfigurationSupport{
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}Spring中的事件
监听事件的三种方式
- 实现ApplicationListener
<T>+ @Component
@Component
public class MyListener implements ApplicationListener<MyEvent> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener get event: {}", event.getSource());
}
}- @EventListener + @Component
@Component
public class MyListener2 {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@EventListener
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener2 get event: {}", event.getSource());
}
}- 实现ApplicationListener
<T>+ spring.factories注入
public class MyListener3 implements ApplicationListener<MyEvent> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void onApplicationEvent(MyEvent event) {
logger.info("MyListener3 get event: {}", event.getSource());
}
}org.springframework.context.ApplicationListener=\
io.spring.server.event.MyListener3位于resources/META-INF/spring.factories文件中
同步/异步发送事件
默认情况下是同步发送事件,在容器的refresh()中存在initApplicationEventMulticaster()方法,用于初始化事件发送器。
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 如果IOC容器中存在名为applicationEventMulticaster的bean则使用该bean作为事件发送器
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
} else {
// 不存在则使用内置的SimpleApplicationEventMulticaster作为事件发送器,并注入到容器中
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(
APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}- SimpleApplicationEventMulticaster(同步)
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}内置的
SimpleApplicationEventMulticaster中的multicastEvent方法会判断是否存在Executor,如果存在则用线程池发送。
- 自定义ApplicationEventMulticaster(异步)
@Component(value = "applicationEventMulticaster")
public class AsyncApplicationEventMulticaster extends SimpleApplicationEventMulticaster {
public AsyncApplicationEventMulticaster() {
super.setTaskExecutor(Executors.newFixedThreadPool(2));
}
}通过传入线程池实现异步invokeListeners,需要注入注入名必须是
applicationEventMulticaster。
事件发送两次
在使用spring.factories注入ApplicationListener时,发现监听器会被调用两次,通过debug发现:因为是web项目,所以上下文中存在父子容器的问题(AnnotationConfigServletWebServerApplicationContext和AnnotationConfigApplicationContext),所以在子容器发布事件后,父容器也会发送一次。
@Override
public void onApplicationEvent(MyEvent event) {
if (applicationContext.getParent() != null) {
logger.info("MyListener3 get event: {}", event.getSource());
}
}只让子容器发送事件,父容器不需要发送。
🎈🎈