為搶市占 傳 LG Chem 明年起腰斬電池價格

日廠有意增產搶市佔,車用電池龍頭 LG Chem 祭出激烈手段,據稱 2016 年起該公司電池售價將對半腰斬,藉此擊退對手。   消息人士透露,LG Chem 在中國生產的部分電動車電池,2016 年價格將減半,此舉是為了先發制人退敵,並因應中美兩國電動車需求大增。他說,LG Chem 將銷售更多電池,因此價格下殺不會拖累獲利。該公司表示,定價策略將保持彈性,價格視市況調整,是否降價仍須與客戶討論之後決定。   LG Chem 目前供應電動車電池給全球 30 多家車廠。該公司中國南京工廠將於明年啟用,增產不成問題,LG Chem 預估新廠年度產能為 10 萬顆電動車電池。該公司高層表示, LG Chem 長年研發材料、壓低成本,開始出現成效,明年起大型電池業務可望損益兩平,獲利提升讓他們決定降低售價。

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

LG Chem 奪奧迪電動車訂單後 電動車電池市場呈三強鼎立

豪華車製造商奧迪(Audi)於 19 日宣布,該公司首款全電動車充一次電能跑 310 英哩(約合 500 公里),使用的是南韓樂金化學公司(LG Chem)製造的電池。至此更加確認樂金和三星電子旗下的 SDI 以及 Panasonic 3 家亞洲製造商,已成電動車電池市場目前的最大贏家。    根據波士頓新興科技研究業者 Lux 本周公布的報告,電動車電池市場規模預估到 2020 年前將從今年的 50 億美元擴大到 300 億美元,且 5 年內大致由上述亞洲供應商三分天下,約 8 成的市場都在他們手中。    據 Lux 統計,目前以 Panasonic 為市場龍頭,過去這 12 個月的占有率達 38%,供應特斯拉(Tesla)、福斯和福特汽車所需的電動車電池。如果特斯拉的銷售增加, Panasonic 的占有率勢必隨之攀高。    然而,樂金化學迅速竄起,不容小覷,目前市占率為 11%,通用、雷諾、富豪、戴姆勒和福斯都是客戶,如果再奪得日產 Leaf 的訂單,且特斯拉未能達到銷售目標,樂金化學很可能在 2020 年前把 Panasonic 擠下冠軍寶座。  

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

巴西奧運電動車組出廠 中車長客 100 列全數完工

為迎接 2016 年奧運,巴西和中國中車長客股份公司簽約,中國將提供 120 輛電動車組和 114 輛地鐵車輛,長客公司已於 19 日順利交付最後 100 列電動車組。   中新社報導,隨著最後一列電動車組(EMU)19 日在長春下線,由中國中車長客股份公司為巴西里約熱內盧製造的 100 列電動車組全部交付,每列 4 輛編組,最高時速可達 100 公里。這些列車屆時將肩負 2016 奧運交通運輸。  

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

BMW 將推新款電動車 競爭目標瞄準 Model S

汽車大廠 BMW 也投入純電動車與油電混合車的開發。但在電動車的領域當中,純電與油電混合實際上屬於光譜的兩端,BMW 分別推出的 i3 純種電動車與 i8 插電式油電混合車在價格上也完全在不一樣的檔次,分別針對不同的客群。   不過,《路透社》報導指出,BMW 的野心不僅止於此。BMW 執行長 Harald Krueger 日前向德國媒體指出,在 i3 與 i8 之間還存有發展其他車款的空間,意味著 BMW 或許將再推出其他款式的電動車。   近來也有報導指出,BMW 或將為傳聞中的 i5 增加 2 項特色,也就是插電式油電混合動力與純電動力。消費者因此能按自身需求,在這兩種系統間取捨。研發中的 i5 也有可能延續大 5 系列 (5-series) 的外觀,而最主要的競爭對手,便是電動車大廠特斯拉 (Tesla) 的 Model S 型電動車。   由於特斯拉的電動車已浮現於市場上相當的時間,加上眾家傳統汽車大廠也紛紛投入電動車的研發,因此要出現能與 Model S 型正面對抗的電動車,不過也只是時間上的問題而已。然而,市場仍須持續關注,畢竟這場電動車大戰誰輸誰贏目前仍難有定論。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

mpvue+小程序雲開發,純前端實現婚禮邀請函(相冊小程序)

請勿使用本文章及源碼作為商業用途!

前言

當初做這個小程序是為了婚禮前的需要,結婚之後,希望這個小程序能夠留存下來,特地花了一些空閑時間將小程序轉化成為“相冊類小程序”

體驗碼

準備工作

  1. mpvue框架 
  2. 小程序·雲開發 

注意:使用mpvue前,首先你得先熟悉vue框架的基本使用 

項目結構介紹

注意:接下來展示的代碼,有幾個對比,分別是本人優化前和優化后的代碼對比,感興趣的可以着重看一下優化后的成熟寫法。

  • common目錄: 放一些公共資源,如js,css,json
  • components目錄:組件相關的.vue文件都放在這裏
  • pages目錄:所有頁面都放在這個目錄
  • utils目錄:使用mpvue時自動生成,可忽略
  • app.json文件:
{
  "pages": [ "pages/index/main", "pages/photo/main", "pages/map/main", "pages/greet/main", "pages/message/main" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "tabBar": { "color": "#ccc", "selectedColor": "#ff4c91", "borderStyle": "white", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/main", "iconPath": "static/images/1-1.png", "selectedIconPath": "static/images/1-2.png", "text": "邀請函" }, { "pagePath": "pages/photo/main", "iconPath": "static/images/2-1.png", "selectedIconPath": "static/images/2-2.png", "text": "甜蜜相冊" }, { "pagePath": "pages/map/main", "iconPath": "static/images/3-1.png", "selectedIconPath": "static/images/3-2.png", "text": "酒店導航" }, { "pagePath": "pages/greet/main", "iconPath": "static/images/4-1.png", "selectedIconPath": "static/images/4-2.png", "text": "好友祝福" }, { "pagePath": "pages/message/main", "iconPath": "static/images/5-1.png", "selectedIconPath": "static/images/5-2.png", "text": "留言評論" } ] }, "requiredBackgroundModes": ["audio"] }
  • App.vue文件 (本人主要是為了增加項目更新后的提醒),所以在這個文件加了些相關內容,內容如下:
<script>
export default { onLaunch () { // 檢測小程序是否有新版本更新 if (wx.canIUse('getUpdateManager')) { const updateManager = wx.getUpdateManager() updateManager.onCheckForUpdate(function (res) { // 請求完新版本信息的回調 if (res.hasUpdate) { updateManager.onUpdateReady(function () { wx.showModal({ title: '更新提示', content: '新版本已經準備好,是否重啟應用?', success: function (res) { if (res.confirm) { // 新的版本已經下載好,調用 applyUpdate 應用新版本並重啟  updateManager.applyUpdate() } } }) }) // 小程序有新版本,會主動觸發下載操作(無需開發者觸發)  wx.getUpdateManager().onUpdateFailed(function () { // 當新版本下載失敗,會進行回調  wx.showModal({ title: '提示', content: '檢查到有新版本,下載失敗,請檢查網絡設置', showCancel: false }) }) } }) } else { // 版本過低則無法使用該方法  wx.showModal({ title: '提示', confirmColor: '#5BB53C', content: '當前微信版本過低,無法使用該功能,請升級到最新微信版本后重試。' }) } } } </script> <style lang="stylus"> page height 100% image display block </style>
  • main.js文件:
import Vue from 'vue'
import App from './App' Vue.config.productionTip = false App.mpType = 'app' wx.cloud.init({ env: '雲開發環境ID' }) const app = new Vue(App) app.$mount()
  • functions目錄:主要放一些雲函數,這裏不清楚雲函數的文章後面會提及
  • images目錄:主要放一些靜態資源圖片

頁面介紹

首頁——邀請函

首頁着重和大家講解下背景音樂的實現方法

const audioCtx = wx.createInnerAudioContext()

 接口獲取實例

接着,通過實例的相關方法來實現音樂的播放與暫停功能

具體代碼如下:

<script>
import IndexSwiper from 'components/indexSwiper' import tools from 'common/js/h_tools' const audioCtx = wx.createInnerAudioContext() export default { name: 'Index', components: { IndexSwiper }, data () { return { isPlay: true, list: [] } }, onShow () { const that = this that.isPlay = true that.getMusicUrl() }, methods: { audioPlay () { const that = this if (that.isPlay) { audioCtx.pause() that.isPlay = false tools.showToast('您已暫停音樂播放~') } else { audioCtx.play() that.isPlay = true tools.showToast('背景音樂已開啟~') } }, getList () { const that = this const db = wx.cloud.database() const banner = db.collection('banner') banner.get().then(res => { that.list = res.data[0].bannerList }) }, getMusicUrl () { const that = this const db = wx.cloud.database() const music = db.collection('music') music.get().then(res => { let musicUrl = res.data[0].musicUrl audioCtx.src = musicUrl audioCtx.loop = true audioCtx.play() that.getList() }) } }, onShareAppMessage: function (res) { return { path: '/pages/index/main' } } } </script>

以上代碼中使用到了雲開發相關功能,文章後面會介紹,請大家稍安勿躁

相冊頁——就一個輪播圖,這裏就不過多介紹

地圖頁——這裏着重講一下地圖標籤map

map標籤 

這裏講一下標記點markers:

data () {
    return { // qqSdk: '',  markers: [{ iconPath: '../../static/images/nav.png', id: 0, latitude: 30.08059, longitude: 115.93027, width: 50, height: 50 }] } }
<template>
    <div class="map">
        <image mode="aspectFit" class="head-img" src="../../static/images/t1.png"/>
        <map class="content" id="map" longitude="115.93027" latitude="30.08059" :markers="markers" scale="18" @tap="toNav">
        </map>
        <div class="call">
            <div class="left" @tap="linkHe">
                <image src="../../static/images/he.png"/>
                <span>呼叫新郎</span>
            </div>
            <div class="right" @tap="linkShe">
                <image src="../../static/images/she.png"/>
                <span>呼叫新娘</span>
            </div>
        </div>
        <image class="footer" src="../../static/images/grren-flower-line.png"/>
    </div>
</template>

祝福頁——也是雲開發相關內容,後面會介紹

留言頁——也是雲開發相關內容,後面會介紹

雲開發介紹

project.config.json文件:

"cloudfunctionRoot": "static/functions/"

進行雲開發首先我們需要找到上面這個文件,在上面這個json文件中加上上面這行代碼

cloudfunctionRoot 用於指定存放雲函數的目錄

app.json文件:

"window": {
    "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle": "black" }, "cloud": true

增加字段 "cloud": true實現雲開發能力的兼容性

開通雲開發

在開發者工具工具欄左側,點擊 “雲開發” 按鈕即可開通雲開發

雲開發控制台

數據庫

 雲開發提供了一個 JSON 數據庫

 

存儲

 

雲開發提供了一塊存儲空間,提供了上傳文件到雲端、帶權限管理的雲端下載能力,開發者可以在小程序端和雲函數端通過 API 使用雲存儲功能。

 

雲函數

 

雲函數是一段運行在雲端的代碼,無需管理服務器,在開發工具內編寫、一鍵上傳部署即可運行後端代碼。

使用雲開發

雲能力初始化

在小程序端開始使用雲能力前,需先調用 wx.cloud.init 方法完成雲能力初始化

import Vue from 'vue'
import App from './App' Vue.config.productionTip = false App.mpType = 'app' wx.cloud.init({ env: '雲開發環境ID' }) const app = new Vue(App) app.$mount()

數據庫的使用

在開始使用數據庫 API 進行增刪改查操作之前,需要先獲取數據庫的引用。以下調用獲取默認環境的數據庫的引用:

const db = wx.cloud.database()

 要操作一個集合,需先獲取它的引用:

const todos = db.collection('todos')

操作數據庫的相關示例

例:首頁獲取背景音樂資源

getMusicUrl () {
      const that = this const db = wx.cloud.database() const music = db.collection('music') music.get().then(res => { let musicUrl = res.data[0].musicUrl audioCtx.src = musicUrl audioCtx.loop = true audioCtx.play() that.getList() }) }

 例:首頁獲取輪播圖數組

getList () {
      const that = this const db = wx.cloud.database() const banner = db.collection('banner') banner.get().then(res => { that.list = res.data[0].bannerList }) }

例:祝福頁,用戶送上祝福存儲用戶

<script>
import tools from 'common/js/h_tools' export default { name: 'Greet', data () { return { userList: [], openId: '', userInfo: '' } }, onShow () { const that = this that.getUserList() }, methods: { scroll (e) { console.log(e) }, sendGreet (e) { const that = this if (e.target.errMsg === 'getUserInfo:ok') { wx.getUserInfo({ success: function (res) { that.userInfo = res.userInfo that.getOpenId() } }) } }, addUser () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.add({ data: { user: that.userInfo } }).then(res => { that.getUserList() }) }, getOpenId () { const that = this wx.cloud.callFunction({ name: 'user', data: {} }).then(res => { that.openId = res.result.openid that.getIsExist() }) }, getIsExist () { const that = this const db = wx.cloud.database() const user = db.collection('user') user.where({ _openid: that.openId }).get().then(res => { if (res.data.length === 0) { that.addUser() } else { tools.showToast('您已經送過祝福了~') } }) }, getUserList () { const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) } } } </script>

獲取送祝福的好友列表

getUserList () {
      const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) }

這裏用到了雲函數,之所以用雲函數是因為小程序端在獲取集合數據時服務器一次默認並且最多返回 20 條記錄,雲函數端這個数字則是 100。

雲函數的使用方法

上面我們講過在project.config.json文件中配置雲函數存放位置

下面是雲函數messageList的index.js文件:

不成熟寫法:

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database() const MAX_LIMIT = 100 exports.main = async (event, context) => { // 先取出集合記錄總數 const countResult = await db.collection('message').count() const total = countResult.total // 計算需分幾次取 const batchTimes = Math.ceil(total / 100) // 承載所有讀操作的 promise 的數組 const tasks = [] for (let i = 0; i < batchTimes; i++) { const promise = db.collection('message').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() tasks.push(promise) } // 等待所有 return (await Promise.all(tasks)).reduce((acc, cur) => ({ data: acc.data.concat(cur.data), errMsg: acc.errMsg })) }

成熟寫法(分頁查詢):

const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database() exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const dbName = 'message' const filter = event.filter ? event.filter : null const pageNum = event.pageNum ? event.pageNum : 1 const pageSize = event.pageSize ? event.pageSize : 10 const countResult = await db.collection(dbName).where(filter).count() const total = countResult.total const totalPage = Math.ceil(total / pageSize) let hasMore if (pageNum >= totalPage) { hasMore = false } else { hasMore = true } return db.collection(dbName).orderBy('time', 'desc').where(filter).skip((pageNum - 1) * pageSize).limit(pageSize).get().then(res => { res.hasMore = hasMore res.total = total res.openId = wxContext.OPENID return res }) }

使用雲函數前,在開發者工具上,找到messageList目錄,右鍵如圖:

 點擊上傳並部署:雲端安裝依賴(不上傳node_modules)

得到如圖的提示:

 安裝完點擊完成就能使用當前雲函數了,使用方法即:

getUserList () {
      const that = this wx.cloud.callFunction({ name: 'userList', data: {} }).then(res => { that.userList = res.result.data.reverse() }) }

數組之所以要倒序是因為希望新祝福的的用戶在最前面显示 

用戶送上祝福的時候存儲用戶

這裏我們用到了雲函數獲取用戶信息,

當小程序端調用雲函數時,雲函數的傳入參數中會被注入小程序端用戶的 openid,開發者無需校驗 openid 的正確性,因為微信已經完成了這部分鑒權,開發者可以直接使用該 openid

不成熟寫法:

下面是雲函數user的index.js文件:

// 雲函數入口文件
const cloud = require('wx-server-sdk')

cloud.init()

// 雲函數入口函數
exports.main = async (event, context) => { const wxContext = cloud.getWXContext() return { event, openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID } }

主要是為了獲取當前操作用戶的openid,獲取當前用戶的openid方法:

getOpenId () {
      const that = this wx.cloud.callFunction({ name: 'user', data: {} }).then(res => { that.openId = res.result.openid that.getIsExist() }) }

接着判斷當前用戶是否已經存在於數據庫中,即getIsExist()方法:

getIsExist () {
      const that = this const db = wx.cloud.database() const user = db.collection('user') user.where({ _openid: that.openId }).get().then(res => { if (res.data.length === 0) { that.addUser() } else { tools.showToast('您已經送過祝福了~') } }) }

如果得到的數組長度為零則添加改用戶到數據庫,否則則提醒當前用戶已經送過祝福

接下來介紹存儲用戶信息的方法,即addUser():

addUser () {
      const that = this const db = wx.cloud.database() const user = db.collection('user') user.add({ data: { user: that.userInfo } }).then(res => { that.getUserList() }) }

存入到數據庫的信息是這樣的:

成熟寫法(使用雲函數一次搞定):

// 雲函數入口文件
const cloud = require('wx-server-sdk')
cloud.init()
const db = cloud.database() exports.main = async (event, context) => { const wxContext = cloud.getWXContext() const dbName = 'user' const filter = { _openid: wxContext.OPENID } const countResult = await db.collection(dbName).where(filter).count() const total = countResult.total if (total) { return { has: true } } else { return db.collection(dbName).add({ data: { user: event.user, _openid: wxContext.OPENID, time: db.serverDate() } }) } }
toMessage (e) {
      const that = this
      if (e.target.errMsg === 'getUserInfo:ok') { wx.getUserInfo({ success: function (res) { that.userInfo = res.userInfo wx.navigateTo({ url: `/pages/writeMessage/main?avatar=${that.userInfo.avatarUrl}&name=${that.userInfo.nickName}`  }) that.addUser(that.userInfo) } }) } }, addUser (obj) { wx.cloud.callFunction({ name: 'addUser', data: { user: obj } }) }

總結 

大概的功能就是這麼多,希望可以幫助到大家,覺得寫得不錯的記得給作者點個贊,你們的支持是我不斷更新的最大動力!

源碼地址

後續優化

  • 留言審核
  • 一些動畫效果
  • 分頁處理
  • 雲函數優化
  • 回到頂部

最後

一開始不清楚隨筆和文章的區別,還是希望這篇隨筆能展示在博客園首頁(所以改成隨筆),讓更多的朋友看到

希望對那些有想法又不會後台開發的朋友一些啟示作用,祝你早日做出只屬於自己的小程序。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

Springboot中的緩存Cache和CacheManager原理介紹

背景理解

什麼是緩存,為什麼要用緩存

程序運行中,在內存保持一定時間不變的數據就是緩存。簡單到寫一個Map,裏面放着一些key,value數據,就已經是個緩存了

所以緩存並不是什麼高大上的技術,只是個概念,把要多次使用的東西存在一個變量里,時不時取出來使用,就達到了緩存的目的,緩存就是存放數據的容器

那為什麼要用緩存呢,是因為要多次使用。一個程序總有一些數據時可預見被多次使用(預見的準不準就是常說的命中率)

比如一個複雜的計算結果,一次數據庫訪問取得的數據等耗時耗資源的數據就能放入緩存,目的就是為了節省開銷,我們要用有限的資源(CPU,內存,帶寬等等)盡量做最多的事情。

為什麼要用SpringCache(緩存的演變過程)

緩存的思考

如果我們要設計一個緩存,最基本的功能是存和取:

1.能在緩存里存放數據

2.能在緩存里取出數據

可是這不夠呀,比如以下的思考

1.取數據時判斷,數據是否存在,如果不存在是不是要數據庫取

2.如果是過期的內容是不是要更新

3.如果我有多個緩存,一個是我自己設計的HashMap緩存,一個是名聲很大的redis,還有….,那需要個緩存管理器呀

為了讓緩存更好用,更“智能”,越來越多的需求就會被提出來,而緩存就是這樣一步步演變直到SpringCache橫空出世,功能十分強大(說白了就是我們少寫很多代碼)

SpringCache的好處

SpringCache包含兩個頂級接口,Cache(緩存)和CacheManager(緩存管理器),顧名思義,用CacheManager去管理一堆Cache。

最最關鍵的地方:抱緊了Spring的大腿,可以使用註解就能完成數據進入緩存!!

給大家舉個例子,就知道多簡單了

首先,Springboot中會自動加載一個CacheManager(它有默認的實現類),所以只要寫好一個自定義的Cache即可(如果想用系統定義好的或者第三方如RedisCache也行,記得向Spring註冊這個bean即可)

@Component
public class MyCache implements Cache {
  /*
       實現接口方法,一些關於數據set和get的方法
       CacheManager是根據Cache的名字進行管理的
       所以假設這個Cache名為MyCache
  */  
}

然後在得出數據的方法上寫上註釋即可

@Cacheable(value = "MyCache",key = "#id")
public String getNavegationURLs(String id) {
        //一個獲取數據的方法
}

這樣就會在調用這個方法時,會以id為key值,在名為MyCache的Cache容器中查找(註解中value就是緩存名字,不同名字指定使用不同的緩存)

如果沒查到,則執行方法 getNavegationURLs,將返回值存入緩存

如果找到了,就直接將從緩存取值,直接返回,不用執行方法 getNavegationURLs

還有其他方便的Cache註解自行百度,重要的是我們根本不用寫任何關於調用緩存的邏輯代碼,只用關注於緩存自身的邏輯

註解如何起作用的,源碼流程大致了解

為什麼要了解源碼

最直接的原因是因為SpringCache是不支持靈活的緩存時間設置的,所以想了解大概的來龍去脈去實現一個支持緩存過期時間設置和自動更新的類(之後會寫實現博文)。

高大上的原因是想通過這次探索,去了解下Spring對類的管理機制,去接觸下AOP的實現

SpringCache源碼簡單分析

大家從上面例子有沒發現問題,Cache和CacheManager是怎樣做關聯的,其實是Spring掃包實現的

凡是繼承了Cache接口的類,都會被自動注入進CacheManager中,最終存儲於CacheManager的實現類中

 

 接着會生成被@Cacheable(或者其他SpringCache註解修飾過)的代理類,並會將管理它的CacheManager賦值進去

 看這段代碼,就知道如果要設置多個CacheManager,就得在眾多實現類的其中一個加上@Primary,不然會Spring會報錯能選擇的Bean太多而不知道用哪個

 

代理類生成后(包括會根據不同的註解生成信息類CacheOperationMetadata,到時候就會根據這個類的內容進行緩存操作,說白了就是調用我們實現Cache裏面的各種方法)

Springboot底層初始化完成后,進入我們寫的代碼邏輯

如果這時進入了該類的方法,如:

 

 代碼跟進去,你會神奇的發現進入了代理類的intercept方法,怎麼進去的呢~(具體原理看下面3.0)

 這裏面就會根據註解類型,進行緩存的邏輯判斷,然後決定會不會調用我們寫的方法~

 代理類原理介紹(AOP切面之類的都是通過代理哦)

Spring代理分為兩種:

1.JDK原生動態代理,要求被代理的類需要實現接口(通過接口來實現的代理

那麼代理類滿足以下條件:

首先實現一個InvocationHandler,方法調用會被轉發到該類的invoke()方法。 意思是:對代理對象的所有接口方法調用都會轉發到InvocationHandler.invoke()方法,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數,加入日誌功能、安全檢查功能等;之後我們通過某種方式執行真正的方法體  
2.CGLIB動態代理,不要求被代理的類需要實現接口,但是final的方法無法被代理(
通過繼承來實現代理) 那麼代理類滿足以下條件: 實現一個MethodInterceptor,方法調用會被轉發到該類的intercept()方法

具體內容可以參考這篇精品博客:

如果你想自己實現代理類(就是不喜歡用工具包),其實也行啊,輸出符合class規範的二進制字節碼就行啦~~~(認真學習JVM規範吧)

 

至此,該分享的就分享完啦,有什麼問題歡迎留言一起探討~

 

 

 

 

 

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

【集合系列】- 初探java集合框架圖

一、集合類簡介

Java集合就像一種容器,可以把多個對象(實際上是對象的引用,但習慣上都稱對象)“丟進”該容器中。從Java 5 增加了泛型以後,Java集合可以記住容器中對象的數據類型,使得編碼更加簡潔、健壯。

Java集合大致可以分為兩大體系,一個是Collection,另一個是Map

  • Collection :主要由List、Set、Queue接口組成,List代表有序、重複的集合;其中Set代表無序、不可重複的集合;Java 5 又增加了Queue體系集合,代表一種隊列集合實現。
  • Map:則代表具有映射關係的鍵值對集合。

java.util.Collection下的接口和繼承類關係簡易結構圖:

java.util.Map下的接口和繼承類關係簡易結構圖:

其中,Java 集合框架中主要封裝的是典型的數據結構和算法,如動態數組、雙向鏈表、隊列、棧、Set、Map 等。

將集合框架挖掘處理,可以分為以下幾個部分
1) 數據結構
List列表、Queue隊列、Deque雙端隊列、Set集合、Map映射
2) 比較器
Comparator比較器、Comparable排序接口
3) 算法
Collections常用算法類、Arrays靜態數組的排序、查找算法
4) 迭代器
Iterator通用迭代器、ListIterator針對 List 特化的迭代器

二、有序列表(List)

List集合的特點就是存取有序,可以存儲重複的元素,可以用下標進行元素的操作

List主要實現類:ArrayList、LinkedList、Vector、Stack。

2.1、ArrayList

ArrayList是一個動態數組結構,支持隨機存取,尾部插入刪除方便,內部插入刪除效率低(因為要移動數組元素);如果內部數組容量不足則自動擴容,因此當數組很大時,效率較低。

2.2、LinkedList

LinkedList是一個雙向鏈表結構,在任意位置插入刪除都很方便,但是不支持隨機取值,每次都只能從一端開始遍歷,直到找到查詢的對象,然後返回;不過,它不像 ArrayList 那樣需要進行內存拷貝,因此相對來說效率較高,但是因為存在額外的前驅和後繼節點指針,因此佔用的內存比 ArrayList 多一些。

2.3、Vector

Vector也是一個動態數組結構,一個元老級別的類,早在jdk1.1就引入進來類,之後在jdk1.2里引進ArrayList,ArrayList大部分的方法和Vector比較相似,兩者是不同的,Vector是允許同步訪問的,Vector中的操作是線程安全的,但是效率低,而ArrayList所有的操作都是異步的,執行效率高,但不安全!

關於Vector,現在用的很少了,因為裏面的getsetadd等方法都加了synchronized,所以,執行效率會比較低,如果需要在多線程中使用,可以採用下面語句創建ArrayList對象

List<Object> list =Collections.synchronizedList(new ArrayList<Object>());

也可以考慮使用複製容器 java.util.concurrent.CopyOnWriteArrayList進行操作,例如:

final CopyOnWriteArrayList<Object> cowList = new CopyOnWriteArrayList<String>(Object);

2.4、Stack

Stack是Vector的一個子類,本質也是一個動態數組結構,不同的是,它的數據結構是先進后出,取名叫棧!

關於Stack,現在用的也很少,因為有個ArrayDeque雙端隊列,可以替代Stack所有的功能,並且執行效率比它高!

三、集(Set)

Set集合的特點:元素不重複,存取無序,無下標;

Set主要實現類:HashSet、LinkedHashSet和TreeSet。

3.1、HashSet

HashSet底層是基於 HashMap 的k實現的,元素不可重複,特性同 HashMap。

3.2、LinkedHashSet

LinkedHashSet底層也是基於 LinkedHashMap 的k實現的,一樣元素不可重複,特性同 LinkedHashMap。

3.3、TreeSet

同樣的,TreeSet也是基於 TreeMap 的k實現的,同樣元素不可重複,特性同 TreeMap;

Set集合的實現,基本都是基於Map中的鍵做文章,使用Map中鍵不能重複、無序的特性;所以,我們只需要重點關注Map的實現即可!

四、隊列(Queue)

Queue是一個隊列集合,隊列通常是指“先進先出”(FIFO)的容器。新元素插入(offer)到隊列的尾部,訪問元素(poll)操作會返回隊列頭部的元素。通常,隊列不允許隨機訪問隊列中的元素。

Queue主要實現類:ArrayDeque、LinkedList、PriorityQueue。

4.1、ArrayDeque

ArrayQueue是一個基於數組實現的雙端隊列,可以想象,在隊列中存在兩個指針,一個指向頭部,一個指向尾部,因此它具有“FIFO隊列”及“棧”的方法特性。

既然是雙端隊列,那麼既可以先進先出,也可以先進后出,以下是測試例子!

先進先出

public static void main(String[] args) {
                ArrayDeque<String> queue = new ArrayDeque<>();
        //入隊
        queue.offer("AAA");
        queue.offer("BBB");
        queue.offer("CCC");
        System.out.println(queue);
        //獲取但不出隊
        System.out.println(queue.peek());
        System.out.println(queue);
        //出隊
        System.out.println(queue.poll());
        System.out.println(queue);
}

輸出結果:

[AAA, BBB, CCC]
AAA
[AAA, BBB, CCC]
AAA
[BBB, CCC]

先進后出

public static void main(String[] args) {
                ArrayDeque<String> stack = new ArrayDeque<>();
        //壓棧,此時AAA在最下,CCC在最外
        stack.push("AAA");
        stack.push("BBB");
        stack.push("CCC");
        System.out.println(stack);
        //獲取最後添加的元素,但不刪除
        System.out.println(stack.peek());
        System.out.println(stack);
        //彈出最後添加的元素
        System.out.println(stack.pop());
        System.out.println(stack);
}

輸出結果:

[CCC, BBB, AAA]
CCC
[CCC, BBB, AAA]
CCC
[BBB, AAA]

4.2、LinkedList

LinkedList是List接口的實現類,也是Deque的實現類,底層是一種雙向鏈表的數據結構,在上面咱們也有所介紹,LinkedList可以根據索引來獲取元素,增加或刪除元素的效率較高,如果查找的話需要遍歷整合集合,效率較低,LinkedList同時實現了stack、Queue、PriorityQueue的所有功能。

例子

public static void main(String[] args) {
                LinkedList<String> ll = new LinkedList<>();
        //入隊
        ll.offer("AAA");
        //壓棧
        ll.push("BBB");
        //雙端的另一端入隊
        ll.addFirst("NNN");
        ll.forEach(str -> System.out.println("遍歷中:" + str));
        //獲取隊頭
        System.out.println(ll.peekFirst());
        //獲取隊尾
        System.out.println(ll.peekLast());
        //彈棧
        System.out.println(ll.pop());
        System.out.println(ll);
        //雙端的後端出列
        System.out.println(ll.pollLast());
        System.out.println(ll);
}

輸出結果:

遍歷中:NNN
遍歷中:BBB
遍歷中:AAA
NNN
AAA
NNN
[BBB, AAA]
AAA
[BBB]

4.3、PriorityQueue

PriorityQueue也是一個隊列的實現類,此實現類中存儲的元素排列並不是按照元素添加的順序進行排列,而是內部會按元素的大小順序進行排列,是一種能夠自動排序的隊列。

例子

public static void main(String[] args) {
        PriorityQueue<Integer> queue1 = new PriorityQueue<>(10);

        System.out.println("處理前的數據");
        Random rand = new Random();
        for (int i = 0; i < 10; i++) {
                Integer num = rand.nextInt(90) + 10;
                System.out.print(num + ", ");
            queue1.offer(num); // 隨機兩位數
        }

        System.out.println("\n處理后的數據");
        for (int i = 0; i < 10; i++) { // 默認是自然排序 [升序]
            System.out.print(queue1.poll() + ", ");
        }
}

輸出結果:

處理前的數據
36, 23, 24, 11, 12, 26, 79, 96, 14, 73, 
處理后的數據
11, 12, 14, 23, 24, 26, 36, 73, 79, 96, 

五、映射表(Map)

Map是一個雙列集合,其中保存的是鍵值對,鍵要求保持唯一性,值可以重複。

Map 主要實現類:HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties。

5.1、HashMap

關於HashMap,相信大家都不陌生,繼承自AbstractMap,key 不可重複,因為使用的是哈希表存儲元素,所以輸入的數據與輸出的數據,順序基本不一致,另外,HashMap最多只允許一條記錄的 key 為 null。

5.2、LinkedHashMap

HashMap 的子類,內部使用鏈表數據結構來記錄插入的順序,使得輸入的記錄順序和輸出的記錄順序是相同的。LinkedHashMap與HashMap最大的不同處在於,LinkedHashMap輸入的記錄和輸出的記錄順序是相同的!

5.3、TreeMap

能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用 Iterator 遍歷時,得到的記錄是排過序的;如需使用排序的映射,建議使用 TreeMap。TreeMap實際使用的比較少!

5.4、IdentityHashMap

繼承自AbstractMap,與HashMap有些不同,在獲取元素的時候,通過==代替equals ()來進行判斷,比較的是內存地址

get方法源碼部分

public V get(Object key) {
        Object k = maskNull(key);
        Object[] tab = table;
        int len = tab.length;
        int i = hash(k, len);
        while (true) {
            Object item = tab[i];
            //用==比較k和元素是否相等
            if (item == k)
                return (V) tab[i + 1];
            if (item == null)
                return null;
            i = nextKeyIndex(i, len);
        }
}

5.5、WeakHashMap

WeakHashMap繼承自AbstractMap,被稱為緩存Map,向WeakHashMap中添加元素,再次通過鍵調用方法獲取元素方法時,不一定獲取到元素值,因為WeakHashMap 中的 Entry 可能隨時被 GC 回收。

5.6、Hashtable

Hashtable,一個元老級的類,鍵值不能為空,與HashMap不同的是,方法都加了synchronized同步鎖,是線程安全的,但是效率上,沒有HashMap快!

同時,HashMap 是 HashTable 的輕量級實現,他們都完成了Map 接口,區別在於 HashMap 允許K和V為空,而HashTable不允許K和V為空,由於非線程安全,效率上可能高於 Hashtable。

如果需要在多線程環境下使用HashMap,可以使用如下的同步器來實現或者使用併發工具包中的ConcurrentHashMap

Map<String, Object> map =Collections.synchronizedMap(new HashMap<>());

5.7、Properties

Properties繼承自HashTable,Properties新增了load()和和store()方法,可以直接導入或者將映射寫入文件,另外,Properties的鍵和值都是String類型。

六、比較器

Comparable和Comparator接口都是用來比較大小的,一般在TreeSet、TreeMap接口中使用的比較多,主要用於解決排序問題。

6.1、Comparable

Comparable:對實現它的每個類的對象進行整體排序

package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}

若一個類實現了Comparable 接口,實現 Comparable 接口的類的對象的 List 列表 ( 或數組)可以通過 Collections.sort(或 Arrays.sort)進行排序。

此外,實現 Comparable 接口的類的對象 可以用作 “有序映射 ( 如 TreeMap)” 中的鍵或 “有序集合 (TreeSet)” 中的元素,而不需要指定比較器。

使用例子:

/**
  * 實體類Person實現Comparable接口
  */
public class Person implements Comparable<Person>{
    private int age;
    private String name;

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Person o){
        return this.age-o.age;
    }
    
    @Override
    public String toString(){
        return name+":"+age;
    }
}

測試

public static void main(String[] args) {
        Person person1 = new Person("張三",18);
        Person person2 = new Person("李四",17);
        Person person3 = new Person("王五",19);

        List<Person> list = new ArrayList<>();
        list.add(person1);
        list.add(person2);
        list.add(person3);

        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
}

輸出:

[張三:18, 李四:17, 王五:19]
[李四:17, 張三:18, 王五:19]
6.2、Comparator

Comparator:也是對實現它的每個類的對象進行排序

package java.util;
import ***;

public interface Comparator<T> {
    int compare(T o1, T o2);
    ......
}

如果我們的這個類Person無法修改或者沒有繼承Comparable接口,我們又要對其進行排序,Comparator就可以派上用場了。

將類Person實現的Comparable接口去掉

/**
  * 實體類Person
  */
public class Person {
    private int age;
    private String name;
    
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString(){
        return name+":"+age;
    }
}

測試類:

public static void main(String[] args) {
        Person person1 = new Person("張三",18);
        Person person2 = new Person("李四",17);
        Person person3 = new Person("王五",19);

        List<Person> list = new ArrayList<>();
        list.add(person1);
        list.add(person2);
        list.add(person3);

        System.out.println(list);
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                if(o1 == null || o2 == null){
                    return 0;
                }
                //o1比o2小,返回負數
                //o1等於o2,等於0
                //o1大於o2,返回正數
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(list);
}

輸出:

[張三:18, 李四:17, 王五:19]
[李四:17, 張三:18, 王五:19]

七、常用工具類

7.1、Collections類

java.util.Collections工具類為集合框架提供了很多有用的方法,這些方法都是靜態的,在編程中可以直接調用。整個Collections工具類源碼差不多有4000行,這裏只針對一些典型的方法進行闡述。

7.1.1、addAll

addAll:向指定的集合c中加入特定的一些元素elements

public static <T> boolean addAll(Collection<? super T> c, T… elements)
7.1.2、binarySearch

binarySearch:利用二分法在指定的集合中查找元素

#集合元素T實現Comparable接口的方式,進行查詢
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)

#元素以外部實現Comparator接口的方式,進行查詢
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
7.1.3、sort
#集合元素T實現Comparable接口的方式,進行排序
public static <T extends Comparable<? super T>> void sort(List<T> list)

#元素以外部實現Comparator接口的方式,進行排序
public static <T> void sort(List<T> list, Comparator<? super T> c)
7.1.4、shuffle

shuffle:混排,隨機打亂原來的順序,它打亂在一個List中可能有的任何排列的蹤跡。

#方法一
public static void shuffle(List<?> list)

#方法二,指定隨機數訪問
public static void shuffle(List<?> list, Random rnd)
7.1.5、reverse

reverse:集合排列反轉

#直接反轉集合的元素
public static void reverse(List<?> list)

#返回可以使集合反轉的比較器Comparator
public static <T> Comparator<T> reverseOrder()

#集合的反轉的反轉,如果cmp不為null,返回cmp的反轉的比較器,如果cmp為null,效果等同於第二個方法.
public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)
7.1.6、synchronized系列

synchronized系列:確保所封裝的集合線程安全(強同步)

#同步Collection接口下的實現類
public static <T> Collection<T> synchronizedCollection(Collection<T> c)

#同步SortedSet接口下的實現類
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)

#同步List接口下的實現類
public static <T> List<T> synchronizedList(List<T> list)

#同步Map接口下的實現類
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)

#同步SortedMap接口下的實現類
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

7.2、Arrays類

java.util.Arrays工具類也為集合框架提供了很多有用的方法,這些方法都是靜態的,在編程中可以直接調用。整個Arrays工具類源碼有3000多行,這裏只針對一些典型的方法進行闡述。

7.2.1、asList

asList:將一個數組轉變成一個List,準確來說是ArrayList

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
}

注意:這個List是定長的,企圖添加或者刪除數據都會報錯java.lang.UnsupportedOperationException

7.2.2、sort

sort:對數組進行排序,適合byte,char,double,float,int,long,short等基本類型,還有Object類型

#基本數據類型,例子int類型數組
public static void sort(int[] a)

#Object類型數組
#如果使用Comparable進行排序,Object需要實現Comparable
#如果使用Comparator進行排序,可以使用外部比較方法實現
public static void sort(Object[] a)
7.2.3、binarySearch

binarySearch:通過二分查找法對已排序的數組進行查找。如果數組沒有經過Arrays.sort排序,那麼檢索結果未知。

適合byte,char,double,float,int,long,short等基本類型,還有Object類型和泛型。

#基本數據類型,例子int類型數組,key為要查詢的參數
public static int binarySearch(int[] a, int key)

#Object類型數組,key為要查詢的參數
#如果使用Comparable進行排序,Object需要實現Comparable
#如果使用Comparator進行排序,可以使用外部比較方法實現
public static int binarySearch(Object[] a, Object key)
7.2.4、copyOf

copyOf:數組拷貝,底層採用System.arrayCopy(native方法)實現。

適合byte,char,double,float,int,long,short等基本類型,還有泛型數組。

#基本數據類型,例子int類型數組,newLength新數組長度
public static int[] copyOf(int[] original, int newLength)

#T為泛型數組,newLength新數組長度
public static <T> T[] copyOf(T[] original, int newLength)
7.2.5、copyOfRange

copyOfRange:數組拷貝,指定一定的範圍,底層採用System.arrayCopy(native方法)實現。

適合byte,char,double,float,int,long,short等基本類型,還有泛型數組。

#基本數據類型,例子int類型數組,from:開始位置,to:結束位置
public static int[] copyOfRange(int[] original, int from, int to)

#T為泛型數組,from:開始位置,to:結束位置
public static <T> T[] copyOfRange(T[] original, int from, int to)
7.2.6、equals和deepEquals

equals:判斷兩個數組的每一個對應的元素是否相等(equals, 對於兩個數組的元素a和a2有a==null ? a2==null : a.equals(a2))

#基本數據類型,例子int類型數組,a為原數組,a2為目標數組
public static boolean equals(int[] a, int[] a2)

#Object數組,a為原數組,a2為目標數組
public static boolean equals(Object[] a, Object[] a2)

deepEquals:主要針對一個數組中的元素還是數組的情況(多維數組比較)

#Object數組,a1為原數組,a2為目標數組
public static boolean deepEquals(Object[] a1, Object[] a2)
7.2.7、toString和deepToString

toString:將數組轉換成字符串,中間用逗號隔開

#基本數據類型,例子int類型數組,a為數組
public static String toString(int[] a)

#Object數組,a為數組
public static String toString(Object[] a)

deepToString:當數組中又包含數組,就不能單純的利用Arrays.toString()了,使用此方法將數組轉換成字符串

#Object數組,a為數組
public static String deepToString(Object[] a)

八、迭代器

JCF的迭代器(Iterator)為我們提供了遍歷容器中元素的方法。只有容器本身清楚容器里元素的組織方式,因此迭代器只能通過容器本身得到。每個容器都會通過內部類的形式實現自己的迭代器。

ArrayList<String> list = new ArrayList<String>();
list.add(new String("a1"));
list.add(new String("a2"));
list.add(new String("a3"));
Iterator<String> it = list.iterator();//得到迭代器
while(it.hasNext()){
    String obj = it.next();//訪問元素
    System.out.println(obj);
}

JDK 1.5 引入了增強的for循環,簡化了迭代容器時的寫法

//使用增強for迭代
ArrayList<String> list = new ArrayList<String>();
list.add(new String("a1"));
list.add(new String("a2"));
list.add(new String("a3"));
for(String obj : list){
    //enhanced for statement
    System.out.println(obj);
}

九、總結

以上,主要是對java集合的整體架構進行簡單的介紹,如果有理解不當之處,歡迎指正。

十、參考

1、JDK1.7&JDK1.8 源碼
2、
3、

作者:炸雞可樂
原文出處:

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

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

剖析nsq消息隊列(三) 消息傳輸的可靠性和持久化[二]diskqueue

,大概提了一下消息的持久化,--mem-queue-size 設置為 0,所有的消息將會存儲到磁盤。
總有人說nsq的持久化問題,消除疑慮的方法就是閱讀原碼做benchmark測試,個人感覺nsq還是很靠譜的。
nsq自己實現了一個先進先出的消息文件隊列是把消息保存到本地文件內,很值得分析一下他的實現過程。

整體處理邏輯

go-diskqueue 會啟動一個gorouting進行讀寫數據也就是方法ioLoop
會根據你設置的參數來進行數據的讀寫,流程圖如下

這個圖畫的也不是特別的準確 ioLoop 用的是 select 並不是if else 當有多個條件為true時,會隨機選一個進行執行

nsq 生成的數據大致如下:

xxxx.diskqueue.meta.dat 元數據保存了未讀消息的長度,讀取和存入數據的編號和讀取位置
xxxx.diskqueue.編號.dat 消息保存的文件,每一個消息的存儲:4Byte消息的長度+消息

參數說明

一些主要的參數和約束說明
這些參數的使用在後面的處理邏輯中會提到

// diskQueue implements a filesystem backed FIFO queue
type diskQueue struct {
    // run-time state (also persisted to disk)
    // 讀取數據的位置    
    readPos      int64
    // 寫入數據的位置
    writePos     int64
    // 讀取文件的編號    
    readFileNum  int64
    // 寫入文件的編號
    writeFileNum int64
    // 未處理的消息總數    
    depth        int64

    // instantiation time metadata
    // 每個文件的大小限制    
    maxBytesPerFile int64 // currently this cannot change once created
    // 每條消息的最小大小限制    
    minMsgSize      int32
    // 每條消息的最大大小限制    
    maxMsgSize      int32
    // 緩存消息有多少條後進行寫入    
    syncEvery       int64         // number of writes per fsync
    // 自動寫入消息文件的時間間隔    
    syncTimeout     time.Duration // duration of time per fsync
    exitFlag        int32
    needSync        bool

    // keeps track of the position where we have read
    // (but not yet sent over readChan)
    // 下一條消息的位置    
    nextReadPos     int64
    // 下一條消息的文件編號    
    nextReadFileNum int64

    // 讀取的文件
    readFile  *os.File
    // 寫入的文件    
    writeFile *os.File
    // 讀取的buffer    
    reader    *bufio.Reader
    // 寫入的buffer    
    writeBuf  bytes.Buffer

    // exposed via ReadChan()
    // 讀取數據的channel    
    readChan chan []byte

    //.....
}

數據

元數據

讀寫數據信息的元數據保存在xxxxx.diskqueue.meta.data文件內主要用到代碼里的字段如下
未處理的消息總數 depth
讀取文件的編號 readFileNum 讀取數據的位置 readPos
寫入文件的編號 writeFileNum 寫入數據的位置 writePos
真實數據如下

15
0,22
3,24

保存元數據信息

func (d *diskQueue) persistMetaData() error {
    // ...
    fileName := d.metaDataFileName()
    tmpFileName := fmt.Sprintf("%s.%d.tmp", fileName, rand.Int())
    // write to tmp file
    f, err = os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE, 0600)
    // 元數據信息
    _, err = fmt.Fprintf(f, "%d\n%d,%d\n%d,%d\n",
        atomic.LoadInt64(&d.depth),
        d.readFileNum, d.readPos,
        d.writeFileNum, d.writePos)
    // 保存
    f.Sync()
    f.Close()
    // atomically rename
    return os.Rename(tmpFileName, fileName)
}

得到元數據信息

func (d *diskQueue) retrieveMetaData() error {
    // ...
    fileName := d.metaDataFileName()
    f, err = os.OpenFile(fileName, os.O_RDONLY, 0600)
    // 讀取數據並賦值
    var depth int64
    _, err = fmt.Fscanf(f, "%d\n%d,%d\n%d,%d\n",
        &depth,
        &d.readFileNum, &d.readPos,
        &d.writeFileNum, &d.writePos)
    //...
    atomic.StoreInt64(&d.depth, depth)
    d.nextReadFileNum = d.readFileNum
    d.nextReadPos = d.readPos
    return nil
}

消息數據

寫入一條數據

ioLoop 中發現有數據寫入時,會調用writeOne方法,把消息保存到文件內

        select {
        // ...
        case dataWrite := <-d.writeChan:
            count++
            d.writeResponseChan <- d.writeOne(dataWrite)
        // ...
func (d *diskQueue) writeOne(data []byte) error {
    var err error

    if d.writeFile == nil {
        curFileName := d.fileName(d.writeFileNum)
        d.writeFile, err = os.OpenFile(curFileName, os.O_RDWR|os.O_CREATE, 0600)
        // ...
        if d.writePos > 0 {
            _, err = d.writeFile.Seek(d.writePos, 0)
            // ...
        }
    }

    dataLen := int32(len(data))
    // 判斷消息的長度是否合法
    if dataLen < d.minMsgSize || dataLen > d.maxMsgSize {
        return fmt.Errorf("invalid message write size (%d) maxMsgSize=%d", dataLen, d.maxMsgSize)
    }
    d.writeBuf.Reset()
    // 寫入4字節的消息長度,以大端序保存
    err = binary.Write(&d.writeBuf, binary.BigEndian, dataLen)
    if err != nil {
        return err
    }
    // 寫入消息
    _, err = d.writeBuf.Write(data)
    if err != nil {
        return err
    }

    // 寫入到文件
    _, err = d.writeFile.Write(d.writeBuf.Bytes())
    // ...
    // 計算寫入位置,消息數量加1
    totalBytes := int64(4 + dataLen)
    d.writePos += totalBytes
    atomic.AddInt64(&d.depth, 1)
    // 如果寫入位置大於 單個文件的最大限制, 則持久化文件到硬盤
    if d.writePos > d.maxBytesPerFile {
        d.writeFileNum++
        d.writePos = 0

        // sync every time we start writing to a new file
        err = d.sync()
        // ...
    }
    return err
}

寫入完消息后,會判斷當前的文件大小是否已經已於maxBytesPerFile如果大,就持久化文件到硬盤,然後重新打開一個新編號文件,進行寫入。

什麼時候持久化文件到硬盤

調用sync()方法會持久化文件到硬盤,然後重新打開一個新編號文件,進行寫入。
有幾個地方調用會調用這個方法:

  • 一個寫入文件的條數達到了syncEvery的值時,也就是初始化時設置的最大的條數。會調用sync()
  • syncTimeout 初始化時設置的同步時間間隔,如果這個時間間隔到了,並且寫入的文件條數>0的時候,會調用sync()
  • 還有就是上面說過的writeOne方法,寫入完消息后,會判斷當前的文件大小是否已經已於maxBytesPerFile如果大,會調用sync()
  • 當讀取文件時,把整個文件讀取完時,會刪除這個文件並且會把needSync 設置為trueioLoop 會調用sync()
  • 還有就是Close的時候,會調用sync()
func (d *diskQueue) sync() error {
    if d.writeFile != nil {
        // 把數據 flash到硬盤,關閉文件並設置為 nil
        err := d.writeFile.Sync()
        if err != nil {
            d.writeFile.Close()
            d.writeFile = nil
            return err
        }
    }
    // 保存元數據信息
    err := d.persistMetaData()
    // ...
    d.needSync = false
    return nil
}

讀取一條數據

元數據保存着 讀取文件的編號 readFileNum 和讀取數據的位置 readPos
並且diskQueue暴露出了一個方法來,通過channel來讀取數據

func (d *diskQueue) ReadChan() chan []byte {
    return d.readChan
}

ioLoop里,當發現讀取位置小於寫入位置 或者讀文件編號小於寫文件編號,並且下一個讀取位置等於當前位置時才會讀取一條數據,然後放在一個外部全局變量 dataRead 里,並把 讀取的channel 賦值監聽 r = d.readChan,當外部有人讀取了消息,則進行moveForward操作

func (d *diskQueue) ioLoop() {
    var dataRead []byte
    var err error
    var count int64
    var r chan []byte
    for {
        // ...
        if (d.readFileNum < d.writeFileNum) || (d.readPos < d.writePos) {
            if d.nextReadPos == d.readPos {
                dataRead, err = d.readOne()
                if err != nil {
                    d.handleReadError()
                    continue
                }
            }
            r = d.readChan
        } else {
            r = nil
        }

        select {
        // ...
        case r <- dataRead:
            count++
            // moveForward sets needSync flag if a file is removed
            d.moveForward()
        // ...
        }
    }

// ...
}

readOne 從文件里讀取一條消息,4個bit的大小,然後讀取具體的消息。如果讀取位置大於最大文件限制,則close。在moveForward里會進行刪除操作

func (d *diskQueue) readOne() ([]byte, error) {
    var err error
    var msgSize int32
    // 如果readFile是nil,打開一個新的
    if d.readFile == nil {
        curFileName := d.fileName(d.readFileNum)
        d.readFile, err = os.OpenFile(curFileName, os.O_RDONLY, 0600)
        // ...
        d.reader = bufio.NewReader(d.readFile)
    }
    err = binary.Read(d.reader, binary.BigEndian, &msgSize)
    // ...
    readBuf := make([]byte, msgSize)
    _, err = io.ReadFull(d.reader, readBuf)
    totalBytes := int64(4 + msgSize)
    // ...
    d.nextReadPos = d.readPos + totalBytes
    d.nextReadFileNum = d.readFileNum
    // 如果讀取位置大於最大文件限制,則close。在moveForward里會進行刪除操作
    if d.nextReadPos > d.maxBytesPerFile {
        if d.readFile != nil {
            d.readFile.Close()
            d.readFile = nil
        }
        d.nextReadFileNum++
        d.nextReadPos = 0
    }
    return readBuf, nil
}

moveForward方法會查看讀取的編號,如果發現下一個編號 和當前的編號不同時,則刪除舊的文件。

func (d *diskQueue) moveForward() {
    oldReadFileNum := d.readFileNum
    d.readFileNum = d.nextReadFileNum
    d.readPos = d.nextReadPos
    depth := atomic.AddInt64(&d.depth, -1)

    // see if we need to clean up the old file
    if oldReadFileNum != d.nextReadFileNum {
        // sync every time we start reading from a new file
        d.needSync = true

        fn := d.fileName(oldReadFileNum)
        err := os.Remove(fn)
        // ...
    }
    d.checkTailCorruption(depth)

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

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

JS中的相等性判斷===, ==, Object.is()

相信剛接觸JS的人都會被他的想等性判斷給整糊塗,看看下面代碼,你能答對幾個?

NaN === NaN // false
NaN == NaN // false
Object.is(NaN, NaN) // true
0 == false  // true
1 == true   // true
Number(null) === 0  // true
null == 0   // false

Javascript提供了三種不同的值比較操作,分別是嚴格相等、寬鬆相等、以及Object.is,今天查完資料后做了一下總結,希望下面的內容能夠對大家有所幫助,如果哪些地方有誤的話,也歡迎大家指正。

1. 嚴格相等 x === y判斷邏輯

  1. 如果x的數據類型和y的數據類型不相同,返回false;
  2. 如果x是Number類型
    • x是NaN,返回false
    • y是NaN,返回false
    • x的值和y的值相等,返回true
    • x是+0,y是-0,返回true
    • x是-0,y是+0,返回true
    • 否則返回false
  3. 其他類型參照SameValueNonNumber(x, y)
    • 斷言:x,y不是Number類型;
    • 斷言: x,y的數據類型相同;
    • x是undefined, y是undefined return true;
    • x是null, y是null,return true;
    • x是字符串類型,當且僅當x,y字符序列完全相同時(長度相同,每個位置上的字符也相同)返回true, 否則返回false;
    • 如果x是布爾類型,當x,y都為true或者都為false時返回true,否則返回false;
    • 如果x是symbol類型,當x,y是相同的symbol值,返回true,否則返回false;
    • 如果x,y是同一個對象值,返回true,否則返回false;
NaN === NaN // false
undefined === undefined // true
null === null   // true
undefined === null  // false

2. 寬鬆相等 x == y

  1. 如果x,y的類型相同,返回x===y的結果;
  2. 如果x是null, y是undefined, 返回true;
  3. 如果x是undefined, y是null, 返回true;
  4. 如果x是數值,y是字符串, 返回x == ToNumber(y);
  5. 如果x是字符串,y是數值, 返回ToNumber(x) == y;
  6. 如果x是布爾類型, 返回ToNumber(x)==y 的結果;
  7. 如果y是布爾類型,返回 x==ToNumber(y) 的結果;
  8. 如果x是String或Number或Symbol中的一種並且Type(y)是Object,返回 x==ToPrimitive(y) 的結果
  9. 如果Type(x)是Object並且Type(y)是String或Number或Symbol中的一種,返回 ToPrimitive(x)==y 的結果
  10. 其他返回false
12 == '0xc' // true, 0xc是16進制
12 == '12'  // true
12 == '12c' // false, 說明ToNumber轉換是用的Number()方法

注意

Number(null) === 0
但是
null == 0 // false, 

2.1 ToNumber將一個值轉換為數值類型

詳情參考

  1. 如果是boolean類型, true返回1,false返回0;
  2. 如果是數值,只是簡單的傳入返回;
  3. 如果是null,返回0
  4. 如果是undefined, 返回NaN;
  5. 如果是字符串,字符串如果只包含数字,則將其轉換成十進制數;如果是有效的浮點格式,將其轉換成對應的浮點數值;如果是二進制或十六進制將其轉換成對應的十進制數值;
  6. 如果是對象,調用對象的valueOf()方法,然後依照前面規則轉換,如果valueOf返回值是NaN,則調用toString()方法,再依照前面的規則轉換返回的字符串

2.2 ToPrimitive

toPrimitive(A)通過嘗試調用 A 的A.toString() 和 A.valueOf() 方法,將參數 A 轉換為原始值(Primitive);
JS中原始類型有:Number、String、Boolean、Null、Undefined;

不同類型對象的valueOf()方法的返回值:

對象 返回值
Array 返回數組對象本身。
Boolean 布爾值
Date 存儲的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數 UTC
Function 函數本身
Number 数字值
Object 對象本身。這是默認情況, 可以覆蓋自定義對象的valueOf方法
String 字符串值
// Array:返回數組對象本身
var array = ["ABC", true, 12, -5];
console.log(array.valueOf() === array);   // true

// Date:當前時間距1970年1月1日午夜的毫秒數
var date = new Date(2013, 7, 18, 23, 11, 59, 230);
console.log(date.valueOf());   // 1376838719230

// Number:返回数字值
var num =  15.26540;
console.log(num.valueOf());   // 15.2654

// 布爾:返回布爾值true或false
var bool = true;
console.log(bool.valueOf() === bool);   // true

// new一個Boolean對象
var newBool = new Boolean(true);
// valueOf()返回的是true,兩者的值相等
console.log(newBool.valueOf() == newBool);   // true
// 但是不全等,兩者類型不相等,前者是boolean類型,後者是object類型
console.log(newBool.valueOf() === newBool);   // false

// Function:返回函數本身
function foo(){}
console.log( foo.valueOf() === foo );   // true
var foo2 =  new Function("x", "y", "return x + y;");
console.log( foo2.valueOf() );
/*
ƒ anonymous(x,y
) {
return x + y;
}
*/

// Object:返回對象本身
var obj = {name: "張三", age: 18};
console.log( obj.valueOf() === obj );   // true

// String:返回字符串值
var str = "http://www.xyz.com";
console.log( str.valueOf() === str );   // true

// new一個字符串對象
var str2 = new String("http://www.xyz.com");
// 兩者的值相等,但不全等,因為類型不同,前者為string類型,後者為object類型
console.log( str2.valueOf() === str2 );   // false

3.同值相等

同值相等由 Object.is 方法判斷:

  • 兩個值都是 undefined
  • 兩個值都是 null
  • 兩個值都是 true 或者都是 false
  • 兩個值是由相同個數的字符按照相同的順序組成的字符串
  • 兩個值指向同一個對象
  • 兩個值都是数字並且
    • 都是正零 +0,
    • 或者都是負零 -0,
    • 或者都是 NaN
    • 都是除零和 NaN 外的其它同一個数字
Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

Object.is(true, 'true')     // false

// 特例
Object.is(0, -0);            // false
Object.is(0, +0);            // true
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true

4.零值相等

與同值相等類似,不過會認為 +0 與 -0 相等。

小結

  • === 不做類型轉換,當兩邊的數類型不相同時,直接返回false;當前類型相同且都是數值類型的時候,有一個是NaN,那麼結果就是false, 另外 +0 === -0
  • ==運算符,當兩邊操作數類不相同時會做隱式轉換,然後才進行比較,這樣的話就會出現 false == 0, ” == false 等現象, 但是Object.is不會做這種轉換

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

實現 sqrt(x):二分查找法和牛頓法

最近忙裡偷閑,每天刷一道 LeetCode 的簡單題保持手感,發現簡單題雖然很容易 AC,但若去了解其所有的解法,也可學習到不少新的知識點,擴展知識的廣度。

創作本文的思路來源於:

簡述題目大意(不想跳轉鏈接,可以看這裏):給定一個非負整數 x,要求計算並返回 x 的平方根(取整)。例如,輸入 4,則輸出 2;輸入 8,則輸出 2(8 的平方根是 2.82842……,由於返回類型是整數,因此小數部分被捨去)。即給定一個 \(x\),我們要計算出 \(\lfloor \sqrt{x} \rfloor\)

最簡單最直覺的方法自然是從 0 開始遍歷,直到找到第一個其平方值大於 \(x\) 的數 \(n\),則 \(n-1\) 即是答案。對於任意的 \(x\),其取整后平方根一定在 \([0, x]\) 區間上,代碼如下:

int sqrt(int x)
{
    if (x == 0)
        return x;
    int ans = 1;
    while (ans <= x / ans)
        ans++;
    return ans - 1;
}

這裏需要注意的有兩點:

  1. 第 6 行代碼中,while 的判斷條件可以避免溢出。很大概率上,你可能會寫成 while (ans * ans <= x),這更自然、更直觀,但當 ans 的值很大時,ans * ans 的結果可能會超過 int 類型的最大表示範圍。舉個例子,比如我們要計算 \(x\) 的取整平方根(其值為 \(n\),即 \(\lfloor \sqrt{x} \rfloor = n\)),算法會將 ans 遍歷到第一個平方超過 \(x\) 的值,即 \(n+1\) 后停止。如果 \(x\) 的值就是 int 類型能夠表示的最大值,那麼當 ans 遍歷到 \(n+1\) 時,計算 ans * ans 的結果就超出了 int 類型的表示範圍。
  2. 由於在 while 的循環判斷中,我們用除法代替了乘法,因此 ans 便不能再從 0 開始遍歷(否則會導致除零錯誤)。為此,我們可以在算法開始單獨處理 \(x = 0\) 的情況,然後讓 ans 從 1 開始遍歷。

作為一道簡單題,這種暴力樸素的算法自然是可以 AC 的。但其效率極低(需要遍歷 \(O(\sqrt{n})\) 次),在 LeetCode 上的時間效率只能快過約 5% 的用戶,使用 C++ 語言的運行時間平均要 90ms 以上。因此,本文提供了兩種更加高效的算法:二分查找法和牛頓法。

1. 二分查找法

如果你在暴力求解的基礎上繼續思考,很大概率會想到用二分搜索求解。

沒錯,思考暴力求解的策略,我們在區間 \([0, x]\) 上搜索解,而搜索區間 \([0, x]\) 天然是有序的,自然可以用二分搜索代替線性搜索,以大大提高搜索效率。

更進一步的,我們還可以縮小我們的搜索區間。直覺告訴我們,對於一個非負整數 \(x\),其 \(\sqrt{x}\) 應該不會大於 \(x / 2\)(例如,\(\sqrt{25} = 5\),小於 \(25 / 2 = 12.5\))。我們可以證明:

\[ \begin{aligned} &\text{設 } y = \frac{x}{2} – \sqrt{x},\text{ 則 } y^\prime = \frac{1}{2} – \frac{1}{2\sqrt{x}}, \\[2ex] &\text{令 } y^\prime = 0, \text{ 可得駐點 } x = 1, \\[2ex] &\text{當 } x > 1 \text{ 時}, y^\prime > 0, \text{ 即當 } x > 1 \text{ 時 }, y = \frac{x}{2} – \sqrt{x} \text{ 的值單調遞增}, \\[2ex] &\text{可推出, 當 } x > 1 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor \text{ 的值單調遞增}, \\[2ex] &\text{又因為當 } x = 2 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor = 0, \\[2ex] &\text{所以當 } x \geq 2 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor \geq 0, \text{ 即 } x \geq 2 \text{ 時},\lfloor \frac{x}{2} \rfloor \geq \lfloor \sqrt{x} \rfloor &\text{(證畢)} \end{aligned} \]

通過證明我們可以得知,當所求的 \(x\) 大於等於 \(2\) 時,sqrt(x) 的搜索空間為 \([1, x / 2]\),對於 \(x < 2\) 的情況,我們只要特殊處理即可(這裏我們也可以得到結論:當 \(x \geq 1\) 時,\(\lfloor \frac{x}{2} \rfloor + 1 \geq \lfloor \sqrt{x} \rfloor\),然後單獨處理 \(x < 1\) 的情況)。代碼:

int sqrt(int x)
{
    if (x < 2)  // 處理特殊情況
        return x;
    
    int left = 1, right = x / 2;
    while (left <= right) {
        # 避免溢出,相當於 mid = (left + right) / 2
        int mid = left + ((right - left) >> 1);
        if (mid == x / mid)
            return mid;
        else if (mid > x / mid)
            right = mid - 1;
        else
            left = mid + 1;
    }
    return right;
}

在這裏解釋一下最後的返回值為什麼是 right。對於二分查找,其搜索空間會不斷收縮到 left == right(關於二分查找,這裏不多贅述,自行手動模擬即可),此時 midleftright 三者的值相等(mid = (left + right) / 2)。根據二分查找的搜索範圍的收縮條件可知,left(或 mid)左側的值都小於等於 \(\lfloor \sqrt{x} \rfloor\)right(或 mid)右側的值都大於 \(\lfloor \sqrt{x} \rfloor\),此時(while 的最後一次循環),判斷 mid 的平方與 x 的大小,有三種情況:

  1. mid == x / mid。則在循環內直接返回 mid 的值。
  2. mid > x / mid。這種情況下,因為 mid 左側的值都小於等於 \(\lfloor \sqrt{x} \rfloor\),而 mid 的值大於 \(x\),則 mid-1 即是答案。而按照分支條件,執行 right = mid - 1,可知 right 的值正是應返回的值。此時,循環結束,應返回 right
  3. mid <= x / mid。這種情況下,midleftright 正是計算答案(右邊的值都大於 \(\lfloor \sqrt{x} \rfloor\))。按照分支條件,執行 left = mid + 1,循環結束。此時,midright 的值為計算結果。

綜合上面三點可知,如果 while 循環結束,則 right 保存的值一定是計算結果。

和之前的暴力法相比,使用二分查找的思想求解 sqrt(x),只需要循環遍歷 \(O(\lg{\frac{x}{2}})\) 次;空間複雜度為 \(O(1)\)

2. 牛頓—拉弗森迭代法

牛頓—拉弗森迭代法(簡稱牛頓法)使用以直代曲的思想,是一種求解函數的方法,不僅僅適用於求解開方計算。當然使用牛頓法求解函數也存在很多坑,但對於求解開方而言,牛頓法是安全的。關於這一方法,你需要掌握一定的高等數學知識,想了解詳細的內容,可以參考鏈接:

簡單的理解,可以參考圖片:

圖片來源:

給定任意一個非負整數 \(n\),我們想要找到一個 \(x = \lfloor \sqrt{n} \rfloor\),這相當於我們要計算函數 \(f(x) = x^2 – n\) 的根。我們首先需要先給出一個猜測值 \(x_0\),不妨令 \(x_0 = \frac{x}{2} + 1\)(證明見第一小節),然後在 \(f(x_0)\) 處作函數的切線,切線與 \(x\) 軸的交點,即為一次迭代后的值 \(x_1\)。若 \(x_1\) 不是要得到的結果,則繼續迭代,在 \(f(x_1)\) 處作函數的切線,切線與 \(x\) 軸的交點,即為第二次迭代后的值 \(x_2\)。以此類推,直到得到 \(x_n = \lfloor \sqrt{n} \rfloor\)

現在我們來推導迭代式。對於 \(x_i\),其函數值為 \(f(x_i)\),則對於點 \((x_i, f(x_i))\),可得其切線方程:

\[ \begin{align} &y – f(x_i) = f(x_i)^\prime(x – x_i) \\[2ex] \implies &y – (x_i^2 – n) = 2x_i(x – x_i) \\[2ex] \implies &y + x_i^2 + n = 2x_ix \end{align} \]

又因為 \(x_{i+1}\) 為切線與 \(x\) 軸的交點,所以令 \(y=0\),可得:

\[ x_{i+1} = (x_i + n / x_i) / 2 \]

現在,我們就可以根據迭代式編寫代碼了:

int sqrt(int x)
{
    // 避免除零錯誤,單獨處理 x = 0 的情況
    if (x == 0)
        return x;
    int t = x / 2 + 1;
    while (t > x / t)
        t = (t + x / t) / 2;
    return t;
}

為了確保算法是正確的,我們還需要一些額外的證明。首先,證明迭代式是單調遞減的:

\[ x_{i+1} – x_i = \left\lfloor \frac{1}{2} (x_i + \frac{n}{x_i}) \right\rfloor – x_i = \left\lfloor \frac{1}{2} (\frac{n}{x_i} – x_i) \right\rfloor \]

可知,在區間 \([\sqrt{x}, +\infty)\) 上,\(x_{i+1} – x_i < 0\)

然後,我們還要證明迭代式是可以收斂到 \(\lfloor \sqrt{n} \rfloor\) 的:

\[ x_{i+1} = \left\lfloor \frac{1}{2} \left( x_i + \left\lfloor \frac{n}{x_i} \right\rfloor \right) \right\rfloor = \left \lfloor \frac{1}{2} (x_i + \frac{n}{x_i}) \right \rfloor \geq \left \lfloor \frac{1}{2} \times 2 \times \sqrt{x_i \cdot \frac{n}{x_i}} \right \rfloor = \lfloor \sqrt{n} \rfloor \]

因此,當 while 循環結束時,我們可以得到正確的答案。

關於牛頓法求 sqrt(x) 的時間複雜度,筆者目前也沒有搞清楚,有了解的童鞋歡迎交流~。不過通過查詢資料,以及實際測試,可知牛頓法的時間效率優於二分搜索法。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!