主頁 > 後端開發 > Spring原始碼分析之回圈依賴及解決方案

Spring原始碼分析之回圈依賴及解決方案

2020-11-16 13:23:14 後端開發

Spring原始碼分析之回圈依賴及解決方案

往期文章:

  1. Spring原始碼分析之預啟動流程
  2. Spring原始碼分析之BeanFactory體系結構
  3. Spring原始碼分析之BeanFactoryPostProcessor呼叫程序詳解
  4. Spring原始碼分析之Bean的創建程序詳解

正文:

首先,我們需要明白什么是回圈依賴?簡單來說就是A物件創建程序中需要依賴B物件,而B物件創建程序中同樣也需要A物件,所以A創建時需要先去把B創建出來,但B創建時又要先把A創建出來...死回圈有木有...

回圈依賴

那么在Spring中,有多少種回圈依賴的情況呢?大部分人只知道兩個普通的Bean之間的回圈依賴,而Spring中其實存在三種物件(普通Bean,工廠Bean,代理物件),他們之間都會存在回圈依賴,這里我給列舉出來,大致分別以下幾種:

  • 普通Bean與普通Bean之間
  • 普通Bean與代理物件之間
  • 代理物件與代理物件之間
  • 普通Bean與工廠Bean之間
  • 工廠Bean與工廠Bean之間
  • 工廠Bean與代理物件之間

那么,在Spring中是如何解決這個問題的呢?

1. 普通Bean與普通Bean

首先,我們先設想一下,如果讓我們自己來編碼,我們會如何解決這個問題?

栗子

現在我們有兩個互相依賴的物件A和B

public class NormalBeanA {

	private NormalBeanB normalBeanB;

	public void setNormalBeanB(NormalBeanB normalBeanB) {
		this.normalBeanB = normalBeanB;
	}
}
public class NormalBeanB {

	private NormalBeanA normalBeanA;

	public void setNormalBeanA(NormalBeanA normalBeanA) {
		this.normalBeanA = normalBeanA;
	}
}

然后我們想要讓他們彼此都含有物件

public class Main {

	public static void main(String[] args) {
		// 先創建A物件
		NormalBeanA normalBeanA = new NormalBeanA();
		// 創建B物件
		NormalBeanB normalBeanB = new NormalBeanB();
		// 將A物件的參考賦給B
		normalBeanB.setNormalBeanA(normalBeanA);
		// 再將B賦給A
		normalBeanA.setNormalBeanB(normalBeanB);
	}
}

發現了嗎?我們并沒有先創建一個完整的A物件,而是先創建了一個空殼物件(Spring中稱為早期物件),將這個早期物件A先賦給了B,使得得到了一個完整的B物件,再將這個完整的B物件賦給A,從而解決了這個回圈依賴問題,so easy!

那么Spring中是不是也這樣做的呢?我們就來看看吧~

Spring中的解決方案

由于上一篇已經分析過Bean的創建程序了,其中的某些部分就不再細講了

先來到創建Bean的方法

AbstractAutowireCapableBeanFactory#doCreateBean

假設此時在創建A

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
  // beanName -> A
  // 實體化A
  BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
  // 是否允許暴露早期物件
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                    isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 將獲取早期物件的回呼方法放到三級快取中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
}

addSingletonFactory

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			// 單例快取池中沒有該Bean
			if (!this.singletonObjects.containsKey(beanName)) {
				// 將回呼函式放入三級快取
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

ObjectFactory是一個函式式介面

在這里,我們發現在創建Bean時,Spring不管三七二十一,直接將一個獲取早期物件的回呼方法放進了一個三級快取中,我們再來看一下回呼方法的邏輯

getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  Object exposedObject = bean;
  // 呼叫BeanPostProcessor對早期物件進行處理,在Spring的內置處理器中,并無相關的處理邏輯
  // 如果開啟了AOP,將引入一個AnnotationAwareAspectJAutoProxyCreator,此時將可能對Bean進行動態代理
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
      if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
        SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
        exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
      }
    }
  }
  return exposedObject;
}

在這里,如果沒有開啟AOP,或者該物件不需要動態代理,會直接回傳原物件

此時,已經將A的早期物件快取起來了,接下來在填充屬性時會發生什么呢?

相信大家也應該想到了,A物件填充屬性時必然發現依賴了B物件,此時就將轉頭創建B,在創建B時同樣會經歷以上步驟,此時就該B物件填充屬性了,這時,又將要轉頭創建A,那么,現在會有什么不一樣的地方呢?我們看看getBean的邏輯吧

doGetBean

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
  // 此時beanName為A
  String beanName = transformedBeanName(name);
  // 嘗試從三級快取中獲取bean,這里很關鍵
  Object sharedInstance = getSingleton(beanName);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 從單例快取池中獲取,此時仍然是取不到的
  Object singletonObject = this.singletonObjects.get(beanName);
  // 獲取不到,判斷bean是否正在創建,沒錯,此時A確實正在創建
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 由于現在仍然是在同一個執行緒,基于同步鎖的可重入性,此時不會阻塞
    synchronized (this.singletonObjects) {
      // 從早期物件快取池中獲取,這里是沒有的
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 從三級快取中獲取回呼函式,此時就獲取到了我們在創建A時放入的回呼函式
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 呼叫回呼方法獲取早期bean,由于我們現在討論的是普通物件,所以回傳原物件
          singletonObject = singletonFactory.getObject();
          // 將早期物件放到二級快取,移除三級快取
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  // 回傳早期物件A
  return singletonObject;
}

震驚!此時我們就拿到了A的早期物件進行回傳,所以B得以被填充屬性,B創建完畢后,又將回傳到A填充屬性的程序,A也得以被填充屬性,A也創建完畢,這時,A和B都創建好了,回圈依賴問題得以收場~

普通Bean和普通Bean之間的問題就到這里了,不知道小伙伴們有沒有暈呢~

2. 普通Bean和代理物件

普通Bean和代理物件之間的回圈依賴與兩個普通Bean的回圈依賴其實大致相同,只不過是多了一次動態代理的程序,我們假設A物件是需要代理的物件,B物件仍然是一個普通物件,然后,我們開始創建A物件,

剛開始創建A的程序與上面的例子是一模一樣的,緊接著自然是需要創建B,然后B依賴了A,于是又倒回去創建A,此時,再次走到去快取池獲取的程序,

// 從三級快取中獲取回呼函式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
  // 呼叫回呼方法獲取早期bean,此時回傳的是一個A的代理物件
  singletonObject = singletonFactory.getObject();
  // 將早期物件放到二級快取,移除三級快取
  this.earlySingletonObjects.put(beanName, singletonObject);
  this.singletonFactories.remove(beanName);
}

這時就不太一樣了,在singletonFactory.getObject()時,由于此時A是需要代理的物件,在呼叫回呼函式時,就會觸發動態代理的程序

AbstractAutoProxyCreator#getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) {
  // 生成一個快取Key
  Object cacheKey = getCacheKey(bean.getClass(), beanName);
  // 放入快取中,用于在初始化后呼叫該后置處理器時判斷是否進行動態代理過
  this.earlyProxyReferences.put(cacheKey, bean);
  // 將物件進行動態代理
  return wrapIfNecessary(bean, beanName, cacheKey);
}

此時,B在創建時填充的屬性就是A的代理物件了,B創建完畢,回傳到A的創建程序,但此時的A仍然是一個普通物件,可B參考的A已經是個代理物件了,不知道小伙伴看到這里有沒有迷惑呢?

不急,讓我們繼續往下走,填充完屬性自然是需要初始化的,在初始化后,會呼叫一次后置處理器,我們看看會不會有答案吧

初始化

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
	//...省略前面的步驟...
  // 呼叫初始化方法
  invokeInitMethods(beanName, wrappedBean, mbd);
  // 處理初始化后的bean
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

在處理初始化后的bean,又會呼叫動態代理的后置處理器了

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // 判斷快取中是否有該物件,有則說明該物件已被動態代理,跳過
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}

不知道小伙伴發現沒有,earlyProxyReferences這個快取可不就是我們在填充B的屬性,進而從快取中獲取A時放進去的嗎?不信您往上翻到getEarlyBeanReference的步驟看看~

所以,此時并未進行任何處理,依舊回傳了我們的原物件A,看來這里并沒有我們要的答案,那就繼續吧~

// 是否允許暴露早期物件
if (earlySingletonExposure) {
  // 從快取池中獲取早期物件
  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
    // bean為初始化前的物件,exposedObject為初始化后的物件
    // 判斷兩物件是否相等,基于上面的分析,這兩者是相等的
    if (exposedObject == bean) {
      // 將早期物件賦給exposedObject
      exposedObject = earlySingletonReference;
    }
  }
}

我們來分析一下上面的邏輯,getSingleton從快取池中獲取早期物件回傳的是什么呢?

synchronized (this.singletonObjects) {
  // 從早期物件快取池中獲取,此時就拿到了我們填充B屬性時放入的A的代理物件
  singletonObject = this.earlySingletonObjects.get(beanName);
  if (singletonObject == null && allowEarlyReference) {
    // 從三級快取中獲取回呼函式
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
      // 呼叫回呼方法獲取早期bean
      singletonObject = singletonFactory.getObject();
      // 將早期物件放到二級快取,移除三級快取
      this.earlySingletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
    }
  }
}

發現了嗎?此時我們就獲取到了A的代理物件,然后我們又把這個物件賦給了exposedObject,此時創建物件的流程走完,我們得到的A不就是個代理物件了嗎~

此次栗子是先創建需要代理的物件A,假設我們先創建普通物件B會發生什么呢?

3. 代理物件與代理物件

代理物件與代理物件的回圈依賴是怎么樣的呢?解決程序又是如何呢?這里就留給小伙伴自己思考了,其實和普通Bean與代理物件是一模一樣的,小伙伴想想是不是呢,這里我就不做分析了,

4. 普通Bean與工廠Bean

這里所說的普通Bean與工廠Bean并非指bean與FactoryBean,這將毫無意義,而是指普通Bean與FactoryBean的getObject方法產生了回圈依賴,因為FactoryBean最終產生的物件是由getObject方法所產出,我們先來看看栗子吧~

假設工廠物件A依賴普通物件B,普通物件B依賴普通物件A,

小伙伴看到這里就可能問了,誒~你這不對呀,怎么成了「普通物件B依賴普通物件A」呢?不應該是工廠物件A嗎?是這樣的,在Spring中,由于普通物件A是由工廠物件A產生,所有在普通物件B想要獲取普通物件A時,其實最終尋找呼叫的是工廠物件A的getObject方法,所以只要普通物件B依賴普通物件A就可以了,Spring會自動幫我們把普通物件B和工廠物件A聯系在一起,

小伙伴,哦~

普通物件A

public class NormalBeanA {

	private NormalBeanB normalBeanB;

	public void setNormalBeanB(NormalBeanB normalBeanB) {
		this.normalBeanB = normalBeanB;
	}
}

工廠物件A

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
	@Autowired
	private ApplicationContext context;

	@Override
	public NormalBeanA getObject() throws Exception {
		NormalBeanA normalBeanA = new NormalBeanA();
		NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);
		normalBeanA.setNormalBeanB(normalBeanB);
		return normalBeanA;
	}

	@Override
	public Class<?> getObjectType() {
		return NormalBeanA.class;
	}
}

普通物件B

@Component
public class NormalBeanB {
	@Autowired
	private NormalBeanA normalBeanA;
}

假設我們先創建物件A

由于FactoryBean和Bean的創建程序是一樣的,只是多了步getObject,所以我們直接定位到呼叫getObject入口

if (mbd.isSingleton()) {
  // 開始創建bean
  sharedInstance = getSingleton(beanName, () -> {
    // 創建bean
    return createBean(beanName, mbd, args);
  });
  // 處理FactoryBean
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
protected Object getObjectForBeanInstance(
			Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
	// 先嘗試從快取中獲取,保證多次從工廠bean獲取的bean是同一個bean
  object = getCachedObjectForFactoryBean(beanName);
  if (object == null) {
    // 從FactoryBean獲取物件
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
  }
}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
	// 加鎖,防止多執行緒時重復創建bean
  synchronized (getSingletonMutex()) {
    // 這里是Double Check
    Object object = this.factoryBeanObjectCache.get(beanName);
    if (object == null) {
      // 獲取bean,呼叫factoryBean的getObject()
      object = doGetObjectFromFactoryBean(factory, beanName);
    }
    // 又從快取中取了一次,why? 我們慢慢分析
    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
      object = alreadyThere;
    }else{
      // ...省略初始化bean的邏輯...
      // 將獲取到的bean放入快取
      this.factoryBeanObjectCache.put(beanName, object);
    }
  }
}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){
  return factory.getObject();
}

現在,就走到了我們自定義的getObject方法,由于我們呼叫了context.getBean("normalBeanB", NormalBeanB.class),此時,將會去創建B物件,在創建程序中,先將B的早期物件放入三級快取,緊接著填充屬性,發現依賴了A物件,又要倒回來創建A物件,從而又回到上面的邏輯,再次呼叫我們自定義的getObject方法,這個時候會發生什么呢?

又要去創建B物件...(Spring:心好累)

但是!此時我們在創建B時,是直接通過getBean在快取中獲取到了B的早期物件,得以回傳了!于是我們自定義的getObject呼叫成功,回傳了一個完整的A物件!

但是此時FactoryBean的緩沖中還是什么都沒有的,

// 又從快取中取了一次
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;
}

這一次取alreadyThere必然是null,流程繼續執行,將此時將獲取到的bean放入快取

this.factoryBeanObjectCache.put(beanName, object);

從FactoryBean獲取物件的流程結束,回傳到創建B的程序中,B物件此時的屬性也得以填充,再回傳到第一次創建A的程序,也就是我們第一次呼叫自定義的getObject方法,呼叫完畢,回傳到這里

// 獲取bean,呼叫factoryBean的getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
  object = alreadyThere;

那么,此時this.factoryBeanObjectCache.get(beanName)能從緩沖中拿到物件了嗎?有沒有發現,拿到了剛剛B物件填充屬性時再次創建A物件放進去的!

所以,明白這里為什么要再次從快取中獲取了吧?就是為了解決由于回圈依賴時呼叫了兩次自定義的getObject方法,從而創建了兩個不相同的A物件,保證我們回傳出去的A物件唯一!

怕小伙伴暈了,畫個圖給大家

5. 工廠Bean與工廠Bean之間

我們已經舉例4種回圈依賴的栗子,Spring都有所解決,那么有沒有Spring也無法解決的回圈依賴問題呢?

有的!就是這個FactoryBeanFactoryBean的回圈依賴!

假設工廠物件A依賴工廠物件B,工廠物件B依賴工廠物件A,那么,這次的栗子會是什么樣呢?

普通物件

public class NormalBeanA {

	private NormalBeanB normalBeanB;

	public void setNormalBeanB(NormalBeanB normalBeanB) {
		this.normalBeanB = normalBeanB;
	}
}
public class NormalBeanB {

	private NormalBeanA normalBeanA;

	public void setNormalBeanA(NormalBeanA normalBeanA) {
		this.normalBeanA = normalBeanA;
	}
}

工廠物件

@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
	@Autowired
	private ApplicationContext context;

	@Override
	public NormalBeanA getObject() throws Exception {
		NormalBeanA normalBeanA = new NormalBeanA();
		NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);
		normalBeanA.setNormalBeanB(normalBeanB);
		return normalBeanA;
	}

	@Override
	public Class<?> getObjectType() {
		return NormalBeanA.class;
	}
}
@Component
public class FactoryBeanB implements FactoryBean<NormalBeanB> {
	@Autowired
	private ApplicationContext context;
	@Override
	public NormalBeanB getObject() throws Exception {
		NormalBeanB normalBeanB = new NormalBeanB();
		NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);
		normalBeanB.setNormalBeanA(normalBeanA);
		return normalBeanB;
	}

	@Override
	public Class<?> getObjectType() {
		return NormalBeanB.class;
	}
}

首先,我們開始創建物件A,此時為呼叫工廠物件A的getObject方法,轉而去獲取物件B,便會走到工廠物件B的getObject方法,然后又去獲取物件A,又將呼叫工廠物件A的getObject,再次去獲取物件B,于是再次走到工廠物件B的getObject方法......此時,已經歷了一輪回圈,卻沒有跳出回圈的跡象,妥妥的死回圈了,

我們畫個圖吧~

沒錯!這個圖就是這么簡單,由于始終無法創建出一個物件,不管是早期物件或者完整物件,使得兩個工廠物件反復的去獲取對方,導致陷入了死回圈,

那么,我們是否有辦法解決這個問題呢?

我的答案是無法解決,如果有想法的小伙伴也可以自己想一想哦~

我們發現,在發生回圈依賴時,只要回圈鏈中的某一個點可以先創建出一個早期物件,那么在下一次回圈時,就會使得我們能夠獲取到早期物件從而跳出回圈!

而由于工廠物件與工廠物件間是無法創建出這個早期物件的,無法滿足跳出回圈的條件,導致變成了死回圈,

那么此時Spring中會拋出一個什么樣的例外呢?

當然是堆疊溢位例外啦!兩個工廠物件一直相互呼叫,不斷開辟堆疊幀,可不就是堆疊溢位有木有~

6. 工廠物件與代理物件

上面的情況是無法解決回圈依賴的,那么這個情況可以解決嗎?

答案是可以的!

我們分析了,一個回圈鏈是否能夠得到終止,關鍵在于是否能夠在某個點創建出一個早期物件(臨時物件),而代理物件在doCreateBean時,是會生成一個早期物件放入三級快取的,于是該回圈鏈得以終結,

具體程序我這里就不再細分析了,就交由小伙伴自己動手吧~

總結

以上我們一共舉例了6種情況,通過分析,總結出這樣一條定律:

在發生回圈依賴時,判斷一個回圈鏈是否能夠得到終止,關鍵在于是否能夠在某個點創建出一個早期物件(臨時物件),那么在下一次回圈時,我們就能通過該早期物件進而跳出(打破)回圈!

通過這樣的定律,我們得出工廠Bean與工廠Bean之間是無法解決回圈依賴的,那么還有其他情況無法解決回圈依賴嗎?

有的!以上的例子舉的都是單例的物件,并且都是通過set方法形成的回圈依賴,

假使我們是由于構造方法形成的回圈依賴呢?是否有解決辦法嗎?

沒有,因為這并不滿足我們得出的定律

無法執行完畢構造方法,自然無法創建出一個早期物件,

假使我們的物件是多例的呢?

也不能,因為多例的物件在每次創建時都是創建新的物件,即使能夠創建出早期物件,也不能為下一次回圈所用!

好了,本文就到這里結束了,希望小伙伴們有所識訓~

Spring IOC的核心部分到此篇就結束了,下一篇就讓我們進行AOP之旅吧~

下文預告:Spring原始碼分析之AOP從決議到呼叫

Spring 原始碼系列
  1. Spring原始碼分析之 IOC 容器預啟動流程(已完結)
  2. Spring原始碼分析之BeanFactory體系結構(已完結)
  3. Spring原始碼分析之BeanFactoryPostProcessor呼叫程序(已完結)
  4. Spring原始碼分析之Bean的創建程序(已完結)
  5. Spring原始碼分析之什么是回圈依賴及解決方案
  6. Spring原始碼分析之AOP從決議到呼叫
  7. Spring原始碼分析之事務管理(上),事物管理是spring作為容器的一個特點,總結一下他的基本實作與原理吧
  8. Spring原始碼分析之事務管理(下) ,關于他的底層事物隔離與事物傳播原理,重點分析一下
Spring Mvc 原始碼系列
  1. SpringMvc體系結構
  2. SpringMvc原始碼分析之Handler決議程序
  3. SpringMvc原始碼分析之請求鏈程序

另外筆者公眾號:奇客時間,有更多精彩的文章,有興趣的同學,可以關注

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/219733.html

標籤:Java

上一篇:安裝JDK

下一篇:類和物件在JVM中是如何存盤的,竟然有一半人回答不上來!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more