算法漫遊指北(第十一篇):歸併排序算法描述、動圖演示、代碼實現、過程分析、複雜度

一、歸併排序

歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。

  • 所謂“分”,指的是將一個亂序數列不斷進行二分,得到許多短的序列。

  • 所謂“治”,指的是將這些短序列進行兩兩合併,然後將合併的結果作為新的序列,再與其他序列進行合併,最終得到一個新的序列。

歸併排序算法描述

 

  • 把長度為n的輸入序列分成兩個長度為n/2的子序列;

  • 對這兩個子序列分別採用歸併排序;

  • 將兩個排序好的子序列合併成一個最終的排序序列。

 

歸併排序動圖演示

 

動畫演示圖2

 

 

歸併排序代碼實現

def merge_sort(alist):
    """歸併排序"""
    n = len(alist)
    #遞歸結束條件
    # 剩一個或沒有直接返回,不用排序
    if n <= 1:
        return alist
    # 拆分
    mid = n//2
​
    # left 採用歸併排序后形成的有序的新的列表
    left_li = merge_sort(alist[:mid])
​
    # right 採用歸併排序后形成的有序的新的列表
    right_li = merge_sort(alist[mid:])
​
    # 將兩個有序的子序列合併為一個新的整體
    # merge(left, right)
    left_pointer, right_pointer = 0, 0
    result = []
​
    while left_pointer < len(left_li) and right_pointer < len(right_li):
        if left_li[left_pointer] <=  right_li[right_pointer]:
            result.append(left_li[left_pointer])
            left_pointer += 1
        else:
            result.append(right_li[right_pointer])
            right_pointer += 1
    
    #將兩個列表按順序融合為一個列表result
    result += left_li[left_pointer:]
    result += right_li[right_pointer:]
    return result
​

  


歸併排序過程分析

示例 針對 arrli = [6,5,3,1,8,7,2,4]進行歸併排序

1、拆分數組

假設數組一共有 n 個元素,我們遞歸對數組進行折半拆分即n//2,直到每組只有一個元素為止。

 

2、合併數組

算法會從最小數組開始有序合併,這樣合併出來的數組一直是有序的,所以合併兩個有序數組是歸併算法的核心,這裏用兩個簡單數組示例:

步驟1:新建一個空數組存放合併結果,用left_pointerright_pointer兩個輔助指針記錄兩個數組當前操作位置;

 

步驟2:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到left_pointerright_pointer指針超出尾部;

 

步驟3:新建一個空數組存放合併結果,用lr兩個輔助指針記錄兩個數組當前操作位置;

 

步驟4:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到lr指針超出尾部;

 

繼續比較寫入較小的元素到新數組

繼續比較寫入較小的元素到新數組

 

指針尚未移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。

 

步驟5:新建一個空數組存放合併結果,用lr兩個輔助指針記錄兩個數組當前操作位置;

 

步驟6:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到lr指針超出尾部;

 

將較小的元素寫入到新數組

繼續比較寫入較小的元素到新數組

繼續比較寫入較小的元素到新數組

繼續比較寫入較小的元素到新數組

 

步驟7:右邊的指針尚未移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。

 

完成歸併排序,返回排好序的新數組

 

 

歸併排序複雜度

 

  • 時間複雜度:O(nlogn)

歸併排序把數組一層層折半分組,長度為 n 的數組,折半層數就是 logn,每一層進行操作的運算量是 n,得出時間複雜度 O(nlogn)。

  • 空間複雜度:O(n)

每次歸併操作需要創建額外的新數組,佔用空間為 n,但這部分額外空間會隨着方法的結束而釋放,所以只需要算單次歸併操作開闢的空間即可,得出空間複雜度 O(n)。

  • 穩定性:穩定

從算法中從左到右逐一比較,較小的先放入新數組,所以兩個值相同的元素,排序后依然保持原先後順序。

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

這一次搞懂SpringMVC原理

@

目錄

  • 前言
  • 正文
    • 請求入口
    • 組件初始化
    • 調用Controller
    • 參數、返回值解析
  • 總結

前言

前面幾篇文章,學習了Spring IOC、Bean實例化過程、AOP、事務的源碼和設計思想,了解了Spring的整體運行流程,但如果是web開發,那麼必不可少的還有Spring MVC,本篇主要分析在請求調用過程中SpringMVC的實現原理,通過本篇要搞懂它是怎麼解決請求、參數、返回值映射等問題的。

正文

請求入口

我們都知道前端調用後端接口時,都會通過Servlet進行轉發,而Servlet的聲明周期包含下面四個階段:

  • 實例化(new)
  • 初始化(init)
  • 執行(service調用doGet/doPost)
  • 銷毀(destroy)

前兩個階段在Spring啟動階段就做好了(init根據配置可能是第一次請求時才會調用),銷毀是服務關閉的時候進行,本文主要分析的就是請求執行階段。我們知道SpringMVC的核心就是DispatcherServlet,該類是對Servlet的擴展,所以直接從該類的service方法開始,但在此類中沒有service方法,那肯定是在其父類中,我們先來看看其繼承體系:

逐個往上找,在FrameworkServlet方法中就有一個service方法:

	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
   

但其主要還是調用父類HttpServlet中的方法,而該類又會根據不同的請求方式會調到子類中,最後的核心方法就是DispatcherServlet中的doDispatch方法:

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		//異步管理
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				//文件上傳
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				//這個方法很重要,重點看
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				//獲取跟HandlerMethod匹配的HandlerAdapter對象
				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				//前置過濾器,如果為false則直接返回
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//調用到Controller具體方法,核心方法調用,重點看看
				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);

				//中置過濾器
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}

			//視圖渲染及後置過濾器執行
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

MVC的所有處理邏輯都在這個方法中,先總結一下這個方法的實現邏輯,首先根據請求的url拿到緩存中的HandlerMethod對象和執行鏈對象,HandlerMethod中封裝了controller對象、方法對象和方法參數等信息,執行鏈則是包含了一個個HandlerInterceptor攔截器;然後再通過HandlerMethod拿到對應的HandlerAdapter,這個對象的作用就是去適配我們的controller;準備工作做完后,首先會執行前置過濾,如果被攔截則直接返回,否則就去調用controller中的方法執行我們的業務邏輯並返回一個ModelView對象;接着執行中置過濾器,以及處理全局異常捕獲器捕獲到異常;最後進行視圖渲染返回並執行後置過濾器進行資源釋放等工作。
以上就是MVC的整體執行流程,下面就逐個來分析,首先進入getHandler方法:

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//handlerMappering實例
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				//獲取HandlerMethod和過濾器鏈的包裝類
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

是委託給HandlerMapping對象的,這是一個接口,主要的實現類是RequestMappingHandlerMapping,同樣先來看看其繼承體系:

這個類是管理請求和處理類之間的映射關係的,你是否疑惑它是在哪裡實例化的呢?下面先來看看MVC組件的初始化。

組件初始化

這裏我以自動化配置的註解方式說明,Spring提供了一個@EnableWebMvc,通過前面的學習我們知道在這個註解中必定導入了一個配置類,點進去可以看到是DelegatingWebMvcConfiguration,這個類就是負責MVC的組件和擴展實現的初始化,其本身我們先不看,先看其父類WebMvcConfigurationSupport,這個類我們應該不陌生,要做一些自定義擴展時就需要繼承該類(如攔截器Interceptor),同樣作用的類還有WebMvcConfigurerAdapter,這個類是對前者相對安全的擴展,為什麼是相對安全呢?因為繼承前者會導致自動配置失效,而使用後者則不必擔心此問題,只需要在類上加上@EnableWebMvc註解。
WebMvcConfigurationSupport中我們可以看到很多@Bean標註的方法,也就是mvc組件的實例化,這裏主要看看requestMappingHandlerMapping,其餘的可自行閱讀理解,也就是一些Bean的註冊:

	public RequestMappingHandlerMapping requestMappingHandlerMapping() {
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
		mapping.setInterceptors(getInterceptors());
		mapping.setContentNegotiationManager(mvcContentNegotiationManager());
		mapping.setCorsConfigurations(getCorsConfigurations());

		......省略

		return mapping;
	}

這裏主要看getInterceptors方法如何獲取攔截器的:

	protected final Object[] getInterceptors() {
		if (this.interceptors == null) {
			InterceptorRegistry registry = new InterceptorRegistry();
			//鈎子方法,需要自己定義
			addInterceptors(registry);
			registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
			registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
			this.interceptors = registry.getInterceptors();
		}
		return this.interceptors.toArray();
	}

第一次進來會調用addInterceptors添加攔截器,這是一個模板方法,在子類DelegatingWebMvcConfiguration中實現:

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
	
	protected void addInterceptors(InterceptorRegistry registry) {
		this.configurers.addInterceptors(registry);
	}

	public void addInterceptors(InterceptorRegistry registry) {
		for (WebMvcConfigurer delegate : this.delegates) {
			delegate.addInterceptors(registry);
		}
	}

可以看到最終是調用WebMvcConfigureraddInterceptors方法,也就是我們對WebMvcConfigurerAdapter的自定義擴展。看到這裏我們應該明白了MVC的組件是如何添加到IOC容器中的,但是DispatcherServlet又是怎麼獲取到它們的呢?回到之前的代碼中,在DispatcherServlet這個類中有一個onRefresh方法,這個方法又調用了initStrategies方法完成了MVC九大組件的註冊:

	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}
		
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		}
	}

initHandlerMappings為例,其它組件實現邏輯基本一樣。首先從IOC容器中拿到handlerMappings的所有實現類(WebMvcConfigurationSupport中注入的對象就在這裏被獲取到),若沒有,則從DispatcherServlet.properties配置文件中(這個配置在spring-webmvc工程下org/springframework/web/servlet/DispatcherServlet.properties)獲取默認的配置:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

但是onRefresh又是在什麼時候調用的呢?有兩個地方,一個是Servlet初始化時會調用到initWebApplicationContext進行容器的初始化,這個方法中就會觸發onRefresh;另外還有一個,在FrameworkServlet中有一個onApplicationEvent方法,而這個方法又會被內部類ContextRefreshListener調用,這個類實現了ApplicationListener接口,表示會接收容器刷新事件。
以上就就是MVC HandlerMapping組件的初始化邏輯,其它組件實現邏輯相同,下面不再分析。

調用Controller

回到getHandler方法,其調用的是AbstractHandlerMapping類的方法:

	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//根據請求的uri拿到對應的HandlerMethod對象
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		//獲取HandlerMethod和過濾器鏈的包裝類
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		//是否是跨域請求,就是查看request請求頭中是否有Origin屬性
		if (CorsUtils.isCorsRequest(request)) {
			//自定義的鈎子方法獲取跨域配置
			CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
			//註解獲取跨域配置
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
			//這裏設置了跨域的過濾器CorsInterceptor
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

先看AbstractHandlerMethodMapping.getHandlerInternal

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		//從request對象中獲取uri,/common/query2
		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
		this.mappingRegistry.acquireReadLock();
		try {
			//根據uri從映射關係中找到對應的HandlerMethod對象
			HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
			//把Controller類實例化
			return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
		}
		finally {
			this.mappingRegistry.releaseReadLock();
		}
	}

	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		// 根據url拿到對應的RequestMappingInfo
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// No choice but to go through all mappings...
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			Match bestMatch = matches.get(0);
			if (matches.size() > 1) {
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}
				if (CorsUtils.isPreFlightRequest(request)) {
					return PREFLIGHT_AMBIGUOUS_MATCH;
				}
				Match secondBestMatch = matches.get(1);
				//如果兩個RequestMappinginfo什麼都相同,報錯
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.handlerMethod.getMethod();
					Method m2 = secondBestMatch.handlerMethod.getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}

	private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
		for (T mapping : mappings) {
			// 拿到匹配的RequestMappingInfo對象,有可能url相同,@RequestMapping的屬性(請求方式、參數等)匹配不上
			T match = getMatchingMapping(mapping, request);
			if (match != null) {
				//RequestMappingInfo對象和HandlerMethod對象封裝到Match對象中,其實就是註解屬性和Method對象的映射
				matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
			}
		}
	}

這裏邏輯很簡單,就是通過請求url從urlLookup中拿到對應的RequestMappingInfo(每一個 @RequestMapping對應一個RequestMappingInfo對象)對象,再根據RequestMappingInfo對象從mappingLookup拿到對應的HandlerMethod並返回。
但這裏你可能會比較好奇urlLookupmappingLookup從哪裡來的,仔細觀察你會發現當前這個類實現了一個接口InitializingBean,實現了這個接口的類會在該類的Bean實例化完成后調用afterPropertiesSet方法,上面的映射關係就是在這個方法中做的。實際上這個方法不止完成了上面兩個映射關係,還有下面兩個:

  • corsLookup:handlerMethod -> corsConfig
  • registry:RequestMappingInfo -> MappingRegistration(包含url、handlerMethod、RequestMappingInfo、name等信息)

這裏就不展開分析了,奉上一張時序圖,讀者可根據下面的時序圖自行分析:

拿到HandlerMethod對象后,又會通過getHandlerExecutionChain方法去獲取到所有的HandlerInterceptor攔截器對象,並連同HandlerMethod對象一起封裝為HandlerExecutionChain。之後是獲取跨域配置,這裏不詳細分析。
拿到HandlerExecutionChain對象后返回到doDispatch方法,又調用了getHandlerAdapter
方法拿到HandlerAdapter

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		//根據handlerMethod對象,找到合適的HandlerAdapter對象,這裏用到了策略模式
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
	}

這裏的handlerAdapters變量值從哪裡來?相信不用我再分析,主要看這裏的設計思想,典型的策略模式
之後調用完前置過濾器后,才是真正調用我們controller方法的邏輯,通過HandlerAdapter.handle去調用,最終會調用到ServletInvocableHandlerMethod.invokeAndHandle

	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		//具體調用邏輯,重點看
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
			//返回值處理
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

這個方法裏面主要看invokeForRequesthandleReturnValue的調用,前者是完成參數綁定並調用controller,後者則是對返回值進行處理並封裝到ModelAndViewContainer中。先來看invokeForRequest

	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		//獲取參數數組
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);
	}

doInvoke就是完成反射調用,主要還是看參數綁定的實現邏輯,在getMethodArgumentValues方法中:

	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		if (ObjectUtils.isEmpty(getMethodParameters())) {
			return EMPTY_ARGS;
		}
		//入參的包裝類,裡面包裝了參數類型,參數名稱,參數註解等等信息
		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			//設置參數名稱解析器
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			//典型的策略模式,根據parameter能否找到對應參數的處理類,能找到就返回true
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				//具體參數值解析過程,重點看看
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled..
				if (logger.isDebugEnabled()) {
					String error = ex.getMessage();
					if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, error));
					}
				}
				throw ex;
			}
		}
		return args;
	}

參數、返回值解析

因為參數類型非常多,同時還會伴隨各種註解,如:@RequestBody、@RequestParam、@PathVariable等,所以參數解析的工作是非常繁雜的,同時還要考慮到擴展性,所以SpringMVC依然採用了策略模式來完成對各種參數類型的解析綁定,其頂層接口就是HandlerMethodArgumentResolver,而默認SpringMVC提供的解析方式就高達20多種:

上面是類圖,讀者可根據自己熟悉的參數類型找到對應的類進行分析,最核心的還是要掌握這裏的設計思想。
接着方法調用完成后就是對返回值的處理,同樣的,返回值類型也是非常多,也可以使用各種註解標註,所以也是使用策略模式實現,其頂層接口是HandlerMethodReturnValueHandler,實現類如下:

調用完成之後就是執行後續操作了:執行中置過濾器、處理全局異常、視圖渲染以及執行後置過濾器,這些與主流程沒有太大關係,本篇不展開分析了,最後是MVC的執行時序圖:

總結

本篇是Spring核心原理系列的最後一篇,前前後后花了一個月時間,終於從宏觀上大致上理解了Spring的實現原理和運行機制,明白了之前項目中一些坑是如何產生的,最主要的是學到設計模式的運用以及如何利用Spring的一些常用的擴展點進行自定義擴展。但對於Spring這個龐大的體系來說,還有很多是要去理解學習的,尤其是設計思想,只有長期琢磨才能深刻的理解掌握。在我之前的文章中包括本篇還有很多沒分析到的細節,在後面我會不定期分享出來。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

工欲善其事,必先利其器 — Mac 軟件推薦(序)

背景

工欲善其事,必先利其器。​後面我將陸陸續續推薦一些軟件利器幫助大家提高效率(主要針對 Mac 電腦)。

如果你在使用 Mac 電腦,並且沒有如某些人那樣安裝並使用 Windows 系統,那麼你可以嘗試使用以下這些軟件。

在 Mac 裝 Windows 使用,感覺有點“暴殄天物”(文化有限,只能找到這個詞),沒有惡意黑 Windows,Windows 有 Windows 的使用場景,對於普通人民群眾來說,確實使用 Windows 夠了,微軟現在也出了不錯的筆記本。但你確實不該買 Mac 然後確使用 Windows 系統,這樣其實裝 X 效果不好。

這些軟件都是我自己使用過且覺得還不錯的,這些軟件或者可以極大地提高效率或者偶爾也足夠裝13(哈哈,亂入了一兩款 App)。

整理下來太多了,因為太多圖,放在一篇文章裏面感覺加載都有點問題(是不是暗示我要換手機了?)。正好有讀者反饋說之前發的有的內容太長太干,都看不下去了,因此,我進行了拆分(技術乾貨花的時間也久,產出沒那麼快)。正好用類似的文章休息下,不用動腦筋,1~2分鐘搞定,並且也有收穫,​一舉兩得。​

主角登場 Alfred

今天的主角是 Alfred。這個軟件很多文章都在說,我這裏就不多做過多介紹了。其具體效果跟 Mac 自帶的 Spotlight 類似,但功能會強大 N 個數量級倍。

我差不多 12 年開始接觸 Mac,當時還是窮學生,托香港的同學幫忙買的教育版 MacBook Air,現在還偶爾服役。但使用這款軟件是我 15 年快工作了才用上,後悔沒早知道呀,不過現在也已經陪伴我走了這麼多年了,首推就是這款軟件了。如果你看到這篇文章且還沒有用過,就趕緊用起來吧,免費版本的功能也都已經挺強悍了。

舉例說下常用的幾個功能:

文件搜索

類似 Windows 版本的 everything。 設置某個標識(示例中為 “’”)開頭,後面為關鍵字就開始全盤索引(當然可以配置過濾)了,找到搜索到的文件后,按 “->” 出現二級菜單,可以選擇下一步的操作。

比如複製,以此命令行 cd 到文件/目錄(後面有類似的工具推薦),複製文件路徑(finder 不比 windows 能夠方便 copy 文件路徑)等。

alfred-file-search

剪貼板歷史

可以幫你保存你最近的剪貼板歷史,通過快捷鍵選取粘貼。實際工作中經常遇到,本來要複製一個東西已經 cmd+c 了,這個時候又來一個更優先需要複製粘貼的,前面那個又被覆蓋了,還得再去複製一遍。有了這個功能就不愁了。

alfred-paste

各種搜索

  • 搜索引擎搜索

同樣可以設置關鍵字,比如 “google keywords”,回車就能直接打開 google 搜索。默認的有google/wiki/等等,這個還可以自己方便添加更多的搜索引擎,比如 baidu,必應,stackoverflow 等等。

  • 各種快捷搜索

其他的比如聯繫人搜索,快捷功能(lock/sleep/shutdown)等等,計算器(直接輸入等式即可),輸入應用名稱快速打開應用等等。

alfred-quick-search

Workflow

Workflow 是其更強大的賣點。比如以下是一些或者極其高效或者很有意思的 workflow。

  • Dash

堪稱程序員神器啊。 結合 Dash,能夠非常方便快捷地搜索某種語言的某個 API,再也不用邊寫邊打開瀏覽器去搜索了。

遇到了 某個 API 不太清楚,直接 ctrl + blank 輸入關鍵字就直接模糊搜索某 API 了。

alfred-dash

  • stackoverflow

其實這個通過在上面的搜索引起那裡設置也 OK 的。這裡是一個單獨的 workflow,同樣可以設置關鍵字(例如 st keywords) 就能直接搜索 stackoverflow 上相關問題。相當於在 google 搜索中 keywords site:stackoverflow.com

alfred-stackoverflow

  • youdao 翻譯

遇到中英文翻譯問題不用再打開瀏覽器去搜索了。

當然 Mac 自帶的取詞翻譯功能也挺不錯的: 不知道? 選中關鍵字,三指輕點觸控板。

mac-translate

  • zhihu

知乎搜索及知乎日報,可以設置關鍵字直接知乎搜索,或者列出當天的知乎日報推薦列表。

  • douban

豆瓣的相關功能,豆瓣讀書/電影等。最近聽到同事談論某電影,想看豆瓣評分多少? 很簡答, 直接 movie 電影名 就出來結果了,如圖:

  • 天氣

調用百度的 API 實現的快捷天氣預報

alfred-weather

  • mail

快速搜索郵件(這裏直接用的以前的截圖)。

alfred-mail

  • 印象筆記(evernote)

快速搜索印象筆記/evernote 中保存的內容。這個得首先去 印象筆記官網 生成一個 token,然後安裝好 alfred-evernote后,配置好(es-token 你自己的generated-token) token 成功后就可以使用了。

查詢有不同的語法格式,詳情可以查閱evernote 搜索語法。

alfred-印象筆記 workflow

搜索后直接回車打開是默認在應用程序中打開,按住 cmd 後會在瀏覽器中打開(由於最開始開發的作者是國際版 evernote,中國版補丁的作者也忘記改這個鏈接了,所以在瀏覽器中打開的跳轉鏈接不對,直接下載我修改后 workflow 是 OK 的 github),其實就是修改一下其中的 app.js中的 get-link 方法。

當然還有更多其他好玩有用的 workflow,你可以直接到github AlfredWorkflow“選購”,沒有的也可以自己實現一個也貢獻出來哦。方法也相對比較簡單,用 php/python 等都可以實現,你打開 alfred 設置項,雙擊具體某個 workflow 就能看到源碼。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

新能源汽車需求井噴 助推鋰材料超預期大漲

據中國汽車工業協會最新發佈數據,今年前6個月我國新能源汽車產量達到7.6萬輛,這一產量同比增幅達到2.5倍。   然而,新能源整車產量快速增長的同時,配套動力電池的產量卻出現缺口。“現在動力電池基本上只要能造出來,銷售出去的問題不大。”乘用車市場資訊聯席會秘書長崔東樹向記者表示,這不僅大大制約了新能源汽車產能的釋放,同時也影響了動力電池技術的進步,“大家都忙著造,很難有人沉下心來做研發。”   實際上,據專家介紹,新能源汽車電池在生產上的技術門檻並不高,這直接導致的是動力電池產能處於快速擴張當中。然而,大批量技術含量較低電池企業的投產,則可能讓國內電池產能由短缺轉向過剩。業內人士預計,隨著產能的快速實現,電池產業可能將在2016年下半年迎來洗牌。  
研發上與日韓有較大差距   “國內電池企業在自動化和研發能力上都與日韓企業有較大差距。”華霆動力技術有限公司的一位負責人向記者介紹,目前日韓企業在生產成本和技術上都整體領先於國內動力電池企業。   一位電池技術專家告訴記者,現階段國內動力電池企業的生產成本大約是2元每瓦時,按照容量為25千瓦時的動力電池計算,成本大約在5萬元左右。   這樣的成本明顯高於LG、三星等韓國動力電池生產企業。據介紹,韓企的成本已降至1.8元每瓦時以下,這意味著同樣是25千瓦時的動力電池,其成本將會低於4.5萬元。   不僅如此,國內電池企業的能量密度也低於日韓企業。上述電池技術專家介紹,國內較好的動力電池模組的能量密度在130瓦時每千克,而松下等日本企業生產動力電池模組的能量密度則能超過200瓦時每千克,LG、三星等韓國企業所生產動力電池也能達到180瓦時每千克左右。   這意味著,國內電池企業生產容量25千瓦時的電池重量將超過190千克,而同樣容量的電池,韓企生產出來的重量為140千克左右,部分日企則能達到125千克。這對於新能源整車的輕量化影響不小。   “目前,在動力電池領域,松下領先LG和三星12~18個月,而LG和三星則領先國內企業12~18個月。”國內某動力電池企業的負責人向記者坦言,“國內電池企業的自動化程度不高,研發和製造水準都趕不上。”   乘聯會資料顯示,國內新能源整車企業除比亞迪擁有自己的配套電池廠外,大多數都通過外採的方式解決電池問題。  
明年底行業恐面臨洗牌   “現在國內電池企業的狀態普遍很浮躁。”上述電池企業負責人向記者表示,由於新能源車企對配套電池的需求持續旺盛,電池企業對產能投入的熱情已大於對研發和技術的追求。 隨著近年來新能源汽車產銷量高速增長,汽車電池產量的缺口也逐步展現出來。這激發了配套電池企業的投產熱情。   一位動力電池公司負責人向記者介紹,僅LG、三星、力神和CATL四家動力電池企業,明年將投產的產能就高達10吉瓦時以上,而每吉瓦時的電池產能可以滿足大約4萬輛新能源汽車的需要,也就是說,僅上述四家動力電池企業的產能就可以滿足40萬輛新能源車的裝配需要。“動力電池的技術門檻並不高。”一位充電設施企業的負責人告訴記者,目前動力電池的核心技術已相對成熟,因此企業實現投產並不難,這造成很多實力並不強大的電池企業紛紛上專案。   不過,“國內主流的12家新能源整車企業的採購,基本上都來自5家主要的動力電池廠家。”一位電池企業負責人向記者表示,隨著具備技術優勢的大電池企業產能的跟上,在技術和成本上都不具備優勢的小企業將很難生存,因此他預測“2016年底電池企業將面臨洗牌”。   乘聯會的資料顯示,目前電池企業CATL主要供應北汽、廣汽、長安和宇通等新能源車企;天津力神主要供應江淮、康迪、廣汽和宇通等;國軒高科則供應康迪、江淮、金龍、安凱和申沃等車企;萬向億能則供應上汽、奇瑞、廣汽、青年等車企。   文章來源:中財網

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

EnergyTrend微信新服務“太陽能價格評析” 立即關注!

EnergyTrend微信(簡中版)
只要在微信關注我們

EnergyTrend專業的分析師將直接告訴您
最即時的太陽能市場價格微評論

讓您一機在手隨時隨地輕鬆掌握太陽能市場最新動態

[加入關注]
1.微信→朋友→新增朋友→掃描QR Code→將右側QR Code置於方框內→關注
2.微信→朋友→新增朋友→查找官方帳戶→輸入”集邦新能源網”→關注

 

 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

元晶維持獲利 第一季每股純益 0.11 元

今年第 1 季太陽能市況依然不佳,元晶在產能去瓶頸化,固定成本有效降低,營運仍維持獲利狀態,稅後淨利 3,643 萬元,每股純益 0.11 元。   元晶近年積極拓展高效產品市場,去年雖因美國雙反調查影響市場需求降溫,產品價格下滑,獲利縮水,不過,元晶去年仍有獲利,稅後淨利 1.7 億元,每股純益 0.56 元。   元晶目前太陽能電池年產能 720MW 規模,預計異質接面 (HiT) 高效太陽能電池技術達量產水準後,展開新一波擴產計畫,初步規劃 2017 年產能將倍增至 1,500MW 規模。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

中國積極推動無補貼綠能專案,太陽能市場有望回穩

雖然中國政府在 2018 年中旬推出 531 新政,讓該國投資去年太陽能總投資下降 53%,重挫當地太陽能投資與建設發展,但該國政府目前已推出無補貼再生能源計畫,或許有望重振中國太陽能市場,彭博能源財經(BNEF)推測 2019 年中國太陽能新增裝置量仍可達到 34-44GW。

中國過去一直以來對當地太陽能產業發展相當優待,提供優渥的補貼金額與固定電價價格以鼓勵太陽能等再生能源發展,大量企業開始投資太陽能產業,形成一股靠補貼攀升的太陽能熱潮,造成產能過剩與補貼缺口過大,據統計,截至 2017 年底,再生能源補貼缺口已達 1,000 億人民幣(約新台幣 4,600 億元)。

因此中國政府在 2018 年中無預警推出新政策,大幅限制電廠建設與補助,為中國高速發展的太陽能產業踩下煞車踏板,未來也將採取嚴控指標方式,並積極鼓勵低價補貼或是無補貼專案。

無補貼專案優惠多

就好比中國在去年 8 月推出首項無補貼太陽能示範專案規劃,每省限定 300-500MW,並在 10 月開始申請、預計在 2019 年 3 月前後開工拚年底前併網發電。日前該政府也為了促進再生能源無補貼發展公布 12 項全新計畫,像是要做好風電、光電發電量檢測,不能在電力供過於求等預警紅色地區推行專案,廠商也要保證將來可以全額併網發電與不浪費。

中國此次將「無補貼專案」定義為無國家補貼、先導計畫不限規模、不佔用補貼指標的計畫,因此在政策方面也有釋出許多善意,要求地方政府對無補貼太陽能與風電在土地利用、成本上給予支持之餘,政府也會為綠色證書市場化交易指出明燈,未來無補貼或是低價補貼專案可以透過中國綠色電力證書交易獲益,與此同時也要求電力公司讓無補貼專案優先發電和全額收購電量,並鼓勵金融機構支持無補貼專案。

另外中國政府也將執行固定電價(FIT)政策,無補貼、低價補貼風電與太陽能專案可簽定 20 年以上的購電合約,提高電價的長期穩定性,也不要會求參與跨區電力市場化交易。

中國國家發改委表示,這些專案獲得核准後就能在 2020 年底前開始施工,但沒有在限定時間完工的專案將會被取消,為其他無補貼專案挪出空間。並明確指出,從現在到 2020 年底前獲準的專案都可採用這項政策,政府則會在 2020 年後依據技術與成本擬定新的政策。

只不過中國目前也有不少地方無法規劃無補貼專案,對此中國政府也表示,推動低價專案並非立即取消所有補貼,若無法達到無補貼或低價補貼仍可採競爭性配置專案政策,並希望這些專案可透過競爭降低電價以減少政府補貼。

BNEF 分析師 Jonathan Luan 表示,這些政策代表著未來中國將朝無補貼再生能源邁進,並有機會促成全新的太陽能專案,公司則對中國的太陽能市場樂觀看待,預測 2019 年新增裝置量可達 34-44GW。

(合作媒體:。首圖來源: CC BY 2.0)

延伸閱讀:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

2019 太陽能五大趨勢:市場走向穩定與分散,度電成本將成為供應鏈價格依歸

2018 年可說是太陽能產業近年來波動最大的一年,歷經美國 201、301 條款,中國 531 新政,印度防衛性關稅,歐盟 MIP 結束等變動,從最上游的供應鏈到最下游的系統端都呈現極不穩定的狀態。由 EnergyTrend 所盤整的 2019 年五大趨勢來看,市況將會好轉,且產業也將在持續的變動中逐漸成熟。

趨勢一:2018 年低谷不低,2019 需求再創新高

中國的「531 新政」雖對市場造成衝擊,但因海外市場的需求走強,加上中國市場所受衝擊輕於預期,使 2018 年出現「低谷不低」的現象,預期全年新增併網量可達到 103GW(實際出貨量約95GW),年增4.9%。

展望 2019 年,在政策鼓勵與供應鏈價格持續下降的推波助瀾下,全球需求預計將繼續正成長,其中又以歐洲的成長幅度最大,最多可超過五成。2019 年預期新增併網量將來到 111.3GW,出現 7.7% 的成長,再次創下歷史新高。

趨勢二:市場持續分散,2019 年 GW 級市場增至 15 

全球市場規模自 2018 年起預計會持穩在 100-120GW 之間,各年度需求量變化幅度將低於 10%。而根據 EnergyTrend 的最新需求報告統計,GW 級市場從 2016  年的 6  個成長到 2019 年將有 15 個,可見市場持續分散化的趨勢。

2016-2020 年 GW 級市場

中國、美國將持續穩居全球前二大市場,印度則從 2017 年起成為第三大需求國,日本次之。東南亞、北非、中東、拉丁美洲等新興市場自 2018 年崛起,如中東地區 2018 年全年需求預計將較 2017 年增加近 100%,2019  年還將增加 50% 左右。全球市場規模自 2019 年起將趨於穩定,印度最有可能出現較大幅度的需求成長。

2016-2023 年全球市場需求趨勢

趨勢三:供應鏈上游更為集中,單晶將逆轉市佔

雖然供應鏈整體在 2018 年陷於供過於求、低利潤的困境,但技術和成本優勢較強、全球布局較廣的一線大廠仍保有強勁的營運動能,既有的擴產計畫多能持續進行,使供應鏈廠家有持續集中化的現象。根據 EnergyTrend  的供給資料庫,中國前五大多晶矽廠的新產能預計在 2Q19 陸續開出,屆時前五大廠的產能將佔全球近 70%,且現金成本更具競爭力。在矽晶圓環節,則將呈現隆基與中環雙龍頭主宰市場的現象,單晶供應鏈也將因而變得更具主導性,有機會拉升全年單晶佔比來到 6 成,2017 年底展開的單多晶之戰逐漸落幕。

趨勢四:雙面產品產能倍增,P-PERC 效率還有成長空間

雙面電池技術已十分成熟,且可在幾乎不增加額外成本的前提下創造額外的發電收益,因此產能比例持續上升,預計 2019 年雙面電池的總產能將接近 40GW,且以雙面單晶 PERC 電池產能增加最多。另一方面,單晶 PERC 電池的量產效率仍有成長空間。據 EnergyTrend 調查,單晶 PERC 電池的平均量產效率在 2019 年上半年即可站上 22%,且還可導入更多技術,在 2019 年底效率可望上看 23%。而單晶 PERC 的強勢也壓縮了次世代 N 型技術的發展空間,2019 年 N 型產能預期僅會有小幅增加。

雙面電池產能成長趨勢(Unit: GW)

趨勢五:均化度電成本成為模組價格降價指標

供應鏈價格持續下探,使太陽能逐步朝擺脫補貼、平價上網的方向邁進;而無補貼系統的普及程度及其實際的均化度電成本(LCOE)將成為未來供應鏈的價格指標。

太陽能產業在 2018 年面臨強大考驗,但同時也進入產業盤整階段,預期長期發展將趨於穩定化與健康化,供應鏈的價格將以整體系統的度電成本為依歸。儲能系統與智慧電網技術的投入,將成為太陽能產業進一步市場化的關鍵。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

澳洲新南威爾斯省 無尾熊2050年恐滅絕

摘錄自2018年09月08日蘋果日報澳洲報導

澳洲動物保護生物學家泰勒(Martin Taylor)周五(7日)提出一份報告,指出新南威爾士省如果繼續目前的土地清理工作,至2050年左右,當地的無尾熊恐面臨滅絕。

《澳洲新聞網》報導,這份報告由澳洲世界自然基金會(WFF)與自然保護委員會(NCC)發布,分析新南威爾士省北部衛星圖像,評估土地清理工作對瀕危物種的影響。泰勒指出,如果清理速度沒有減緩,將會對當地野生動物造成嚴重後果。「我們看到無尾熊的棲地正以驚人的速度消失,每個人都在告訴我們,無尾熊的數量正在下降。如果速度不變,本世紀中葉,新南威爾士省將沒有野生無尾熊。」泰勒的研究發現,自2016年至今,完全或部分被清理的土地面積幾乎增加了2倍,從2845公頃增加到8194公頃。

然而新南威爾士省政府對這項警告提出反駁,當地環境廳長辦公室聲明指控,澳洲世界自然基金會(WFF)與自然保護委員會(NCC)正在散布恐慌。並強調「政府承諾會為無尾熊提供更多自然棲地,並改善道路殺手問題,也已投入4500萬澳幣的大量經費。」政府也強調「無尾熊的數量事實上高於預期。」

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

空拍機驚嚇小熊可避免 論文提出技術與倫理建議:預警為要

環境資訊中心記者 鄭雅云報導

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心