從軟件開發到 AI 領域工程師:模型訓練篇_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

前言

4 月熱播的韓劇《王國》,不知道大家有沒有看?我一集不落地看完了。王子元子出生時,正逢宮內殭屍作亂,元子也被咬了一口,但是由於大腦神經元尚未形成,寄生蟲無法控制神經元,所以醫女在做了簡單處理后,判斷不會影響大腦。這裏提到了人腦神經元,它也是 AI 神經網絡的研究起源,具體展開講講。

人腦中總共有 860 億個神經元,其中大腦皮層有 160 億個神經元。大腦皮層的神經元數量決定了動物的智力水平,人的大腦皮層中神經元數量遠高於其他物種,所以人類比其他物種更聰明。大象的腦子總共有 2570 億個神經元,但是其中 98% 的神經元都存在於大象的小腦中,而大象的大腦皮層只有 56 億個神經元,無法與人類相比。大腦皮層中的神經元數量越大,能耗也越大。人腦每天消耗的能量占人體全部耗能的 25%,這也就是為什麼我們每天都要吃多餐,很容易餓的原因。人之所以能夠很快超越其他物種,主要是因為人類掌握了烹飪技術,能夠在短時間內攝入大量卡路里以支持大腦運轉,其他物種則將攝入的卡路里用於維護身體運轉,不得不犧牲大腦皮層的神經元數量。

之所以先談大腦神經元原理,也是為了引出本文的重點–現代 AI 技術。在正式進入 AI 技術前,我先講講軟件工程師這份工作,因為現在有很多軟件工程師準備轉入 AI 行業。

軟件工程師

我是軟件工程師出身,2004 年剛畢業時我寫的是 JSP 代碼(一種將 Java 語言嵌入在 HTML 代碼中的編寫方式),工作幾年後轉入分佈式軟件技術,再後來進入大數據技術領域,最近的 4 年時間我一直在從事 AI 平台研發工作。

軟件工程師的要求,我總體分為基礎編碼和系統架構兩方面,因此我對於軟件工程師的考察,特別是校招學生時,為了進一步考察他們的綜合能力,我每次都會自己準備面試題,這些題目包括了編程基本概念、算法編程題、操作系統、數據庫編程、開源代碼閱讀、垃圾回收機制、系統架構描述等。

編碼的話題展開來可以講很久,發展歷史很悠久,我 15 歲學編程時用的是 Basic 語言,讀大學時學的是 C 語言,大學畢業參加工作后第一門用的語言是 Java,其中的各種故事和理解可以寫幾篇文章,這裏不展開談。

我覺得談到軟件工程師工作,避不開軟件架構設計。大眾談軟件架構,很多人會認為軟件架構就是一堆框架的組合,其實不對,軟件架構本身是對於軟件實體的組織形式的闡述,使用框架的意義是快速完成軟件架構設計,而不是取代軟件架構設計,兩者本質上不是一類事物,更像是設計圖紙和所使用的原材料。軟件架構就是通過對軟件生命周期的拆分,在符合業務架構的前提下,以達到軟件本身訪問增長目的的方式。這個增長需要軟件開發的增長,也需要軟件運行的增長,由此達到所支撐業務的增長。

市面上也確實有很多例如“分佈式系統架構”、“微服務架構”等等跟隨着潮流的書籍,但是看完后只停留在會採用一些開源框架進行整體框架搭建,我說的是搭建,而不是設計。確實是搭建,你所擁有的能力就好像小孩子搭積木,只會採用固定討論,或者學得差點,連固定套路都沒學會,這樣對你的個人能力發展其實沒有多大好處,這也是為什麼很多程序員在完成了程序員 – 架構師的轉型后,沒過多久就轉為純管理,或者徹底離開了技術界,因為從來沒有大徹大悟理解系統架構。

之所以談了這麼多系統架構相關的工作理解,是因為我認為系統架構師系統化的思維,我們搞 AI 系統也是系統化的思維,從有較強編程能力的系統架構師轉 AI 技術,相對容易一些。

AI 工程師

為什麼要從軟件工程師轉行到 AI 產品研發?因為 AI 產品研發有更大的吸引力,因為它更難,難到我們並不確定什麼時候才能真正做出來,做出來真正能夠可複製的 AI 產品。表面上看它也是一個門檻—一個“可用”且“可複製”的 AI 技術,但因為難度足夠大,所以有挑戰性,必須不斷地改善技術,做全球範圍內還沒有做出來的技術。搞軟件開發時處理的一些問題可能是其他公司已經解決的,並非“人類”都還沒有解決的問題。

AI 的研究最早可以被追溯到亞里士多德的三段論,然後萊布尼茨創立了處理邏輯,布爾在布爾代數上的貢獻,弗雷德在近代邏輯上的貢獻,羅素在邏輯主義方面的貢獻,這些工作都是在數據邏輯上的。一般認為,現代 AI 技術討論,起源於 1956 年在達特茅斯學院召開的夏季研討會,而這門學科的源頭可能是 Alan Turing(阿蘭. 圖靈) 1948 年在英國國家物理實驗室(NPL)寫過的一份內部報告,這份報告中提到了肉體智能和無肉體智能,從某種意義上預示了後來符號派和統計派之爭,或是 Turing 在 1950 年在哲學雜誌《心》(Mind)上發表的文章“計算機與智能”,反正都是 Turing。

可以這麼認為,現代 AI 是一系列通用目的技術的總稱。現代 AI 技術,主要指基於機器學習(Machine Learning,簡稱 ML)/ 深度學習(Deep Learning,簡稱 DL)的一系列方法和應用,這隻是 AI 領域的一個分支,也是目前發展最快、應用最廣的分支。

機器學習 / 深度學習的原理可以這樣理解:建立一個模型,給一個輸入,通過模型的運算,得到一個輸出。可以用於解決一個簡單問題,例如識別圖片是不是狗,也可以用來解決複雜問題,例如下棋、開車、醫療診斷、交通治理等等,也可以理解為,模型就是一個函數 f(x),上述過程,可以表達為:f(一張圖片)= 狗 / 不是狗。

一個 AI 應用開發,大概分為三個階段:

  • 第一階段,識別問題、構建模型、選擇模型。AI 的開發和培養一個小孩子類似,不同的孩子有不同的愛好和特長,同樣地,AI 也有很多模型 / 算法,不同的模型 / 算法適合解決不同的問題。所以,首先要識別你要解決的是個什麼問題,然後選擇一個合適的模型 / 算法;
  • 第二階段,訓練模型。和培養小孩子一樣,即使你發現小孩子有音樂天分,他也不可能天生就是鋼琴家,他需要專業的訓練。AI 開發也一樣,選定模型 / 算法后,即使算法再好,也不能馬上有效工作,你需要用大量的數據訓練這個模型,訓練過程中不斷優化參數,讓模型能夠更為有效地工作。這個階段 AI 模型的工作狀態,叫做“訓練”;
  • 第三階段,模型部署。模型訓練結束后就可以部署了。比如一個人臉識別的模型,你可以把它部署在手機上,用於開機鑒權,也可以把它部署在園區閘機上,用於出入管理,還可以把它部署在銀行的客戶端上,用於業務鑒權,等等。如同一個孩子成長為鋼琴家后,既可以在音樂會上演奏,也可以在家庭聚會上表演。AI 模型部署之後的工作狀態,專業的說法,叫做“推理”。

通過上述 AI 開發過程的簡述,可以發現,算法、數據和算力,是驅動 AI 發展的三大動力,三者缺一不可。

  • 算法相當於是基因。基因不好,再努力也白搭。如何識別問題,並根據問題選擇算法,甚至開發新的算法,是高端 AI 專家的核心競爭力;
  • 數據相當於是學習材料。光基因好,沒有好的教材,也教不出大師。AI 的訓練,需要海量的、高質量的數據作為輸入。AlhpaGo 通過自己和自己下棋,下了幾千萬盤,人類一輩子最多也就下幾千盤。沒有這樣的訓練量,AlhpaGo 根本不可戰勝人類。自動駕駛,Google 已經搞了 10 年,訓練了幾十萬小時,遠遠超過一個專業賽車手的訓練量,但離真正的無人駕駛還差很遠。另外,數據的質量也很重要,如果你給 AI 輸入的數據是錯的,那麼訓練出來的 AI,也會做出錯誤的結果。簡單的說,如果你把貓的圖片當做狗的圖片去訓練 AI,那麼訓練出來的 AI,就一定會把貓當做狗。數據的重要性直接導致了中國湧現了大批以數據標註為生存手段的公司和個人;
  • 一個小孩,光有天分和好的學習材料,自己如果不努力,不投入時間和精力好好學習,絕對不可能成為大師。同樣的道理,一個 AI 模型,算法再好、數據再多,如果沒有足夠的算力,支撐它持續不斷的訓練,這個模型永遠也不能成為一個真正好用的模型。這就是為什麼英偉達崛起的原因,這家公司的 GPU 芯片提供了最為適配於人腦神經網絡的計算算力,現在國內工業界也有了類似的公司產品 – 華為的達芬奇芯片。

訓練 AI 應用模型

動手實踐前

接下來,我們通過對一個 AI 應用模型的訓練和推理過程介紹,開始動手實踐。

訓練模型需要算力,對於算力的獲取,訓練和推理可以根據自己的業務需求,選擇使用公有雲或自己購買帶算力芯片的服務器,本文案我選擇的是某花廠的 AI 開發平台,因為近期他們剛推出一個免費算力的推廣活動,可以省下一筆訓練費用。為了便於調試,我首先在自己的 CPU 個人電腦上編寫代碼、訓練模型,這樣做的缺點是模型收斂的時間長了一些。

疫情期間,對於民眾來說,佩戴口罩是最有效防止被傳染新冠病毒的方式,保護自己的同時也保護他人。所以本文的案例是佩戴口罩的識別模型訓練。識別算法離不開目標檢測。目標檢測(Object Detection)的任務是找出圖像中所有感興趣的目標(物體),確定它們的位置和大小。由於各類物體有不同的形狀、大小和數量,加上物體間還會相互遮擋, 因此目標檢測一直都是機器視覺領域中最具挑戰性的難題之一。

基於深度學習的人臉檢測算法,多數都是基於深度學習目標檢測算法進行的改進,或者說是把通用的目標檢測模型,為適應人臉檢測任務而進行的特定配置。而眾多的目標檢測模型(Faster RCNN、SSD、YOLO)中,人臉檢測算法最常用的是 SSD 算法(Single Shot MultiBox Detector,“Single Shot”指的是單目標檢測,“MultiBox”中的“Box”就像是我們平時拍攝時用到的取景框,只關注框內的畫面,屏蔽框外的內容。創建“Multi”個 “Box”,將每個 “Box” 的單目標檢測結果匯總起來就是多目標檢測。換句話說,SSD 將圖像切分為 N 片,並對每片進行獨立的單目標檢測,最後匯總每片的檢測結果。),其他如 SSH 模型、S3FD 模型、RetinaFace 算法,都是受 SSD 算法的啟發,或者基於 SSD 進行的任務定製化改進, 例如將定位層提到更靠前的位置,Anchor 大小調整、Anchor 標籤分配規則的調整,在 SSD 基礎上加入 FPN 等。本文訓練口罩識別模型採用了 YOLO。

目標檢測過程都可以分解為兩個獨立的操作:

  • 定位(location): 用一個矩形(bounding box)來框定物體,bounding box 一般由 4 個整數組成,分別表示矩形左上角和右下角的 x 和 y 坐標,或矩形的左上角坐標以及矩形的長和高。
  • 分類(classification): 識別 bounding box 中的(最大的)物體。

我選擇採用 keras-yolo3-Mobilenet 方案,開源項目地址:
https://github.com/Adamdad/keras-YOLOv3-mobilenet。
MobileNet 的創新亮點是 Depthwise Separable Convolution(深度可分離卷積),與 VGG16 相比,在很小的精度損失情況下,將運算量減小了 30 倍。YOLOv3 的創新亮點是 DarkNet-53、Prediction Across Scales、多標籤多分類的邏輯回歸層。

基於開源數據集的實驗結果:

動手訓練模型

訓練模型自然需要訓練數據集和測試數據集,大家可以在這裏下載:

https://modelarts-labs-bj4.obs.cn-north-4.myhuaweicloud.com/casezoo/maskdetect/datasets/maskdetectdatasets.zip

Yolo v3-MobileNet 目標檢測工程的目錄結構如下:

|--model_data
 |--voc_classes.txt
 |--yolo_anchors.txt
|--yolo3
 |--model.py
 |--model_Mobilenet.py
 |--utiles.py
|--convert.py
|--kmeans.py
|--train.py
|--train_Mobilenet.py
|--train_bottleneck.py
|--voc_annotation.py
|--yolo.py
|--yolo_Mobilenet.py
|--yolo_video.py

開源項目的好處是已經幫你封裝了流程,例如涉及的 Yolo 代碼不用修改,本次訓練過程需要修改的代碼主要是以下三個:

1.train_Mobilenet.py:模型訓練代碼;
2.yolo/model_Mobilenet.py:基於 mobilenet 的 yolo 的模型代碼,如果相對模型代碼仔細研究的人,可以研究這個代碼;
3.yolo_Mobilenet.py:模型推理代碼。

接下來具體介紹我們需要修改的代碼,按照功能分為數據類、模型類、可視化類、遷移上雲準備類。

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

•數據類:

仿照 modeldata/vocclasses.txt 寫一個是否有戴口罩的類別的 txt,內容只有 yes_mask、no_mask 兩個字符。

如果你下載我給出的數據集,你會發現,口罩數據集中給出的 xml 標註格式是 VOC 的標準的,仿照 convert.py 和 voc_annotation.py 寫一個數據轉換文件,代碼如下所示:

import xml.etree.ElementTree as ET 
import os 
 
def convert_annotation(classes, label_path): 
    in_file = open(label_path) 
    tree=ET.parse(in_file) 
    root = tree.getroot() 
    output_list = [] 
    for obj in root.iter('object'): 
        difficult = obj.find('difficult').text 
        cls = obj.find('name').text 
        if cls not in classes or int(difficult)==1: 
            continue 
        cls_id = classes.index(cls) 
        xmlbox = obj.find('bndbox') 
        b = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text)) 
        output_list.append(" " + ",".join([str(a) for a in b]) + ',' + str(cls_id)) 
    return (' '.join(output_list)) 
 
 
def mask_convert(data_path, classes): 
    img_list = [] 
    for i in list(os.listdir(data_path)): 
        if i.split('.')[1] == 'jpg': 
            img_list.append(i.split('.')[0]) 
 
    output_list = [] 
    for image_id in img_list: 
        img_path = (data_path + '/%s.jpg' % (image_id)) 
        label_path = (data_path + '/%s.xml' % (image_id)) 
        annotation = convert_annotation(classes, label_path) 
        output_list.append(img_path + annotation) 
 
    return output_list

•模型類:

訓練過程中會有一個 tensor 對不上的錯誤,需要修改 model_data/model.py 這個代碼中的 140-141 行,如下所示:

box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[..., ::-1], K.dtype(feats)) 
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[..., ::-1], K.dtype(feats))

•可視化類:

為了直觀判斷模型效果,增加了一個在圖片上直接標註的可視化代碼,也就是在圖片上打印輸出結果(yes_mask 或 no_mask),代碼如下所示:

#!/usr/bin/env python 
# coding: utf- 
 
img_path = "D:/Code/mask_detection/data/test" 
save_path = "D:/Code/mask_detection/data/test_result/" 
 
 
import matplotlib 
matplotlib.use('Agg') 
import matplotlib.pyplot as plt 
import matplotlib.patches as patches 
import matplotlib.image as mpimg 
import random 
import json 
 
# 推理輸出路徑 
with open('D:/Code/mask_detection/keras-YOLOv3-mobilenet-master/annotation_YOLOv3.json') as json_file: 
    data = json.load(json_file) 
 
imgs = list(data.keys()) 
 
def parse_json(json): 
    bbox = [] 
    for item in json['annotations']: 
        name = item['label'] 
        xmin = item['x'] 
        ymin = item['y'] 
        xmax = item['x']+item['width'] 
        ymax = item['y']+item['height'] 
        bbox_i = [name, xmin, ymin, xmax, ymax] 
        bbox.append(bbox_i) 
    return bbox 
 
 
def visualize_bbox(image, bbox, name): 
    fig, ax = plt.subplots() 
    plt.imshow(image) 
    colors = dict()#指定標註某個對象的邊框的顏色 
    for bbox_i in bbox: 
        cls_name = bbox_i[0] #得到 object 的 name 
        if cls_name not in colors: 
            colors[cls_name] = (random.random(), random.random(), random.random()) #隨機生成標註 name 為 cls_name 的 object 的邊框顏色 
        xmin = bbox_i[1] 
        ymin = bbox_i[2] 
        xmax = bbox_i[3] 
        ymax = bbox_i[4] 
        #指明對應位置和大小的邊框 
        rect = patches.Rectangle(xy=(xmin, ymin), width=xmax-xmin, height=ymax-ymin, edgecolor=colors[cls_name],facecolor='None',linewidth=3.5) 
        plt.text(xmin, ymin-2, '{:s}'.format(cls_name), bbox=dict(facecolor=colors[cls_name], alpha=0.5)) 
        ax.add_patch(rect) 
    plt.axis('off') 
    plt.savefig(save_path+'{}_gt.png'.format(name)) #將該圖片保存下來 
    plt.close() 
 
 
for item in imgs: 
    img = mpimg.imread(img_path+item) 
    bbox = parse_json(data[item]) 
    visualize_bbox(img, bbox, item.split('.')[0])

•上雲準備類:

開源代碼寫的比較隨意,直接就是在訓練代碼 trian_Mobilenet.py 代碼中一開頭指定所有的參數。華為雲中訓練作業是需要指定 OBS 的輸入路徑和輸出路徑的,最好使用 argparse 的形式將路徑參數傳進去。其他參數可以按照自己需求做增加,修改樣例如下:

import argparse 
 
parser = argparse.ArgumentParser(description="training a maskmodel in modelarts") 
# 訓練輸出路徑 
parser.add_argument("--train_url", default='logs/maskMobilenet/003_Mobilenet_finetune/', type=str) 
# 數據輸入路徑 
parser.add_argument("--data_url", default="D:/code/mask_detection/data/MASK_MERGE/", type=str) 
# GPU 數量 
parser.add_argument("--num_gpus", default=0, type=int) 
args = parser.parse_args()

開源代碼中,數據處理的部分是將 xml 轉換成 yolo 讀的 txt 文檔,這樣導致數據輸入需要有一個寫入到 txt 文件,然後訓練工程讀取這個 txt 文件和圖片的過程。上雲后,這種流程不太方便,需要將數據處理,數據轉換和訓練代碼打通。這裏我使用緩存將數據直接傳到訓練代碼中,這樣改起來比較方便,但是當數據量較大的時候並不科學,有興趣的人可以自己修改。

遷移公有雲

我使用某廠商公有雲的 AI 訓練平台,用的是 OBS 桶上傳已經調試好的代碼(建議大家體驗 Notebook 方式,在線編程、編譯),如下圖所示:

接着啟動 Notebook,不過我沒有用 jupyter 方式寫代碼,而是採用同步 OBS 桶的資源,通過 Notebook 啟動一個 GPU 鏡像:

創建一個 Notebook 環境:

確認計算資源規格:

創建 Notebook 環境成功:

從 OBS 桶同步相關文件:

接下來進入該 Notebook 的終端環境,運行以下代碼,啟動訓練任務:

訓練過程輸出片段如下所示:

2020-04-07 18:58:14.497319: I tensorflow/stream_executor/dso_loader.cc:152] successfully opened CUDA library libcublas.so.10.0 locally
7/7 [==============================] - 17s 2s/step - loss: 4226.4421 - val_loss: 22123.3750
Epoch 2/50
7/7 [==============================] - 6s 855ms/step - loss: 1083.1558 - val_loss: 1734.1427
Epoch 3/50
7/7 [==============================] - 6s 864ms/step - loss: 521.8567 - val_loss: 455.0971
Epoch 4/50
7/7 [==============================] - 6s 851ms/step - loss: 322.8907 - val_loss: 193.3107
Epoch 5/50
7/7 [==============================] - 6s 841ms/step - loss: 227.7257 - val_loss: 150.8902
Epoch 6/50
7/7 [==============================] - 6s 851ms/step - loss: 179.0605 - val_loss: 154.9351
Epoch 7/50
7/7 [==============================] - 6s 868ms/step - loss: 150.4297 - val_loss: 147.3101
Epoch 8/50
7/7 [==============================] - 8s 1s/step - loss: 129.5681 - val_loss: 144.8283

模型生成后,創建一個 python 腳本,代碼如下,實現了模型文件拷貝到 OBS 桶:

from modelarts.session import Session
session = Session()
session.upload_data(bucket_path="/mask-detection-modelarts-test/run/log/", path="/home/ma-user/work/log/trained_weights_final.h5")

運行推理腳本,我把推測結果打印在了測試圖片上,如下圖所示,識別出了口罩:

後記

AI 技術的興起,已經帶動了科技行業的革命,而每一次業界的革命,都會讓一些公司落寞而讓另一些公司崛起,程序員也一樣,每一次技術換代也都會讓一些程序員沒落而讓另一些程序員崛起。抓住目前正在流行的 AI 技術趨勢,使用雲端的免費計算資源,上手學習並實踐 AI 技術,會是相當一部分軟件工程師、數據科學家的選擇。此外,由於在圖像識別、文本識別、語音識別等技術領域,算法的精度已經給有大幅度的提升,在很多場景下已經達到可商用級別,也進一步讓自動機器學習技術(模型的自動設計和訓練)的發展成為可能。因此,在上述幾個技術領域的很多應用場景下,公有雲已經可以做到根據用戶自定義數據進行 AI 模型的自動訓練。

點擊關注,第一時間了解華為雲新鮮技術~

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

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

前後端分離,如何在前端項目中動態插入後端API基地址?(in docker)_網頁設計公司

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

開門見山,本文分享前後端分離,容器化前端項目時動態插入後端API基地址,這是一個很贊的實踐,解決了前端項目容器化過程中受制後端調用的尷尬。

尷尬從何而來

常見的web前後端分離:前後端分開部署,前端項目由nginx承載打包文件,反向代理請求。
應用的某些部分必須是可配置的,比如API調用基地址
前端打包的時候需要統一插入該地址形成完整chunk files。

# ------------------------------------------------------
# generate chunk file
# ------------------------------------------------------
FROM node:10-alpine as builder

# install and cache app dependencies
COPY package.json package-lock.json ./
RUN npm install && mkdir /react-frontend && mv ./node_modules ./react-frontend
WORKDIR /react-frontend
COPY . .
RUN npm run build


# ------------------------------------------------------
# Production Build
# ------------------------------------------------------
FROM nginx:latest
COPY  nginx.conf  /etc/nginx/nginx.conf
COPY --from=builder /react-frontend/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

當在Docker中打包前端,或許會嘗試用鏡像構建參數Arg/Env來傳遞後端API調用基地址,但這樣是很不理想的:
打包時參數被統一插入,打包結果chunk files作為最終鏡像的一部分,導致最終的前端鏡像會與後端API地址強關聯。

或許你會針對不用的後端環境(canary、staging、production)構建不同的前端鏡像,但這是一次又一次的工作量,並不是最佳實踐。

下面分享一個容器執行階段動態插入後端API基地址的實踐

前端獨立部署,動態插入後端API基地址(in Docker)

我希望將API基地址延遲到生成容器階段(與構建鏡像的過程解耦), 這樣我就可以使用一個鏡像,針對不同的環境傳參形成不同的前端容器。

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

前端項目插入配置的腳本如下:

// FILE: set-env.ts
...
export const environment = {
  production: ${isProd},
  apiBaseUrl: 'API_BASE_URL',
  version: 'v${require('../package.json').version}'
};
...

我們在前端配置中寫入API_BASE_URL佔位符,按照既定流程前端打包;

Dockerfile CMD指令指示容器如何運行:

  • 用真實值替換前端chunk files中原插入的API_BASE_URL佔位符
  • 使用nginx承載替換后的chunk files
# FILE: Dockerfile
...
EXPOSE 80

COPY --from=builder /react-frontend/replace_api_url.sh /
CMD ["sh", "replace_api_url.sh"]

下面是replace_api_url.sh的內容:

#!/usr/bin/env sh

find '/usr/share/nginx/html' -name '*.js' -exec sed -i -e 's,API_BASE_URL,'"$API_BASE_URL"',g' {} \;
nginx -g "daemon off;"

正常構建鏡像之後;現在生成容器時,可通過環境變量傳參替換原前端chunk files的API_BASE_URL字符串

docker build -t front .
docker run -p 80:80 -e API_BASE_URL=http://somebackend.com/api front

總結輸出

這是一個巧妙的設計,讓我們在前端獨立容器化部署時,能解耦後端API基地址,避免了一次又一次的構建鏡像工作量。

Dockerfile CMD指令包裝的容器啟動腳本:讓我們在nginx承載前端打包文件之前,做一次字符串替換,成功將後端API基地址“延遲”到容器運行階段。

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

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用,循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理,循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理_網頁設計

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

在我們開發BS頁面的時候,往往需要了解常規界面組件的使用,小到最普通的單文本輸入框、多文本框、下拉列表,以及按鈕、圖片展示、彈出對話框、表單處理、條碼二維碼等等,本篇隨筆基於普通表格業務的展示錄入的場景介紹這些常規Element組件的使用,使得我們對如何利用Element組件有一個大概的認識。

1、列表界面和其他模塊展示處理

在前面隨筆《循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理》介紹了基於列表展示了相關數據,並在列表界面整合了增刪改查等常規的業務操作處理。

 常規的列表展示界面,一般分為幾個區域,一個是查詢區域,一個是列表展示區域,一個是底部的分頁組件區域。查詢區域主要針對常規條件進行布局,以及增加一些全局或者批量的操作,如導入、導出、添加、批量添加、批量刪除等按鈕;而其中主體的列表展示區域,是相對比較複雜一點的地方,需要對各項數據進行比較友好的展示,可以結合Tag,圖標,按鈕等界面元素來展示,其中列表一般後面會包括一些對單行記錄處理的操作,如查看、編輯、刪除的操作,如果是批量刪除,可以放到頂部的按鈕區域。

對於常規按鈕、表格、分頁組件,前面已經做了相關的介紹,這裏就不再贅述。

在介紹具體界面組件的時候,我們先來了解下,整體的界面布局,我們把常規的列表界面,新增、編輯、查看、導入等界面放在一起,除了列表頁面,其他內容以彈出層對話框的方式進行處理,如下界面示意所示。

 每個對話框的:visible的屬性值,則是確定哪個模態對話框的显示和隱藏。

在Vue的JS模塊裏面,我們除了定義對應的對話框显示的變量外,對每個對話框,我們定義一個表單信息用來進行數據的雙向綁定處理。

 常規的新增、編輯、查看、導入等內容的定義,作為一個對話框組件定義,常規的對話框組件的使用代碼如下所示。

<el-dialog
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
  :before-close="handleClose">
  <span>這是一段信息</span>
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">確 定</el-button>
  </span>
</el-dialog>

為了控制對話框的樣式,我們這裏注意下footer的slot,這個我們一般是把處理按鈕放在這裏,如對於查看界面對話框,我們定義如下所示。

  一般來說,對於表單內容比較多的場景,我們一般分開多個選項卡進行展示或者錄入,這樣方便管理,查看界面整體效果如下所示。

 對於對話框的數據綁定,我們在打開對話框前,先通過API模塊請求獲得JSON數據,然後綁定在對應的對話框模型屬性上即可,如對於查看界面,我們的處理邏輯如下所示。

    showView(id) {
      var param = { id: id }
      GetProductDetail(param).then(data => {
        Object.assign(this.viewForm, data);
      })
      this.isView = true
    },

對於表格的雙擊,我們同樣綁定它的查看明細處理操作,如下模板代碼和JS代碼所示。

模板HTML代碼如下:

<el-table
  v-loading="listLoading"
  :data="productlist"
  border
  fit
  stripe
  highlight-current-row
  :header-cell-style="{background:'#eef1f6',color:'#606266'}"
  @selection-change="selectionChange"
  @row-dblclick="rowDbclick"
>

JS邏輯代碼如下  

    rowDbclick(row, column) {
      var id = row.ID
      this.showView(id);
    },

 

2、常規界面組件的使用

 一般情況下,我們使用界面組件的時候,參考下官網《Element組件使用》,尋找對應組件的代碼進行參考,就差不多了,這裏還是就各種常規的Element組件進行大概的介紹吧。

1)表單和表單項、單文本框

對於表單,我們一般定義一個對應的名稱,並設置它的data對應的模型名稱即可,如下所示。

<el-form ref="viewForm" :model="viewForm" label-width="80px">

而表單項,一般是定義好表單項的Label即可,然後在其中插入對應的錄入控件或者展示控件。如對於單文本組件使用,如下所示。

 <el-form-item label="產品編號">
   <el-input v-model="editForm.ProductNo" />
 </el-form-item>

其中 v-model=”editForm.ProductNo”  就是對應綁定的數據。

而表單項,可以添加對字段的驗證處理,在數據提交前,可以校驗客戶的錄入是否有效等。

  <el-form-item
    prop="email"
    label="郵箱"
    :rules="[
      { required: true, message: '請輸入郵箱地址', trigger: 'blur' },
      { type: 'email', message: '請輸入正確的郵箱地址', trigger: ['blur', 'change'] }
    ]"
  >
    <el-input v-model="dynamicValidateForm.email"></el-input>
  </el-form-item>

注意這裏表單項,必須添加一個prop的屬性設置,如 prop=”email” 所示。

一般為了控制布局,我們還結合el-row進行一個布局的處理,如下代碼所示(一行等於span為24,span=12也就是一行放兩個控件組)。

<el-row>
  <el-col :span="12">
    <el-form-item label="產品編號">
      <el-input v-model="editForm.ProductNo" />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="條碼">
      <el-input v-model="editForm.BarCode" />
    </el-form-item>
  </el-col>
</el-row>

 

2)、下拉列表控件的綁定

下拉列表的綁定處理,也是通過 v-model 進行值的綁定,而選項則可以通過數據列表進行綁定。

<el-form-item label="商品類型">
  <el-select v-model="editForm.ProductType" filterable="" placeholder="請選擇">
    <el-option
      v-for="(item, key) in typeList"
      :key="key"
      :label="item.value"
      :value="item.key"
    />
  </el-select>
</el-form-item>

而選項中的 typeList,我們可以在頁面初始化的時候獲取出來即可。

  created() {
    // 獲取產品類型,用於綁定字典等用途
    GetProductType().then(data => {
      if (data) {
        data.forEach(item => {
          this.productTypes.set(item.id, item.name)
          this.typeList.push({ key: item.id, value: item.name })
        })

        // 獲取列表信息
        this.getlist()
      }
    });
  },

對於textarea常規的多行文本框,其實和普通單行文本框處理差不多,指定它的type=”textarea” 和 rows的數值即可。

  <el-tab-pane label="說明" name="second">
    <el-form-item label="說明">
      <el-input v-model="editForm.Description" type="textarea" :rows="10" />
    </el-form-item>
  </el-tab-pane>

而對於一些可能需要展示HTML內容的,我們可以使用DIV控件來展示,通過v-html標識來處理包含HTML代碼的內容。

  <el-tab-pane label="詳細說明">
    <el-form-item label="詳細說明">
      <div class="border-radius" v-html="viewForm.Note" />
    </el-form-item>
  </el-tab-pane>

 

3)、圖片展示

對於一些需要展示服務器圖片,我們請求后,根據Element圖片組件的設置處理即可,如下包括單個圖片和多個圖片的展示和預覽操作。

 圖片展示的代碼如下所示。

  <el-tab-pane label="圖片信息">
    <el-form-item label="封面圖片">
      <el-image
        style="width: 100px; height: 100px"
        :src="viewForm.Picture"
        :preview-src-list="[viewForm.Picture]"
      />
    </el-form-item>
    <el-form-item label="Banner圖片">
      <el-image
        style="width: 100px; height: 100px"
        :src="viewForm.Banner"
        :preview-src-list="[viewForm.Banner]"
      />
    </el-form-item>
    <el-form-item label="商品展示圖片">
      <el-image
        v-for="item in viewForm.pics"
        :key="item.key"
        class="border-radius"
        :src="item.pic"
        style="width: 100px; height: 100px;padding:10px"
        :preview-src-list="getPreviewPics()"
      />
    </el-form-item>
  </el-tab-pane>

上圖中,如果是單個圖片,那麼預覽我們設置一個集合為一個url即可,如 [viewForm.Banner],如果是多個圖片,需要通過一個函數來獲取圖片列表,如 getPreviewPics() 函數所示。

    getPreviewPics() {
      // 轉換ViewForm.pics裏面的pic集合
      var list = []
      if (this.viewForm.pics) {
        this.viewForm.pics.forEach(item => {
          if (item.pic) {
            list.push(item.pic)
          }
        })
      }
      return list
    }

 

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

4)、第三方擴展控件

對於一些需要使用擴展組件的,我們一般搜索下解決方案,通過npm安裝對應的組件即可解決,如對於條碼和二維碼,我使用 @chenfengyuan/vue-barcode和 @chenfengyuan/vue-qrcode,一般在Github上搜索下關鍵字,總能找到一些很受歡迎的第三方組件。

  安裝這些組件都有具體的說明,如下所示(如果卸載,直接修改install為uninstall即可)。

npm install @chenfengyuan/vue-barcode vue

以及

npm install @chenfengyuan/vue-qrcode vue

條碼和二維碼的展示效果如下所示

如果全局引入barcode和qrcode 組件,我們在main.js裏面引入即可,如下代碼所示

// 引入barcode,qrcode
import VueBarcode from '@chenfengyuan/vue-barcode';
import VueQrcode from '@chenfengyuan/vue-qrcode';
Vue.component(VueBarcode.name, VueBarcode);

  

 富文本編輯,我這裏採用了 Tinymce 第三方組件來實現編輯處理,展示效果如下所示。

  代碼如下所示

  <el-tab-pane label="詳細說明" name="third">
    <el-form-item label="詳細說明">
      <tinymce v-model="editForm.Note" :height="300" />
    </el-form-item>
  </el-tab-pane>

以上就是一些常規的界面組件的使用,後面在繼續介紹文件上傳和圖片結合的操作。

3、自定義組件的創建使用

使用Vue的比以往BS開發的好處,就是可以很容易實現組件化,這點很好,一旦我們定義好一個控件,就可以在多個界面裏面進行使用,非常方便,而且封裝性可以根據自己的需要進行處理。

查詢區域一般的界面效果如下所示,除了包含一些常用的查詢條件,一般會有一些下拉列表,這些可能是後台字典裏面綁定的內容,可以考慮作為一個通用的字典下拉列表組件來做。

其實界面錄入的時候,也往往需要這些條件下拉列表的。

  那麼我們來定義一個自定義組件,並在界面上使用看看。

在Components目錄創建一個目錄,並創建一個組件的vue文件,命名為my-dictdata.vue,如下所示。

 界面模板代碼我們就一個select組件為主即可。

<template>
  <el-select v-model="svalue" filterable clearable placeholder="請選擇">
    <el-option
      v-for="(item, index) in dictItems"
      :key="index"
      :label="item.Text"
      :value="item.Value"
    />
  </el-select>
</template>

script腳本邏輯代碼如下所示。

<script>
// 引入API模塊類方法
import { GetDictData } from '@/api/dictdata'

export default {
  name: 'MyDictdata', // 組件的名稱
  props: {
    typeName: { // 字典類型方式,從後端字典接口獲取數據
      type: String,
      default: ''
    },
    options: {// 固定列表方式,直接綁定
      type: Array,
      default: () => { return [] }
    }
  },
  data() {
    return {
      dictItems: [], // 設置的字典列表
      svalue: '' // 選中的值
    }
  },
  watch: {
    // 判斷下拉框的值是否有改變
    svalue(val, oldVal) {
      if (val !== oldVal) {
        this.$emit('input', this.svalue);
      }
    }
  },
  mounted() {
    var that = this;

    if (this.typeName && this.typeName !== '') {
      // 使用字典類型,從服務器請求數據
      GetDictData(this.typeName).then(data => {
        if (data) {
          data.forEach(item => {
            if (item && typeof (item.Value) !== 'undefined' && item.Value !== '') {
              that.dictItems.push(item)
            }
          });
        }
      })
    } else if (this.options && this.options.length > 0) {
      // 使用固定字典列表
      this.options.forEach(item => {
        if (item && typeof (item.Value) !== 'undefined' && item.Value !== '') {
          that.dictItems.push(item)
        }
      });
    }
    // 設置默認值
    this.svalue = this.value;
  },
  methods: {

  }
}
</script>

主要就是處理字典數據的獲取,並綁定到模型對象上即可。

在頁面上使用前,需要引入我們定義的組件

import myDictdata from '@/components/Common/my-dictdata'

然後包含進去components裏面即可

export default {
  components: { myDictdata },

那麼原來需要直接使用select組件的代碼

<el-select v-model="searchForm.ProductType" filterable clearable placeholder="請選擇">
  <el-option
    v-for="(item, key) in typeList"
    :key="key"
    :label="item.value"
    :value="item.key"
  />
</el-select>

則可以精簡為一行代碼

<my-dictdata v-model="searchForm.ProductType" type-name="商品類型" />

而對於固定列表的,我們也可以通用的處理代碼

<my-dictdata v-model="searchForm.Status" :options="Status" />

其中Status是定義的一個對象集合

  Status: [
    { Text: '正常', Value: 0 },
    { Text: '推薦', Value: 1 },
    { Text: '停用', Value: 2 }
  ]

是不是非常方便,而得到的效果則不變。

 

 以上就是多個頁面內容,通過對話框層模式整合在一起,並介紹如何使用,以及對界面中常見的Element組件進行介紹如何使用,以及定義一個字典列表的主定義組件,用於簡化界面代碼使用,

 

列出以下前面幾篇隨筆的連接,供參考:

循序漸進VUE+Element 前端應用開發(1)— 開發環境的準備工作

循序漸進VUE+Element 前端應用開發(2)— Vuex中的API、Store和View的使用

循序漸進VUE+Element 前端應用開發(3)— 動態菜單和路由的關聯處理

循序漸進VUE+Element 前端應用開發(4)— 獲取後端數據及產品信息頁面的處理

循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理

 

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

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

設計模式之單例模式_貨運

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

目錄:

  • 什麼是單例模式
  • 單例模式的應用場景
  • 單例模式的優缺點
  • 單例模式的實現
  • 總借

一、什麼是單例模式

  單例模式顧名思義就是只存在一個實例,也就是系統代碼中只需要一個對象的實例應用到全局代碼中,有點類似全局變量。例如,在系統運行時,系統需要讀取配置文件中的參數,在設計系統的時候讀取配置文件的類往往設計成單例類。因為系統從啟動運行到結束,只需要讀取一次配置文件,這個讀取配置文件全部由該類負責讀取,在全局代碼中只需要使用該類即可。這樣不僅簡化了配置文件的管理,也避免了代碼讀取配置文件數據的不一致性。

 

 單例模式的特點:

  1、該類的構造方法聲明為private,這樣其他類無法初始花該類,只能通過該類的public方法獲取該類的對象。

  2、裏面有個私有的對象成員,該成員對象是類本身,用於public方法返回該類的實例。

  3、該類中提供一個public的靜態方法,返回該類的私有成員對象。

 

二、單例的應用場景

 

  舉一個小例子,在我們的windows桌面上,我們打開了一個回收站,當我們試圖再次打開一個新的回收站時,Windows系統並不會為你彈出一個新的回收站窗口。,也就是說在整個系統運行的過程中,系統只維護一個回收站的實例。這就是一個典型的單例模式運用。

 

  繼續說回收站,我們在實際使用中並不存在需要同時打開兩個回收站窗口的必要性。假如我每次創建回收站時都需要消耗大量的資源,而每個回收站之間資源是共享的,那麼在沒有必要多次重複創建該實例的情況下,創建了多個實例,這樣做就會給系統造成不必要的負擔,造成資源浪費。

 

  再舉一個例子,網站的計數器,一般也是採用單例模式實現,如果你存在多個計數器,每一個用戶的訪問都刷新計數器的值,這樣的話你的實計數的值是難以同步的。但是如果採用單例模式實現就不會存在這樣的問題,而且還可以避免線程安全問題。同樣多線程的線程池的設計一般也是採用單例模式,這是由於線程池需要方便對池中的線程進行控制

 

  同樣,對於一些應用程序的日誌應用,或者web開發中讀取配置文件都適合使用單例模式,如HttpApplication 就是單例的典型應用。

 

  從上述的例子中我們可以總結出適合使用單例模式的場景和優缺點:  

 

   適用場景:

  1.需要生成唯一序列的環境

  2.需要頻繁實例化然後銷毀的對象。

  3.創建對象時耗時過多或者耗資源過多,但又經常用到的對象。 

  4.方便資源相互通信的環境

 

 

三、單例模式的優缺點

  優點

    1、在內存中只有一個對象,節省內存空間;

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

    2、避免頻繁的創建銷毀對象,可以提高性能;

    3、避免對共享資源的多重佔用,簡化訪問;

    4、為整個系統提供一個全局訪問點。

  缺點

    1、不適用於變化頻繁的對象;

    2、濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;

    3、如果實例化的對象長時間不被利用,系統會認為該對象是垃圾而被回收,這可能會導致對象狀態的丟失;

 

四、單例模式的實現

  1、餓漢式

public class Mgr{
    //創建自己的實例,並初始化私有靜態final成員
    private static final Mgr mgr = new Mgr();
    //私有構造方法
    private Mgr() {}; 
    //公共方法,返回自己的實例化成員
    public static Mgr getMgr() { 
        return  mgr;
    }
}

  備註:該單例實現方法簡單明了,推薦使用。該類被JVM加到內存的時候,只會加載一次,並且只實例化一個單例,優點是具有線程安全性,缺點是:不用他也在內存中實例化,浪費內存。所以提出了懶散式實現方式。

  2、懶漢式

public class Mgr{
   //聲明私有靜態對象成員,作為返回值
    private static Mgr mgr;
   //私有構造函數
    private Mgr() {}; 
   //懶漢的特點,提供公共靜態方法,如果該成員對象為空,實例化並返回
    public static Mgr getMgr() {
        if(mgr == null){
            mgr =  = new Mgr();
        }
         return  mgr;
     }
}

  備註:(不推薦用)這種實現方法只有程序在調用該類的getMgr方法才實例話對象並返回,特點就是調用的時候再實例化並返回,延遲加載的被動形式。但是該實現方法不是線程安全的,因為當同時有有兩個線程執行到if(mgr==null)語句的時候,由於某些原因其中一個線程先一步執行下一句,實例化了對象並返回;兩一個線程再實例化對象在返回,這兩個線程返回的對象不是同一個對象(這難道還是單例嗎!),所以該實現方法的缺點也很明顯。那為了避免線程不安全問題,在懶漢寫法上提出加鎖的實現方式。

  3、給公共方法加鎖

public class Mgr{
    //聲明私有靜態對象成員,作為返回值
    private static Mgr mgr;
    //私有構造函數
    private Mgr() {}; 
    //給公共方法加鎖,只有一個線程第一次獲得鎖實例化對象並返回
    public static syncnronized Mgr getMgr() {
        if(mgr == null){
            mgr = new Mgr();
        }
        return  mgr;
   }
}

  備註:(推薦使用)這種實現方式比較完善,既保證了懶散式的延遲加載方式,也保證了線程安全。缺點是在整個方法上加鎖,導致性能下降。因為只有第一次獲得鎖的線程實例化對象並返回,以後的線程獲得鎖的時候執行 if(mgr == null)語句的時候,由於mgr已經實例化了不為空,直接跳過返回實例。整個過程要競爭鎖,不能併發執行導致性能下降。那為優化性能下降問題,那我只在mgr = new Mgr()上加鎖,保證鎖粒度最小化的同時保證單例實例化。

  4、給實例化加鎖

public class Mgr{
    //私有靜態成員對象
    private static Mgr mgr;
    //私有構造函數
    private Mgr() {}; 
    //公共方法,在實例化語句塊加鎖,保證單例
    public static  Mgr getMgr() {
        if(mgr == null){
            syncnronized(Mgr.class){
                mgr = new Mgr();
            }
        }
         return  mgr;
  }
}

  備註:(不推薦使用)該實現方法雖然相較方法3性能有所提升,但並不能保證線程安全。因為當兩個線程同時執行if(mgr == null)語句時,其中線程1獲取鎖,實例化對象並返回,線程2在獲得鎖又在實例化對象並返回。線程1和線程2獲取的對象並不是同一個。所以在此基礎上提出了雙重判斷方式。

5、雙重判斷加鎖

public class Mgr{
    //私有靜態成員對象
    private static  Mgr mgr;
    //私有構造函數
    private Mgr() {}; 
    //公共方法提供雙重判斷並在實例化代碼塊加鎖
    public static  Mgr getMgr() {
        if(mgr == null){ //第一次判斷
            syncnronized(Mgr.class){
                if(mgr == null){ //第二次判斷
                    mgr =  = new Mgr();
                }      
            }
        }
         return  mgr;
  }
}

  備註:(推薦使用)相較於方法4,該方法雙重判定,如果多線程同時執行到第一次判斷語句位置,其中一個線程獲得鎖,由於是第一次該對象成員為空,實例化后並返回。其後其它線程調用公共方法的時候,由於實例化了,在第一次判斷自接返回實例,不在產生鎖競爭。大大提高了效率,保證了線程的安全性,也保證了延遲加載的特性。

 6、靜態內部類

public class Mgr{
    private Mgr() {};
    //定義靜態內部類
    private static class MgrHolder{
        private final static Mgr mgr = new Mgr();
    } 
    //公共方法直接返回靜態內部類的實例對象
    public static  Mgr getMgr() {
        return  MgrHolder.mgr;
  }
}

  備註:(可使用)該實現方法通過JVM來保證線程安全性,靜態內部類MgrHolder來New一個Mgr對象,JVM只會加載一次Mgr類(靜態內部類不會加載),當類調用getMgr方法的時候,也只會調用一次,公共方法調用靜態內部類,獲取一個對象(也是實現懶加載)。所以也是線程安全的。

7、枚舉類單例模式

public enum Mgr{
    mgr;
    public void m(){} //業務方法
}

  備註:(推薦使用)jdk1.5之後才能正常達到單例效果,參考來自《Effective Java》。注意枚舉類的枚舉變量必須寫在第一行,後面實現業務代碼。調用方式是:Mgr.mgr.Function_Name();具備枚舉類型的特點,有點是:線程同步,防止反序列化。

五、總結

  通過上面幾種單例模式的實現方式的列舉,但是在實際應用中其中的2,3,4三種方式並不適用,列出來只是讓讀者更好的理解方式5的由來,起到拋磚引玉的作用,更好的理解單例模式。總之常用的四種,懶漢,雙重校驗鎖,靜態內部類,枚舉單例。

  餓漢:類加載的時候就創建實例,所以是線程安全的,但不能延遲加載。

  雙重校驗鎖:線程安全,效率較高,延遲加載。

  靜態內部類:實現起來比較麻煩,在不同的編譯器上會出現不可預知的錯誤。

  枚舉單例:很好,不僅避免了多線程同步問題,而且能反正反序列化重新創建對象,但是不能延遲加載,用的人少。

 

  • 讀者發現有什麼有問題的地方謝謝留言指正。部分參考自:https://www.cnblogs.com/xuwendong/p/9633985.html#_label0

 

 

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

Keycloak快速上手指南,只需10分鐘即可接入Spring Boot/Vue前後端分離應用實現SSO單點登錄_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

登錄及身份認證是現代web應用最基本的功能之一,對於企業內部的系統,多個系統往往希望有一套SSO服務對企業用戶的登錄及身份認證進行統一的管理,提升用戶同時使用多個系統的體驗,Keycloak正是為此種場景而生。本文將簡明的介紹Keycloak的安裝、使用,並給出目前較流行的前後端分離應用如何快速接入Keycloak的示例。

Keycloak是什麼

Keycloak是一種面向現代應用和服務的開源IAM(身份識別與訪問管理)解決方案

Keycloak提供了單點登錄(SSO)功能,支持OpenID ConnectOAuth 2.0SAML 2.0標準協議,擁有簡單易用的管理控制台,並提供對LDAP、Active Directory以及Github、Google等社交賬號登錄的支持,做到了非常簡單的開箱即用。

Keycloak常用核心概念介紹

首先通過官方的一張圖來了解下整體的核心概念

這裏先只介紹4個最常用的核心概念:

  1. Users: 用戶,使用並需要登錄系統的對象

  2. Roles: 角色,用來對用戶的權限進行管理

  3. Clients: 客戶端,需要接入Keycloak並被Keycloak保護的應用和服務

  4. Realms: 領域,領域管理着一批用戶、證書、角色、組等,一個用戶只能屬於並且能登陸到一個域,域之間是互相獨立隔離的, 一個域只能管理它下面所屬的用戶

Keycloak服務安裝及配置

安裝Keycloak

Keycloak安裝有多種方式,這裏使用Docker進行快速安裝

docker run -d --name keycloak \
    -p 8080:8080 \
    -e KEYCLOAK_USER=admin \
    -e KEYCLOAK_PASSWORD=admin \
    jboss/keycloak:10.0.0

訪問http://localhost:8080並點擊Administration Console進行登錄

創建Realm

創建一個新的realm: demo,後續所有的客戶端、用戶、角色等都在此realm中創建

創建客戶端

創建前端應用客戶端

創建一個新的客戶端:vue-demo,Access Type選擇public

創建後端應用客戶端

創建一個新的客戶端:spring-boot-demo,Access Type選擇bearer-only

保存之後,會出現Credentials的Tab,記錄下這裏的secret,後面要用到

關於客戶端的訪問類型(Access Type)

上面創建的2個客戶端的訪問類型分別是public、bearer-only,那麼為什麼分別選擇這種類型,實際不同的訪問類型有什麼區別呢?

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

事實上,Keycloak目前的訪問類型共有3種:

confidential:適用於服務端應用,且需要瀏覽器登錄以及需要通過密鑰獲取access token的場景。典型的使用場景就是服務端渲染的web系統。

public:適用於客戶端應用,且需要瀏覽器登錄的場景。典型的使用場景就是前端web系統,包括採用vue、react實現的前端項目等。

bearer-only:適用於服務端應用,不需要瀏覽器登錄,只允許使用bearer token請求的場景。典型的使用場景就是restful api。

創建用戶和角色

創建角色

創建2個角色:ROLE_ADMIN、ROLE_CUSTOMER

創建用戶

創建2個用戶:admin、customer

綁定用戶和角色

給admin用戶分配角色ROLE_ADMIN

給customer用戶分配角色ROLE_CUSTOMER

Vue應用集成Keycloak簡明指南

創建vue項目

vue create vue-demo

添加官方Keycloak js適配器

npm i keycloak-js --save
npm i axios --save

main.js

import Vue from 'vue'
import App from './App.vue'
import Keycloak from 'keycloak-js'

Vue.config.productionTip = false

// keycloak init options
const initOptions = {
  url: 'http://127.0.0.1:8080/auth',
  realm: 'demo',
  clientId: 'vue-demo',
  onLoad:'login-required'
}

const keycloak = Keycloak(initOptions)

keycloak.init({ onLoad: initOptions.onLoad, promiseType: 'native' }).then((authenticated) =>{
  if(!authenticated) {
    window.location.reload();
  } else {
    Vue.prototype.$keycloak = keycloak
    console.log('Authenticated')
  }

  new Vue({
    render: h => h(App),
  }).$mount('#app')

  setInterval(() =>{
    keycloak.updateToken(70).then((refreshed)=>{
      if (refreshed) {
        console.log('Token refreshed');
      } else {
        console.log('Token not refreshed, valid for '
            + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');
      }
    }).catch(error => {
      console.log('Failed to refresh token', error)
    })
  }, 60000)

}).catch(error => {
  console.log('Authenticated Failed', error)
})

HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <div>
      <p>
        current user: {{user}}
      </p>
      <p>
        roles: {{roles}}
      </p>
      <p>
        {{adminMsg}}
      </p>
      <p>
        {{customerMsg}}
      </p>
    </div>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data() {
    return {
      user: '',
      roles: [],
      adminMsg: '',
      customerMsg: ''
    }
  },
  created() {
    this.user = this.$keycloak.idTokenParsed.preferred_username
    this.roles = this.$keycloak.realmAccess.roles

    this.getAdmin()
            .then(response=>{
              this.adminMsg = response.data
            })
            .catch(error => {
              console.log(error)
            })

    this.getCustomer()
            .then(response => {
              this.customerMsg = response.data
            })
            .catch(error => {
              console.log(error)
            })
  },
  methods: {
    getAdmin() {
      return axios({
        method: 'get',
        url: 'http://127.0.0.1:8082/admin',
        headers: {'Authorization': 'Bearer ' + this.$keycloak.token}
      })
    },
    getCustomer() {
      return axios({
        method: 'get',
        url: 'http://127.0.0.1:8082/customer',
        headers: {'Authorization': 'Bearer ' + this.$keycloak.token}
      })
    }
  }
}
</script>

getAdmin()getCustomer()這2個方法內部分別請求restful api

Spring Boot應用集成Keycloak簡明指南

添加Keycloak Maven依賴

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-spring-boot-starter</artifactId>
    <version>10.0.0</version>
</dependency>

Spring Boot配置文件

官方文檔及網上大部分示例使用的都是properties格式的配置文件,而yaml格式的配置文件相對更簡潔清晰些,此示例使用yaml格式的配置文件,內容如下

server:
  port: 8082
keycloak:
  realm: demo
  auth-server-url: http://127.0.0.1:8080/auth
  resource: spring-boot-demo
  ssl-required: external
  credentials:
    secret: 2d2ab498-7af9-48c0-89a3-5eec929e462b
  bearer-only: true
  use-resource-role-mappings: false
  cors: true
  security-constraints:
    - authRoles:
        - ROLE_CUSTOMER
      securityCollections:
        - name: customer
          patterns:
            - /customer
    - authRoles:
        - ROLE_ADMIN
      securityCollections:
        - name: admin
          patterns:
            - /admin

除了幾個必填的配置項外,另外需要注意的幾個配置項如下

credentials.secret:上文添加客戶端后Credentials Tab內對應的內容

bearer-only:設置為true,表示此應用的Keycloak訪問類型是bearer-only

cors:設置為true表示允許跨域訪問

security-constraints:主要是針對不同的路徑定義角色以達到權限管理的目的

  • /customer:只允許擁有ROLE_CUSTOMER角色的用戶才能訪問
  • /admin:只允許擁有ROLE_ADMIN角色的用戶才能訪問
  • 未配置的路徑表示公開訪問

Controller內容

@RestController
public class HomeController {
    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/customer")
    public String customer() {
        return "only customer can see";
    }

    @RequestMapping("/admin")
    public String admin() {
        return "only admin cas see";
    }
}

項目效果演示

分別啟動前後端項目后,本地8081端口對應vue前端項目,本地8082端口對應Spring Boot實現的restful api項目

首次訪問vue前端項目

第一次訪問vue項目會跳轉Keycloak登錄頁

登錄admin用戶

登錄customer用戶

總結

Keycloak部署及接入簡單,輕量的同時功能又不失強大,非常適合企業內部的SSO方案。

本文示例項目地址:keycloak-demo

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

算法講堂一:博弈論入門_租車

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

博弈論的題目有如下特點:

  • 1:博弈模型為兩人輪流決策的博弈。並且兩人都使用最優策略來取得勝利。

    • 兩個玩家,都會採取最優的決策,那麼如果存在一個局面為必勝局面,某玩家位於此局面。只要自己無失誤,則必勝。那麼同樣又一個局面為必敗局面,某玩家位於此局面。只要對手無失誤,則必敗。
    • 那也就是說,針對這樣的遊戲,我們關注點應該在局面上。
  • 2:博弈是有限的。即無論兩人如何決策,都會在有限步決出勝負。

  • 3:博弈是公平的。即兩人進行決策的規則相同。

  • 相關概念:
    • 先手必勝狀態:先手可以從這個狀態走到某一個必敗狀態。
    • 先手必敗狀態:先手走不到任何一個必敗狀態。
    • 也就是說先手必勝狀態,那麼先手一定能採取某些操作,讓後手面對必敗態。如果是先手必敗態,無論先手怎麼操作,都無法讓後手面對必敗態。

簡單博弈的基本題型

1:bash博弈;2:nim博弈;3:威佐夫博弈;5:Fibonacci博弈;6:sg函數;

bash博弈 (巴什博奕)

  • 假設一堆石子有n個,每次最多取m個,甲乙兩個玩家輪流取石子,最後把石子取完的人獲勝,保證甲乙每一步的決策都是最優的,請問給定n和m,問甲勝還是乙勝。

    • 不妨假設剛剛開始

      n = m + 1
      

      ,那麼後手必勝,有如下結論:

      • n = ( m + 1 ) * r + s 其中(r > 1,0 <= s < m + 1)。如果s=0的話,先手每次取k個,後手只要取(m+1-k)個即可,後手必贏。如果s!=0的話,先手者第一次取s個,後手第一次取k個,接下來先手只要取(m + 1 - k)個即可,先手必贏。
      • 所以只需考慮 是否為0就可以判定結果。余為0,先手必敗,反之必勝。
  • 例題:

    • hdu_2188
    #include<bits/stdc++.h>
    using namespace std;
    int c, m, n;//總捐款數,每次最多m
    int main() {
        //freopen("in.txt","r",stdin);
    	cin >> c;
    	while (c--) {
    		cin >> n >> m;
    		if (n % (m + 1) == 0)
    			cout << "Rabbit\n";
    		else
    			cout << "Grass\n";
    	}
    	return 0;
    }
    
  • hdu_1846

  • hdu_1847

Nim遊戲

  • 假設有n堆石子,每堆石子分別有\(a_1,a_2,…,a_n個\),每次可以選擇任意一堆且至少取1枚石子, 甲乙兩個玩家輪流取石子, 最後把石子取完的人獲勝, 保證甲乙每一步的決策都是最優的, 甲為先手操作, 問甲勝還是乙勝。

  • 結論:

    • 設若\(a_1^{ ∧ }a_2^{∧}…^{∧}a_n = 0\)則先手必敗, 反之必勝。
  • 證明

  • \(a\)不全為\(0\)時, 任意一個\(res!=0\)的局面, 先手可以通過一定的操作讓後手面對\(res=0\)的局面。

  • 對於任意一個\(res=0\)的局面, 先手無法通過任何操作讓後手面對\(res=0\)的局面。

  • 得出結論, 當\(res=0\)時先手必敗, 反之必勝。

Nim博弈拓展-台階Nim

  • 問題描述: 有一個\(n\)級台階的樓梯, 每級台階上有若干個石子, 其中第i級台階上有\(ai\)個石子\((i≥1)\)。兩位玩家路輪流操作, 每次操作可以從任意一級台階上拿若干個石子放到下一級台階上(不能不拿)。

  • 已經拿到地面的石子不能再拿, 最後無法進行操作的人視為失敗。

  • 問如果兩人都採取最優策略, 先手是否必勝.

  • 結論
    • \(res=a_1∧a_3∧a_5∧,…,∧a_n=0\)(當然這裏的n是奇數)先手必敗, 反之先手必勝。
  • 證明
  • 1): 考慮極端情況, 當\(a1,a3,…,an\)全為0時, \(res=0\), 此時先手只能將偶數級台階往下搬, 後手只需要將先手從偶數級台階上搬下來的石子全部搬到下一級偶數級台階, 先手必敗。

  • 2): 當\(res=x≠0\)時, 通過經典\(Nim\)遊戲的證明, 我們知道一定有一種方法搬一定的石子到下一級讓後手面對res為0的局面。

  • 3):當\(res=x=0\)\(a\)不全為\(0\)時, 我們無法通過任何操作讓下一個狀態的\(res\)也為\(0\)

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

    日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

  • 即對於\(res\)不為\(0\)的情況, 先手總能通過一定的操作讓後手面對\(res\)\(0\)的情況,。

  • 然而\(res\)\(0\)時, 先手無論做什麼操作都無法讓後手面對\(res\)\(0\)的情況。

  • 那麼此刻我們就將題目轉化為在奇數台階上的經典Nim遊戲。

  • 思考題:

  • 為什麼不用\(res=a_2∧a_4∧a_6∧,…,∧a_n=0\)(n為偶數)來判定勝負?

    • 因為當先手搬去一定的石子讓後手面對res=0res=0的情況, 後手可以搬去一號台階的石子到地面讓先手重新面對res=0res=0的情況
例題:
  • hdu_1850(經典Nim)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 10;
int n, a[maxn], res;
int main() {
    //freopen("in.txt","r",stdin);
	while (cin >> n,n)
	{
		res = 0;
		for (int i = 1; i <= n; i++){
			cin >> a[i];
			res ^= a[i];
		}
		if (res == 0) puts("0");
		else{
			int ans = 0;
			for (int i = 1; i <= n; i++)
				if ((res ^ a[i]) < a[i]) ans++;
			cout << ans << endl;
		}
	}
	return 0;
}
  • hdu_1730(經典Nim)
#include<bits/stdc++.h>
using namespace std;
int main() {
	//freopen("in.txt","r",stdin);
	int n, m;
	while (cin >> n >> m) {
		int res = 0;
		for (int i = 1; i <= n; ++i) {
			int a, b; cin >> a >> b;
			res = res ^ (abs(a - b) - 1);
		}
		if (res == 0) puts("BAD LUCK!");
		else puts("I WIN!");
	}
	return 0;
}
  • poj_1704(台階Nim)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1000 + 10;
int a[N];
int main() {
	//freopen("in.txt", "r", stdin);
	int t, n;
	cin >> t;
	while (t--) {
		int ans = 0;
		cin >> n;
		for (int i = 1; i <= n; ++i) cin >> a[i];
		sort(a + 1, a + n + 1);
		if (n % 2)
		{
			ans ^= (a[1] - 1);
			for (int i = 3; i <= n; i += 2)
				ans ^= (a[i] - a[i - 1] - 1);
		}
		else for (int i = 2; i <= n; i += 2)
			ans ^= (a[i] - a[i - 1] - 1);
		if (ans) printf("Georgia will win\n");
		else printf("Bob will win\n");
	}
	return 0;
}
  • hdu_4315(台階Nim)
#include<bits/stdc++.h> 
using namespace std;
const int maxn = 1e3 + 10;
int a[maxn];
int n, k;
int main() {
    //freopen("in.txt","r",stdin);
    while(cin >> n >> k)
    {
        memset(a, 0, sizeof a);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        if(k == 1) puts("Alice");
        else
        {
            int res = 0;
            if(n & 1)
            {
                if(k == 2) res ^= a[1] - 1;
                else res ^= a[1];
                for(int i = 3; i <= n; i += 2)
                    res ^= a[i] - a[i - 1] - 1;
            }
            else
            {
                for(int i = 2; i <= n; i += 2)
                    res ^= a[i] - a[i - 1] - 1;
            }
            if(res) puts("Alice");
            else puts("Bob");
        }
    }
    return 0;
}

Wythoff 遊戲 (威佐夫博弈)

  • 兩堆石子各有若干個, 兩人輪流從一堆取至少一個石子或從兩堆取同樣多的物品, 最後一名取完石子者勝利。

  • 結論:

    • 當兩堆石子各有\(n\)\(m\)個且不妨設\(n<m\)
    • 當(m−n)(√5+1)/2=n時, 先手必敗。
  • 證明
  • •首先考慮最(zhao)極(gui)端(lv)的情況, (0, 0), (1, 2), (3, 5)局面為先手必敗局面。而且這樣的数字對被稱為奇異局勢。

  • 奇異局勢的定義如下:

    • 設数字對為\(a[(i),b(i)]\)
    • 1:\(a(0)=b(0)=0\);
    • 2: \(a(k)\)是前面数字對中未出現的最小的自然數, 且\(a(k)+k=b(k)\)
  • 接下來我們看奇異局勢的幾個性質:

    • 性質1: 任何自然數都包含在一個且僅有一個奇異局勢中。

    • 性質2: 任意操作都能將奇異局勢轉變為非奇異局勢.

    • 性質3: 採取適當的方法, 可將非奇異局勢轉變為奇異局勢。

      證明略

  • 結論:奇異局勢必敗
例題
  • hdu_2177
#include<bits/stdc++.h> 
using namespace std;
int n, m;
    
bool check(int n, int m) {
    int x = min(n, m), y = max(n, m);
    double c = (sqrt(5.00000) + 1) / 2;
    double d = (double)(y - x);
    if(x == int(c*d)) return 1; // 必敗     return 0;
}
    
void work() {
    if(n > m) swap(n, m); // (n, m)     //第一個模塊 我們能一起減去讓他成為必敗態     {
        int tem = m - n;
        double c = (sqrt(5.00000) + 1) / 2;
        int a = double(tem) * c;
        int b = a + tem;
        if(n - a == m - b) cout << a << " " << b << endl;
    }
    //第二個模塊 我們求出當前n的奇異局勢, 如果m比他大 拿走就行     //如果m比他小我們求出(x, n) 然後拿走m     {
        double c = (sqrt(5.00000) + 1) / 2;
        int x = n;
        double d = x / c;
        int y = n + d;
        if(m > y) cout << x << " " << y << endl;
        else
        {
            double x = double(n) * 2 / (sqrt(5.000000) + 1);
            cout << int(x) << " " << n << endl;
        }
    }
}
    
int main() {
    while(cin >> n >> m)
    {
        if(!(n + m)) break;
        if(check(n, m)) puts("0");
        else
        {
            puts("1");
            work();
        }
    }
    return 0;
}
    

斐波那契博弈(Fibonacci Nim Game)

  • 一堆石子有nn個,兩人輪流取.先取者第1次可以取任意多個,但不能全部取完。以後每次取的石子數不能超過上次取子數的22倍。取完者勝。給定nn,問先手必勝還是必敗。

  • 結論:
    • \(n\)\(fibonacci\)數的時候,先手必敗
  • 證明:

例題:

  • hdu_2516
#include<bits/stdc++.h> using namespace std;
typedef long long ll;
unordered_map<int, int> mp;
ll f[50];
void fib() {
    f[0] = f[1] = 1;
    for(int i = 2; i <= 50; i++)
    {
        f[i] = f[i - 1] + f[i - 2];
        mp[f[i]]++;
    }
}
    
int main() {
    int n;fib();
    while(cin >> n)
    {
        if(n == 0)break;
        if(!mp[n]) puts("First win");
        else puts("Second win"); //如果是fibonacci數, 則先手必敗
    return 0;
}

SG函數

  • \(mex\)運算:
    • 定義\(mex(S)\)為不屬於集合S的最小非負整數運算。
    • •舉個栗子: \(S=1,2,3,mex(s)=0\);
  • SG函數:
    • \(SG\)函數: 設對於每個節點x, 設從x出發有k條有向邊分別到達節點\(y1,y2,…,yk\), 定義SG(x)函數為後繼節點\(y1,y2,…,yk\)的SG函數值構成的集合再執行mex運算的結果。
    • 特別的, 整個有向圖GG的SGSG函數被定義為有向圖起點sSG函數值, 即\(SG(G)=SG(s)\)
    • 有向圖終點的SG函數為0。
  • 結論:
    • •先手必敗, 則該局面對應\(SG函數=0\)。反之必勝。
例題
  • hdu_1524
#include<bits/stdc++.h> using namespace std;
const int maxn = 1e3 + 10;
int n, num;
int sg[maxn];
    
int head[maxn], ver[maxn], nex[maxn], tot;
void add(int x, int y) {
    ver[++tot] = y; nex[tot] = head[x]; head[x] = tot;
}
    
int GetSg(int x) {
    if(sg[x] != -1) return sg[x];
    bool vis[maxn];
    memset(vis, 0, sizeof(vis));
    for(int i = head[x]; i; i = nex[i]) // 掃描所有出邊     {
        int y = ver[i];
        sg[y] = GetSg(y);
        vis[sg[y]] = 1; //所有出邊的sg函數值     }
    for(int i = 0; i < n; i++)
        if(!vis[i]) return sg[x] = i; // mex運算     return 0;
}
    
void init() {
    memset(head, 0, sizeof(head));
    memset(nex, 0, sizeof nex);
    memset(ver, 0, sizeof ver);
    memset(sg, -1, sizeof sg);
    tot = 0;
}
    
int main() {
    while(cin >> n)
    {
        init();
        for(int i = 0; i < n; i++)
        {
            cin >> num;
            while(num--)
            {
                int x; scanf("%d", &x);
                add(i, x);
            }
        }
        while(cin >> num)
        {
            if(!num) break;
            int res = 0;
            while(num--)
            {
                int x; scanf("%d", &x);
                res ^= GetSg(x);
            }
            if(res) puts("WIN");
            else puts("LOSE");
        }
    }
    return 0;
}
  • hdu_1536
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
int s[maxn], sg[maxn];
int k;
    
void init() {
    memset(sg, -1, sizeof(sg));
}
    
int GetSg(int x) {
    if(sg[x] != -1) return sg[x];
    bool vis[maxn]; memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= k; i++)
        if(x >= s[i])
        {
            sg[x - s[i]] = GetSg(x - s[i]);
            vis[sg[x - s[i]]] = 1;
        }
    for(int i = 0; ; i++)
        if(!vis[i]) return sg[x] = i;
    return 0;
    
}
    
int main() {
    ios::sync_with_stdio(false);
    while(cin >> k)
    {
        init();
        if(k == 0) break;
        for(int i = 1; i <= k; i++) cin >> s[i];
        int num; cin >> num;
        while(num--)
        {
            int x, res = 0; cin >> x;
            for(int i = 1; i <= x; i++)
            {
                int y; cin >> y;
                res ^= GetSg(y);
            }
            if(res) cout << "W";
            else cout << "L";
        }
        cout << endl;
    }
    return 0;
}

參考

【博弈論】關於三姬分金(五海盜分贓)的博弈論問題分析

李永樂老師對三姬分金視頻講解

ACM集訓隊講解

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

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

java中的垃圾處理機制_包裝設計

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

1.何為垃圾
在Java中,如果對象實體沒有引用指向的話,存儲該實體的內存便成為垃圾。JVM會有一個系統線程專門負責回收垃圾。垃圾同時包括分配對象內存間的碎片塊

2.垃圾處理包含的算法

Java語言規範沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾回收算法一般要做2件基本的事情:(1)發現無用的信息對象,(2)回收無用對象佔據的內存,使得該內存可以被程序再次使用。
垃圾回收一面在回收內存,一面使堆中的內存緊密排列。下面介紹幾種算法:

2-1引用計數法
該算法使用引用計數器來區分存活對象和不再使用的對象。一般來說,堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變量時,引用計數器置為1。當對象被賦給任意變量時,引用計數器每次加1當對象出了作用域后(該對象丟棄不再使用)或者被置為null時,引用計數器減1,一旦引用計數器為0,對象就滿足了垃圾收集的條件。
基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜必須實時運行的程序。但引用計數器增加了程序執行的開銷,因為每次對象賦給新的變量,計數器加1,而每次現有對象出了作用域,計數器減1。雖然管理引用計數的開銷不大,但是該開銷在整個程序的生命周期

2-2tracing算法(標記-清除)
基於tracing算法的垃圾收集也稱為標記和清除(mark-and-sweep)垃圾收集器,它所依據的思路是,從棧和靜態存儲區出發,遍歷所有的引用,找到存活的對象,每當找到一個存活的對象,就給該對象設一個標記,當標記工作全部完成時,清理工作才會開始。在清理的過程中,沒有標記的對象會被釋放。該方式相當慢,在產生少量垃圾和幾乎不產生垃圾的情況下速度就很快了。

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

2.3. compacting算法(標記-整理)
為了解決堆碎片問題,基於compacting的垃圾回收吸收了tracing算法的思想。在清除無用對象之後,算法將所有的對象移到堆的一端,堆的另一端就變成了一個相鄰的空閑內存區,收集器會對它移動的所有對象的所有引用進行更新,使得這些引用在新的位置能識別原來的對象。解決了內存碎片的問題(不但進行了清理而且進行了對象的搬運,成本更高)。在基於Compacting算法的收集器的實現中,一般增加句柄和句柄表。

2.4. copying算法
它開始時把堆分成一個對象區和多個空閑區,程序從對象區為對象分配空間,當對象滿了,基於coping算法的垃圾回收就掃描活動對象,並將每個活動對象複製到空閑區(使得活動對象所佔的內存之間沒有空閑間隔),這樣空閑區變成了對象區,原來的對象區變成了空閑區,程序會在新的對象區中分配內存。
一種典型的基於coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象區和空閑區域區,在對象區與空閑區域的切換過程中,程序暫停執行。

2.5. generation算法
stop-and-copy垃圾收集器的一個缺陷是收集器必須複製所有的活動對象,這增加了程序等待時間,這是coping算法低效的原因。分代的垃圾回收策略,是基於這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以採取不同的回收算法,以便提高回收效率。generation算法將堆分成兩個或多個,每個子堆作為對象的一代(generation)。1.所有新生成的對象首先都是放在年輕代的。年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。2.在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。3.持久代用於存放靜態文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。

3.System.gc()方法
使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請求Java虛擬機進行垃圾回收,值得注意的是,JVM接受這個消息后,並不是立即做垃圾回收(需要搶佔CPU資源),而只是對幾個垃圾回收算法做了加權,使垃圾回收操作容易發生,或提早發生,或回收較多而已。
盡量避免显示的調用gc,若不針對GC的特點進行設計和編碼,就會出現內存駐留等一系列負面影響。此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。

4. finalize()方法
java垃圾處理器只能釋放哪些經由New出來的對象內存,對於其它途徑產生的內存,java允許在類中定義finalize()方法,在垃圾回收時候調用finalize(),處理內存,雖然不一定發生,但是當垃圾回收動作發生時,非new內存會被清理。finalize中添加一些非java能夠處理的垃圾,例如類似C語言中使用的malloc()函數分配的內存,除非調用free(),否則內存得不到釋放,造成泄露。所以,在finalize方法中調用free()方法,(free是C和C++的方法)。當垃圾回收發生時,finalize()函數被調用。絕對不能直接調用finalize(),因為垃圾回收只與內存有關,無論對象是如何創建的,垃圾回收器都會負責釋放那些對象佔有的內存。
當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。子類重寫 finalize 方法,以配置系統資源或執行其他清除。
java虛擬機在未面臨內存耗盡的情況下,不會浪費時間去執行垃圾回收以恢復內存的。

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

一個騷命令防止你的文件被誤刪除!_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

大家好,我是良許。

文件是我們在電腦里最珍貴的財富,我們經常工作了老半天,然後出來的成果就只是一個文件而已。特別是程序員,我們寫了半天的代碼,結果也就是一個個代碼文件而已。

但是,我們都有過這樣慘痛的經歷——工作了老半天,結果不小心把文件刪除了……

這時候,你的心情是怎樣的?痛苦?自責?無奈?

今天良許就給大家介紹一個命令,它將給你的重要文件加一把鎖,防止誤刪除或誤修改,為你的寶貝文件保駕護航!

chattr命令簡介

這裏我們需要使用到一個命令是:chattr ,這個命令在大部分的 Linux 發行版里都有,所以對於它的安裝就不贅述了。它的基本用法如下:

$ chattr 操作符 屬性 文件名

對於操作符,有以下三種:

  • + :給文件增加屬性
  • - :去除文件屬性
  • = :設置文件的僅有屬性

注意,這裏講的屬性不是文件對應的系統屬性,而是 chattr 給文件賦予的屬性。本文要講的屬性有以下兩個:

  • a – 允許給文件追加內容
  • i – 保護模式(不允許刪除或修改)

但是,它的屬性可以設置很多,有興趣的小夥伴可以去看下它的 man 手冊。

$ man chattr

防止文件被誤刪除或修改

假如我們現在有個國寶級重要文件 file.txt ,現在我們使用 chattr 對它進行保護。在這裏,我們給文件增加 +i 屬性:

$ sudo chattr +i file.txt

然後,我們可以使用 lsattr 命令查看它的屬性。類似的輸出如下:

$ lsattr file.txt
----i---------e---- file.txt

現在,我們來嘗試一下,手賤去刪除那個文件:

$ rm file.txt
rm: cannot remove 'file.txt': Operation not permitted

咦?不允許刪除?難道權限不夠?

那好,我 sudo 一下!

$ sudo rm file.txt
rm: cannot remove 'file.txt': Operation not permitted

我 X ,居然還是不能刪除?

我們再來試一下,修改文件的內容。

$ echo 'hello world!' >> file.txt
bash: file.txt: Operation not permitted

可以看出來,依然不能對文件進行修改了。

而且,即使你從 GUI 界面手動去刪除這個文件,也還是不能耐他幾何。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

所以,可以看出來,現在這個文件得到了很好的保護,既不能被刪除(各種方法都不行),也不能被修改。

那我們要怎麼去除這個保護呢?很簡單,只需加上 -i 這個選項。

$ sudo chattr -i file.txt

現在,文件又恢復原樣了,我們想修改就修改,想刪除就刪除。

$ echo 'Hello World!' >> file.txt
$ cat file.txt
Hello World!
$ rm file.txt

防止文件夾被誤刪除或修改

上面講到的是保護文件,那麼文件夾要如何保護呢?

其實也是一樣,使用 +i 這個選項。假如我們現在有個 dir1 目錄,裏面有個 file.txt 文件。我們來對這個文件夾進行保護。

$ sudo chattr -R +i dir1

在這裏,我們使用 -R 選項表示可以遞歸作用到目錄里所有的文件(包括子目錄)。

現在,我們同樣測試一下是否可以被刪除或修改。

$ rm -rf dir1
$ sudo rm -rf dir1
rm: cannot remove 'dir1/file.txt': Operation not permitted
$ echo 'hello world!' >> dir1/file.txt
bash: file.txt: Operation not permitted

所以與文件一樣,我們成功地對文件夾進行了保護。

防止文件/目錄被刪除,但允許追加內容

現在我們知道怎麼防止文件/目錄被誤刪除或修改了,但是,假如我們不想要文件已有內容被修改,但允許別人在文件末尾追加內容,要怎麼操作?

這時候我們就需要使用 +a 這個選項了。

對文件:

$ sudo chattr +a file.txt

對目錄:

$ sudo chattr -R +a dir1

現在,我們來確認一下,文件是否可以被追加內容。

$ echo 'Hello World!' >> file.txt
$ echo 'Hello World!' >> dir1/file.txt

我們再使用 cat 命令去查看一下內容:

$ cat file.txt
Hello World!
$ cat dir1/file.txt
Hello World!

可以看出來,文件都是可以被追加的。

但是,file.txt 還有 dir1/file.txt 依然不能被刪除。

如果你想去掉可追加的屬性,可以使用 -a 這個選項。

對文件:

$ sudo chattr -R -a file.txt

對目錄:

$ sudo chattr -R -a dir1/

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

“造輪運動”之 ORM框架系列(一)~談談我在實際業務中的增刪改查_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

  想想畢業已經快一年了,也就是大約兩年以前,懷着滿腔的熱血正式跨入程序員的世界,那時候的自己想象着所熱愛的技術生涯會是多麼的豐富多彩,每天可以與大佬們坐在一起討論解決各種牛逼的技術問題,喝着咖啡,翹着二郎腿,大致就是下面這幅場景:

  

 

  可是現實卻總是那麼不盡如人意,現實的所謂技術生涯是永遠寫不完的增刪改查,還有那日漸稀薄的頭髮,哎,難…

  

 

    話說回來,如果連增刪改查都做不好,還談何技術生涯。

     所以,我一直認為:如果你認為自己是一個優秀的程序員,那就應該從最基礎的增刪改查中就能體現出來

     有人可能會說,增刪改查?很難嗎?

     其實,說難,也不難,無非就是寫SQL,Add、Delete、Update、Select,僅此而已;但是如果只是從SQL的角度認為這很容易,而對其不懈一顧,那你可能就和當初剛剛開始增刪改查大業的我一樣,太天真了。

    首先,我和大家分享一下我在工作這將近兩年中在增刪改查這條路上的心路歷程。

 

一、初出茅廬——原生sql走天下,sql寫到手抽筋

    某年某月某日,大三尾聲,翹課在宿舍睡大覺的我剛從床上艱難地爬起來,打開手機,發現剛工作的老學長阿威給我發來了一條信息:

   “阿森,來活了,幫我搞個網站,會弄嗎?”

   “網站?沒弄過誒。。”

     “這個項目給的銀兩還挺多的。。”

   “可以,可以,這個可以搞,沒問題,阿威哥”

     

  就這樣,我就走上了Web開發的道路。初入坑,首先遇到的問題就是數據庫的操作,但是經過自己的旁門左道的學習也大致摸清了使用ADO.NET操作數據庫的方法:

  第一步,使用DBConnection建立與數據庫之間的連接,打開連接

  第二步,然後創建DBCommand對象,初始化你想要執行的SQL語句

  第三步,調用DBCommand的方法,執行SQL語句

  第四步,使用DataReader逐條讀取sql執行的結果,或者使用DataAdapter類將結果填充(Fill)到DataSet中,最後關閉Connection連接

  到此為止,就實現了在.net中操作數據庫執行sql語句返回執行結果的操作。

  當然,我覺得任何一個初級的程序員也都會給自己封裝一個叫做SQLHelper的幫助類,我也不例外,它可以幫助你節省頻繁的建立數據庫連接(DBConnection)、初始化DBCommand對象、讀取sql執行結果等重複的代碼量。所以,封裝一個自己的SQLHelper工具類是一個初級程序員必備的素質。

  我把一個當初自己封裝的SQLHelper類貼出來獻獻醜:

   

  1     public static class SqlServerHelper
  2     {
  3         //數據庫連接字符串,從配置文件的配置字段中讀取
  4         private static readonly string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
  5 
  6         /// <summary>
  7         /// 創建新的數據庫連接
  8         /// </summary>
  9         public static SqlConnection CreateSqlConnection()
 10         {
 11             SqlConnection conn = new SqlConnection(connStr);
 12 
 13             conn.Open();   //打開數據庫連接
 14 
 15             return conn;
 16         }
 17 
 18         /// <summary>
 19         /// 執行無結果返回的sql語句(共用同一個連接)
 20         /// </summary>
 21         /// <param name="sqlConn"></param>
 22         /// <param name="sqlStr"></param>
 23         /// <param name="sqlParamters"></param>
 24         /// <returns>返回受影響的數據總條數</returns>
 25         public static long ExecuteNoQuery(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
 26         {
 27             using (SqlCommand cmd = sqlConn.CreateCommand())
 28             {
 29                 cmd.CommandText = sqlStr;
 30                 cmd.Parameters.AddRange(sqlParamters);
 31 
 32                 long sum = cmd.ExecuteNonQuery();
 33 
 34                 return sum;
 35             }
 36         }
 37 
 38         /// <summary>
 39         /// 執行無結果返回的sql語句(不共用一個連接)
 40         /// </summary>
 41         /// <param name="sqlStr"></param>
 42         /// <param name="sqlParamters"></param>
 43         /// <returns>返回受影響的數據總條數</returns>
 44         public static long ExecuteNoQuery(string sqlStr, params SqlParameter[] sqlParamters)
 45         {
 46             using (SqlConnection sqlConn = CreateSqlConnection())
 47             using (SqlCommand cmd = sqlConn.CreateCommand())
 48             {
 49                 cmd.CommandText = sqlStr;
 50                 cmd.Parameters.AddRange(sqlParamters);
 51 
 52                 long sum = cmd.ExecuteNonQuery();
 53 
 54                 return sum;
 55             }
 56         }
 57 
 58         /// <summary>
 59         /// 執行只有一行一列返回數據的sql語句(共用同一個連接)
 60         /// </summary>
 61         /// <param name="sqlConn"></param>
 62         /// <param name="sqlStr"></param>
 63         /// <param name="sqlParamters"></param>
 64         /// <returns>返回結果的第一行第一列數據</returns>
 65         public static object ExecuteScalar(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
 66         {
 67             using (SqlCommand cmd = sqlConn.CreateCommand())
 68             {
 69                 cmd.CommandText = sqlStr;
 70                 cmd.Parameters.AddRange(sqlParamters);
 71 
 72                 object res = cmd.ExecuteScalar();
 73 
 74                 return res;
 75             }
 76         }
 77 
 78         /// <summary>
 79         /// 執行只有一行一列返回數據的sql語句(不共用同一個連接)
 80         /// </summary>
 81         /// <param name="sqlStr"></param>
 82         /// <param name="sqlParamters"></param>
 83         /// <returns>返回結果的第一行第一列數據</returns>
 84         public static object ExecuteScalar(string sqlStr, params SqlParameter[] sqlParamters)
 85         {
 86             using (SqlConnection sqlConn = CreateSqlConnection())
 87             using (SqlCommand cmd = sqlConn.CreateCommand())
 88             {
 89                 cmd.CommandText = sqlStr;
 90                 cmd.Parameters.AddRange(sqlParamters);
 91 
 92                 object res = cmd.ExecuteScalar();
 93 
 94                 return res;
 95             }
 96         }
 97 
 98         /// <summary>
 99         /// 執行有查詢結果的sql語句(共用同一個連接)
100         /// </summary>
101         /// <param name="sqlConn"></param>
102         /// <param name="sqlStr"></param>
103         /// <param name="sqlParamters"></param>
104         /// <returns></returns>
105         public static DataTable ExcuteQuery(SqlConnection sqlConn, string sqlStr, params SqlParameter[] sqlParamters)
106         {
107             using (SqlCommand cmd = sqlConn.CreateCommand())
108             {
109                 cmd.CommandText = sqlStr;
110 
111                 cmd.Parameters.AddRange(sqlParamters);
112 
113                 //把sql語句的執行結果填充到SqlDataAdapter中
114                 SqlDataAdapter adapter = new SqlDataAdapter(cmd);
115 
116                 DataTable dt = new DataTable();
117                 adapter.Fill(dt);    //將執行結果填充到dt對象中
118 
119                 return dt;
120             }
121         }
122 
123         /// <summary>
124         /// 執行有查詢結果的sql語句(共用同一個連接)
125         /// </summary>
126         /// <param name="sqlStr"></param>
127         /// <param name="sqlParamters"></param>
128         /// <returns></returns>
129         public static DataTable ExcuteQuery(string sqlStr, params SqlParameter[] sqlParamters)
130         {
131             using (SqlConnection sqlConn = CreateSqlConnection())
132             using (SqlCommand cmd = sqlConn.CreateCommand())
133             {
134                 cmd.CommandText = sqlStr;
135 
136                 cmd.Parameters.AddRange(sqlParamters);
137 
138                 //把sql語句的執行結果填充到SqlDataAdapter中
139                 SqlDataAdapter adapter = new SqlDataAdapter(cmd);
140 
141                 DataTable dt = new DataTable();
142                 adapter.Fill(dt);    //將執行結果填充到dt對象中
143 
144                 return dt;
145             }
146         }
147 
148         /// <summary>
149         /// 批量插入數據到數據庫
150         /// </summary>
151         /// <param name="insertTable"></param>
152         /// <param name="dataTableName"></param>
153         public static void BatchInsert(DataTable insertTable, string dataTableName)
154         {
155             using (SqlBulkCopy sbc = new SqlBulkCopy(connStr))
156             {
157                 sbc.DestinationTableName = dataTableName;
158 
159                 for (int i = 0; i < insertTable.Columns.Count; i++ )
160                 {
161                     sbc.ColumnMappings.Add(insertTable.Columns[i].ColumnName, insertTable.Columns[i].ColumnName);
162                 }
163 
164                 sbc.WriteToServer(insertTable);
165             }
166         }
167 
168     }

SQLHelper

    於是在那段開發的歲月里,是這個SQLHelper類陪我走完了全程,向它說聲辛苦了… 

 

二、EF框架真香,Lambda表達式我最愛

  大三暑假,由於抑制不住想要出去試一試的衝動,我來到了一家小型互聯網公司實習,公司也是用.net開發網站的技術棧。

   當上崗的第一天,我準備掏出我自認為牛逼的SQLHelper的時候,卻被項目經理叫停了,項目經理阿勇對我說:

   “小伙子,21世紀了,還在傻傻地用SQLHelper?來,試試EF吧,讓你欲罷不能…”

   於是,一臉懵逼的我,一頭埋進了EF的世界中,不得不說,的確香。

   EF全名EntityFramework,是微軟官方的一款ORM框架,支持Object(對象)與數據庫之間的數據映射,支持Linq的操作語法,受廣大.NET程序員青睞。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

   其實在接觸EntityFramework之前我就使用過Dapper,Dapper相對與EF來說是輕量的多的一款ORM框架,近乎於原生sql的寫法,讓它處於性能之最,也提供對象與數據庫表之間的映射。

   但正是由於用過了EF,我才知道了原來還有 Lambda to SQL 這種東西,相信大多數程序員都和我一樣經歷過寫原生SQL的痛苦

   無論是多麼簡單的一條sql,你都要在表設計文檔和sql編輯器中反覆切換比對字段,生怕自己打錯一個字母;而且我們在基礎的業務中的大部分增刪改查其實都是相當簡單的單表操作sql,正是使用了Lambda to SQL 的代碼編寫方式,你可以在VsStudio豐富的代碼提示下飛快地完成一條簡單的SQL語句。

   為什麼說簡單的SQL編寫才使用Lambda表達式的形式去編寫呢,複雜的sql就不行了嗎?

   理由是,你要明白不管是Lambda還是Linq的語句,最終都是在EF的SQL語句解析器中被翻譯成可供數據庫執行的sql語句,不得不說EF的Linq To SQL 的解析器的確強大,但是畢竟還是機器翻譯的sql,當你讓它給你轉化一個複雜的linq語句時,它是一定考慮不到sql性能的優化。

   就像你去用金山詞霸翻譯一個單詞,它可以給你翻譯的很準確;但是你讓它去幫你翻譯一個長句,可能你讀着就覺得彆扭了;更有甚者把一篇論文粘貼複製到金山詞霸里,那可能比你直接看英文原文還費勁,這是同樣一個道理,有些東西還是需要人工來做,就比如優化sql語句。不能奢求什麼都扔給Lambda和Linq,它幫我們做的已經足夠多了。

   總之一句話,Lambda很香,但絕對不能替代SQL

 

三、全部都是存儲過程,調試太難

  畢業后的我來到了一家大型製造業企業從事IT行業,說的這麼體面,其實還是一個苦逼敲代碼的。

   上崗的第一天我就問旁邊的程序媛,對,你沒看錯,是“媛”,不是“猿”,嘿嘿。想我代碼生涯一年以來,第一次見這麼多程序媛,低調低調,不要聲張,不然讓隔壁的程序猿聽到了會嫉妒的。

   

  “姐姐,我們這用EF嗎?”

   “EF?是啥…”,姐姐一臉懵逼。。。

   “那你們用什麼操作數據庫啊?自己封裝SQLHelper嗎?”

   “用存儲過程啊,都用了7、8年了”

   “哦,這樣啊”,我心想,都用了7、8年了,這個姐姐是個狠人。

   入鄉隨俗,咱也不能壞了規矩,那就照着用吧,存儲過程以前用過幾次,但是那是在sql的邏輯比較複雜的時候才會寫存儲過程調用的,要是所有的sql都放在存儲過程裏面去調用的話,那就太繁瑣了吧。

   經過一段時間的使用,證明我的顧慮是對的。

   全部使用存儲過程后,發現,就是一條簡單的Delete、Insert語句都要寫在存儲過程中,簡直不要太麻煩。

   首先的問題就是,不好編寫,對於一個剛上手存儲過程語法的程序員來說這簡直就是災難,目前的SQL編輯器沒有語法提示功能而造成非常不好的代碼編寫體驗,嚴重拖慢開發進程;

   更要命的是,太難調試!當你在c#代碼中調用完存儲過程后,你發現程序安然無恙地執行完,但是並沒有得到你想要的結果,於是你找啊找,找啊找,最後,你只能懷疑是不是存儲過程有問題,但是運行過程中存儲過程一點動靜也沒有啊,這就是使用存儲過程最大的弊端,悄無聲息地出錯,完全不知道裏面發生了什麼,只能默默地把存儲過程的參數記下,然後輸入到sql的調試其中執行一遍才可以發現錯誤。

   然後你的項目經理跑過來問你昨天的代碼敲完了嗎,心累…

   於是,我對存儲過程留下了非常不好的印象。但是存儲過程的好處是顯而易見的:當你的業務邏輯用一條sql解決不了的時候你會考慮使用多條sql來配合完成查詢,可是如果執行每一條sql語句都要與數據庫建立連接然後再執行的話好像有點繁瑣不說,主要是重建數據庫連接的過程也是需要時間與資源的,所以這個時候存儲過程就派上用場了,把你的sql都扔到存儲過程裏面去吧,多麼令人心曠神怡的操作,這個時候你就不要去想存儲過程出錯難找的問題了,因為你想要獲得方便總要有點犧牲精神。

 

四、沒有哪一種是完全適合自己的,如果有,那就是自己造的

  想要有Dapper的神速,又想要有EF中使用lambda表達式的便捷,但是二者好像並不能兼得。

   Dapper雖然是效率之王,但是它沒有EF中可以使用的便捷的lambda表達式;

   EF雖然可以使用便捷的lambda、linq語法,可是由於EF的過分強大(EF還具有對象追蹤的功能),讓它顯得稍微有點臃腫,屬於重量級的ORM框架。

   於是我就在想,有沒有一款輕量級的ORM,既有不低的執行效率,又有簡單實用的Lambda TO SQL?

   答案當然是,有的,現在開源的國產sqlSuger、freeSql,比比皆是。但是,我就是想自己試試造輪子的感覺,目的並不是說造出多麼好的ORM框架去超越他們,而是在這個過程中學到的東西和得到的歷練是非常可觀的。

 

   沒辦法,有觀眾說“褲子都脫了,就給我看這個?”,我決定還是先把藏的貨先擺出來,也好有個交代…

   源碼地址:https://gitee.com/xiaosen123/CoffeeSqlORM

   本文為作者原創,轉載請註明出處:https://www.cnblogs.com/MaMaNongNong/p/12884871.html

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

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

這幾款應該是SUV里最保值的 而且優惠普遍達到2萬_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

18-26。78萬元優惠幅度:2-3萬元(僅供參考)兩大日系車都推出了城市SUV,後知后覺的日產也在00年推出自家的城市SUV車型X-TRAIL,並且01-10年連續十年獲得日本SUV銷量冠軍,實力不容小覷。在二手車市場里,11年的第二代奇駿,跑了近7萬多公里后依然能夠賣出接近13萬的價格,算是很保值的了。

對於那些炸天的富二代來說,買車當然是性能第一,但是對於我們普通的老百姓來說,一輛便宜、品質好的車才是最佳選擇。有沒有一些車優惠比較大,然後保值率又足夠高,到下次換車的時候還能賣個好價錢的?這肯定是有的,想要買既優惠又保值的SUV嗎?馬上給你介紹。

東風本田-CRV

指導價:17.98-24.98萬元

優惠幅度:1.8萬元(僅供參考)

本田CR-V的起源要追溯到96年的第一代CR-V,但CR-V真正火起來,是07年的第三代CR-V上市,一時間,等車、加價,開始走入了消費者的生活。在一些二手車網站發現,12年上牌,10萬公里的CR-V,商家依然敢報價14.98萬,保值率高得驚人啊。

第四代的CR-V在15年迎來了中期改款,主要的變化在於中網的造型,那塊碩大的亮黑色飾板辨識度還是頗高的。大燈也進行了升級,現在我們可以在頂配車型找到氙氣近光燈和LED日行燈。和07年那時不同,現在CR-V的外觀在云云眾車中已經漸顯中庸,新意相對少了。

CR-V的內飾布局還是熟悉的味道,變速箱依然是在靠近中控台的位置,給扶手箱前方留下不少的儲物空間。做工方面是值得肯定的,本田一直被詬病使用過多的塑料,而在這一款的CR-V中,副駕前方就加入了一大片的軟質內飾材料,還有縫線的工藝,質感是有所提升的。

CR-V採用2.0L和2.4L的發動機,搭配CVT無級變速箱。只有2.4L版本是本田地球夢發動機,最大馬力達到186ps,這個參數對於一輛SUV來說不算特別的出色,但實際的駕駛感受沒有讓失望,起步輕快、直接,沒有很明顯的拖沓。自吸加CVT的動力系統,主打的就是平順,只要逐步的加深油門,CR-V能給你持續的加速感。

底盤的質感很溫柔,同時又能夠快速的過濾掉路面的顛簸,給人很舒適的乘坐感受。而操控就更加接近轎車,即使重心高了,側傾的現象也不是很明顯,彎道循跡性很好。

一汽豐田-Rav4榮放

指導價:17.98-26.98萬

優惠幅度:1萬(僅供參考)

早在1994年,第一代Rav4就首次在日本銷售,隨後的市場表現也十分出色,真的無愧於“城市SUV”這一稱號。當然,以可靠性聞名的豐田,該品牌下的車子保值率自然很高,一輛有5年車齡,開了11萬公里的Rav4,現在還能輕輕鬆松的賣到12萬,也是真不虧了。

今年新上市的Rav4,除了外觀進行了調整,加入了豐田“Keen Look”的設計元素以外,在內飾和動力方面都進行了一些調整。Rav4的造型和車身線條營造出一種年輕化、運動化的效果,也這也是豐田對Rav4的定位。

內飾在造型和布局上的變化不大,分上下層式的設計,上層採用了硬質塑料材質,下層則使用皮革材料,觸感不錯。除此之外,配置的提升也很重要,全系標配了車身穩定系統、前排側氣囊和頭部氣簾等等,安全性能有所提高。

動力方面,2.0L版本的發動機是升級了的,馬力比上一款車型提升了4ps,但是實際駕駛的感受並沒有明顯的提升。原本Rav4搭載的這套動力系統就比較輕快的,2.0L的變速箱是CVT,2.5L的變速箱是AT,

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

動力輸出是以平順為主。

至於操控方面,Rav4的方向盤並不靈敏,力度比較輕,但出彎時還是會有點粘手,整體轉向的質感不夠乾脆。至於懸挂,支撐是十分到位的,操控信心很足,作為家用車的話就會過硬了,坐過Rav4的後排,能感受到經過坑窪時會有明顯的跳動,舒適感遠不如CR-V啊。

東風日產-奇駿

指導價:18.18-26.78萬元

優惠幅度:2-3萬元(僅供參考)

兩大日系車都推出了城市SUV,後知后覺的日產也在00年推出自家的城市SUV車型X-TRAIL,並且01-10年連續十年獲得日本SUV銷量冠軍,實力不容小覷。在二手車市場里,11年的第二代奇駿,跑了近7萬多公里后依然能夠賣出接近13萬的價格,算是很保值的了。

從初代奇駿到現在的最新款奇駿,我們可以看到該車的外觀從平直硬朗的線條一路演變到現在的流暢立體的車身外觀。前臉是毫無疑問的加入了V-motion的設計了,除此以外,引擎蓋、腰線、輪拱等位置的複雜線條,讓奇駿看起來更加的有立體感,這車的外觀雖然說不上超級美,但還是十分耐看。

內飾方面其實大有向天籟看起的趨勢,用料方面主要是硬質的塑料,這跟同級別的多數日系車相似,但做工卻毫不含糊,絲毫沒有低檔的感覺。配置方面,除了低配車型的中控屏面子偏小以外,平時常用的功能基本都齊全。

動力方面則是日產的兩套經典的動力系統,2.0L或者2.5L加手動擋活CVT的變速箱。看上去並不出彩的2.0L發動機能提供150ps馬力,實際上表現卻不差,日常駕駛很輕快,而且發動機並沒有煩人的嘶吼聲。配合CVT變速箱,動力平順且有勁,維持了日產的一貫作風。

別看奇駿體積較大,其方向盤用起來虛位並不誇張,且阻尼適中並伴有一定的路感反饋,一定程度提升了駕駛樂趣。至於底盤就還是那樣的舒適,對一些小顛簸的過濾是很淡定自如的,只是過一些大減速帶時,彈跳就會有點多,緊緻度還是不足啊。

長安馬自達-CX5

指導價:16.98-24.58萬元

優惠幅度:1.9萬元(僅供參考)

馬自達,算是對市場不太敏銳的一家廠商,喜歡做他們認為好的東西(實際也是很好),而不是最迎合市場的東西。同樣定位城市SUV的CX-5,也是13年6月才上市的年輕車型。至於保值率,13款跑了6萬公里的CX-5,依然能賣15.5萬元。

外觀方面,魂動的外觀設計也是夠出色,車身線條十分流暢,並不會找到一些不協調的元素出來。外型上追求年輕、運動,配合紅色的主色調,作為年輕人的也會忍不住多看幾眼。

內飾方面,CX-5在原本全黑的基礎上加入了一些鍍鉻飾條作裝飾,恰到好處的讓其更顯年輕。用料方面當然主要是塑料的了,大體的做工是不錯的,只是細節方面的處理還是有較大的進步空間。採用了為第二代MZD CONNECT悅聯繫統,多媒體功能更加豐富了。

動力方面,2.0L或2.5L的創馳藍天發動機分別提供155ps和196ps的馬力,即使是2.0L的動力其實也足夠市區通勤的使用。但更加讓拍掌的,是那個6AT變速箱,無論是換擋邏輯、換擋速度還是平順性,都是同級別的頂級水平。這套動力系統可以讓你在自動擋的車裡感受人車合一的感覺。

至於行駛品質,馬自達也沒有含糊,底盤的質感是比較紮實穩重的,舒適性良好,同時實測在120km/h時的噪音值為65.6分貝,也是較舒適的範圍。CX-5還是偏向於一輛舒適型的家用SUV,只是其綜合表現也是實在優秀。

為什麼說這四輛車優惠大?因為其處於當代車型的中期,像CR-V,新一代車型已經發布,換代只是時間問題。這些有一定時間的車型價格一般都很少水分。可靠性良好且價格優惠,它們確實是你買了不會後悔的車型。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。