CTR學習筆記&代碼實現6-深度ctr模型 後浪 xDeepFM/FiBiNET_網頁設計公司

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

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

xDeepFM用改良的DCN替代了DeepFM的FM部分來學習組合特徵信息,而FiBiNET則是應用SENET加入了特徵權重比NFM,AFM更進了一步。在看兩個model前建議對DeepFM, Deep&Cross, AFM,NFM都有簡單了解,不熟悉的可以看下文章最後其他model的博客鏈接。

以下代碼針對Dense輸入更容易理解模型結構,針對spare輸入的代碼和完整代碼
https://github.com/DSXiangLi/CTR

xDeepFM

模型結構

看xDeepFM的名字和DeepFM相似都擁有Deep和Linear的部分,只不過把DeepFM中用來學習二階特徵交互的FM部分替換成了CIN(Compressed Interactino Network)。而CIN是在Deep&Cross的DCN上進一步改良的得到。整體模型結構如下

我們重點看下CIN的部分,和paper的notation保持一致,有m個特徵,每個特徵Embedding是D維,第K層的CIN有\(H_k\)個unit。CIN第K層的計算分為3個部分分別對應圖a-c:

  1. 向量兩兩做element-wise product, 時間複雜度\(O(m*H_{k-1}*D)\)
    對輸入層和第K-1層輸出,做element-wise乘積進行兩兩特徵交互,得到\(m*H_{k-1}\)個D維向量矩陣,如果CIN只有一層,則和FM, NFM,AFM的第一步相同。FM 直接聚合成scaler,NFM沿D進行sum_pooling,而AFM加入Attention沿D進行weighted_pooling。忽略batch的矩陣dimension變化如下

\[z^k = x^0 \odot x^{k-1} = (D * m* 1) \odot (D * 1* H_{k-1}) = D * m*H_{k-1} \]

  1. Feature Map,空間複雜度\(O(H_k *H_{k-1} *m)\),時間複雜度\(O(H_k *H_{k-1} *m*D)\)
    \(W_k \in R^{H_{k-1}*m *H_k}\) 是第K層的權重向量,可以理解為沿Embedding做CNN。每個Filter對所有兩兩乘積的向量進行加權求和得到 \(1*D\)的向量 一共有\(H_k\)個channel,輸出\(H_k * D\)的矩陣向量。

\[w^k \bullet z^k = (H_k *H_{k-1} *m)* (m*H_{k-1}*D) = H_k *D \]

  1. Sum Pooling
    CIN對每層的輸出沿Dimension進行sum pooling,得到\(H_k*1\)的輸出,然後把每層輸出concat以後作為CIN部分的輸出。

CIN每一層的計算如上,T層CIN每一層都是上一次層的輸出和第一層的輸入進行交互得到更高一階的交互信息。假設每層維度一樣\(H_k=H\), CIN 部分整體時間複雜度是\(O(TDmH^2)\),空間複雜度來自每層的Filter權重\(O(TmH^2)\)

CIN保留DCN的任意高階和參數共享,兩個主要差別是

  • DCN是bit-wise,CIN是vector-wise。DCN在做向量乘積時不區分Field,直接對所有Field拼接成的輸入(m*D)進行外積。而CIN考慮Field,兩兩vector進行乘積
  • DCN使用了ResNet因為多項式的核心只用輸出最後一層,而CIN則是每層都進行pooling后輸出

CIN的設計還是很巧妙滴,不過。。。吐槽小分隊上線: CIN不論是時間複雜度還是空間複雜度都比DCN要高,感覺更容易過擬合。至於說vector-wise的向量乘積要比bit-wise的向量乘積要好,這。。。至少bit-wise可以不限制embedding維度一致, 但vector-wise嘛我實在有些理解無能,明白的童鞋可以comment一下

代碼實現

def cross_op(xk, x0, layer_size_prev, layer_size_curr, layer, emb_size, field_size):
    # Hamard product: ( batch * D * HK-1 * 1) * (batch * D * 1* H0) -> batch * D * HK-1 * H0
    zk = tf.matmul( tf.expand_dims(tf.transpose(xk, perm = (0, 2, 1)), 3),
                    tf.expand_dims(tf.transpose(x0, perm = (0, 2, 1)), 2))

    zk = tf.reshape(zk, [-1, emb_size, field_size * layer_size_prev]) # batch * D * HK-1 * H0 -> batch * D * (HK-1 * H0)
    add_layer_summary('zk_{}'.format(layer), zk)

    # Convolution with channel = HK: (batch * D * (HK-1*H0)) * ((HK-1*H0) * HK)-> batch * D * HK
    kernel = tf.get_variable(name = 'kernel{}'.format(layer),
                             shape = (field_size * layer_size_prev, layer_size_curr))
    xkk = tf.matmul(zk, kernel)
    xkk = tf.transpose(xkk, perm = [0,2,1]) # batch * HK * D
    add_layer_summary( 'Xk_{}'.format(layer), xkk )
    return xkk


def cin_layer(x0, cin_layer_size, emb_size, field_size):
    cin_output_list = []

    cin_layer_size.insert(0, field_size) # insert field dimension for input
    with tf.variable_scope('Cin_component'):
        xk = x0
        for layer in range(1, len(cin_layer_size)):
            with tf.variable_scope('Cin_layer{}'.format(layer)):
                # Do cross
                xk = cross_op(xk, x0, cin_layer_size[layer-1], cin_layer_size[layer],
                              layer, emb_size, field_size ) # batch * HK * D
                # sum pooling on dimension axis
                cin_output_list.append(tf.reduce_sum(xk, 2)) # batch * HK

    return tf.concat(cin_output_list, axis=1)

@tf_estimator_model
def model_fn_dense(features, labels, mode, params):
    dense_feature, sparse_feature = build_features()
    dense_input = tf.feature_column.input_layer(features, dense_feature)
    sparse_input = tf.feature_column.input_layer(features, sparse_feature)

    # Linear part
    with tf.variable_scope('Linear_component'):
        linear_output = tf.layers.dense( sparse_input, units=1 )
        add_layer_summary( 'linear_output', linear_output )

    # Deep part
    dense_output = stack_dense_layer( dense_input, params['hidden_units'],
                               params['dropout_rate'], params['batch_norm'],
                               mode, add_summary=True )
    # CIN part
    emb_size = dense_feature[0].variable_shape.as_list()[-1]
    field_size = len(dense_feature)
    embedding_matrix = tf.reshape(dense_input, [-1, field_size, emb_size]) # batch * field_size * emb_size
    add_layer_summary('embedding_matrix', embedding_matrix)

    cin_output = cin_layer(embedding_matrix, params['cin_layer_size'], emb_size, field_size)

    with tf.variable_scope('output'):
        y = tf.concat([dense_output, cin_output,linear_output], axis=1)
        y = tf.layers.dense(y, units= 1)
        add_layer_summary( 'output', y )

    return y

FiBiNET

模型結構

看FiBiNET前可以先了解下Squeeze-and-Excitation Network,感興趣可以看下這篇博客Squeeze-and-Excitation Networks。

FiBiNET的主要創新是應用SENET學習每個特徵的重要性,加權得到新的Embedding矩陣。在FiBiNET之前,AFM,PNN,DCN和上面的xDeepFM都是在特徵交互之後才用attention, 加權等方式學習特徵交互的權重,而FiBiNET在保留這部分的同時,在Embedding部分就考慮特徵自身的權重。模型結構如下

原始Embedding,和經過SENET調整過權重的新Embedding,在Bilinear-interaction層學習二階交互特徵,拼接后,再經過MLP進一步學習高階特徵。和paper notation保持一致(啊啊啊大家能不能統一下notation搞的我自己看自己的註釋都蒙圈),f個特徵,k維embedding

SENET層

SENET層學習每個特徵的權重對Embedding進行加權,分為以下3步

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

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

  1. Squeeze
    \(f*k\)的Embedding矩陣壓縮成\(f*1\), 壓縮方式不固定,SENET原paper用的max_pooling,作者用的sum_pooling,感覺這裏壓縮方式應該取決於Embedding的信息表達

\[\begin{align} E &= [e_1,…,e_f] \\ Z &= [z_1,…,z_f] \\ z_i &= F_{squeeze}(e_i) = \frac{1}{k}\sum_{i=1}^K e_i \\ \end{align} \]

  1. Excitation
    Excitation是一個兩層的全連接層,通過先降維再升維的方式過濾一些無用特徵,降維的幅度通過額外變量\(r\)來控制,第一層權重\(W_1 \in R^{f*f/r}\),第二層權重\(W_2 \in R^{f/r*f}\)。這裏r越高,壓縮的幅度越高,最終的權重會更集中,反之會更分散。

\[A = \sigma_2(W_2·\sigma_1(W_1·Z)) \]

  1. Re-weight
    最後一步就是用Excitation得到的每個特徵的權重對Embedding進行加權得到新Embedding

\[E_{new} = F_{Reweight}(A,E) = [a_1·e_1, …,a_f·e_f ] \]

在收入數據集上進行嘗試,r=2時會有46%的embedding特徵權重為0,所以SENET會在特徵交互前先過濾部分對target無用的特徵來增加有效特徵的權重

Bilinear-Interaction層

作者提出內積和element-wise乘積都不足以捕捉特徵交互信息,因此進一步引入權重W,以下面的方式進行特徵交互

\[v_i · W \odot v_j \]

其中W有三種選擇,可以所有特徵交互共享一個權重矩陣(Field-All),或者每個特徵和其他特徵的交互共享權重(Field-Each), 再或者每個特徵交互一個權重(Field-Interaction) 具體的優劣感覺需要casebycase來試,不過一般還是照着數據越少參數越少的邏輯來整。

原始Embedding和調整權重后的Embedding在Bilinear-Interaction學習交互特徵后,拼接成shallow 層,再經過全連接層來學習更高階的特徵交互。後面的屬於常規操作這裏就不再細說。

我們不去吐槽FiBiNET可以加入wide&deep框架來捕捉低階特徵信息和任意高階信息,更多把FiBiNET提供的SENET特徵權重的思路放到自己的工具箱中就好。

代碼實現

def Bilinear_layer(embedding_matrix, field_size, emb_size, type, name):
    # Bilinear_layer: combine inner and element-wise product
    interaction_list = []
    with tf.variable_scope('BI_interaction_{}'.format(name)):
        if type == 'field_all':
            weight = tf.get_variable( shape=(emb_size, emb_size), initializer=tf.truncated_normal_initializer(),
                                      name='Bilinear_weight_{}'.format(name) )
        for i in range(field_size):
            if type == 'field_each':
                weight = tf.get_variable( shape=(emb_size, emb_size), initializer=tf.truncated_normal_initializer(),
                                          name='Bilinear_weight_{}_{}'.format(i, name) )
            for j in range(i+1, field_size):
                if type == 'field_interaction':
                    weight = tf.get_variable( shape=(emb_size, emb_size), initializer=tf.truncated_normal_initializer(),
                                          name='Bilinear_weight_{}_{}_{}'.format(i,j, name) )
                vi = tf.gather(embedding_matrix, indices = i, axis =1, batch_dims =0, name ='v{}'.format(i)) # batch * emb_size
                vj = tf.gather(embedding_matrix, indices = j, axis =1, batch_dims =0, name ='v{}'.format(j)) # batch * emb_size
                pij = tf.matmul(tf.multiply(vi,vj), weight) # bilinear : vi * wij \odot vj
                interaction_list.append(pij)

        combination = tf.stack(interaction_list, axis =1 ) # batch * emb_size * (Field_size * (Field_size-1)/2)
        combination = tf.reshape(combination, shape = [-1, int(emb_size * (field_size * (field_size-1) /2)) ]) # batch * ~
        add_layer_summary( 'bilinear_output', combination )

    return combination


def SENET_layer(embedding_matrix, field_size, emb_size, pool_op, ratio):
    with tf.variable_scope('SENET_layer'):
        # squeeze embedding to scaler for each field
        with tf.variable_scope('pooling'):
            if pool_op == 'max':
                z = tf.reduce_max(embedding_matrix, axis=2) # batch * field_size * emb_size -> batch * field_size
            else:
                z = tf.reduce_mean(embedding_matrix, axis=2)
            add_layer_summary('pooling scaler', z)

        # excitation learn the weight of each field from above scaler
        with tf.variable_scope('excitation'):
            z1 = tf.layers.dense(z, units = field_size//ratio, activation = 'relu')
            a = tf.layers.dense(z1, units= field_size, activation = 'relu') # batch * field_size
            add_layer_summary('exciitation weight', a )

        # re-weight embedding with weight
        with tf.variable_scope('reweight'):
            senet_embedding = tf.multiply(embedding_matrix, tf.expand_dims(a, axis = -1)) # (batch * field * emb) * ( batch * field * 1)
            add_layer_summary('senet_embedding', senet_embedding) # batch * field_size * emb_size

        return senet_embedding

@tf_estimator_model
def model_fn_dense(features, labels, mode, params):
    dense_feature, sparse_feature = build_features()
    dense_input = tf.feature_column.input_layer(features, dense_feature)
    sparse_input = tf.feature_column.input_layer(features, sparse_feature)

    # Linear part
    with tf.variable_scope('Linear_component'):
        linear_output = tf.layers.dense( sparse_input, units=1 )
        add_layer_summary( 'linear_output', linear_output )

    field_size = len(dense_feature)
    emb_size = dense_feature[0].variable_shape.as_list()[-1]
    embedding_matrix = tf.reshape(dense_input, [-1, field_size, emb_size])

    # SENET_layer to get new embedding matrix
    senet_embedding_matrix = SENET_layer(embedding_matrix, field_size, emb_size,
                                         pool_op = params['pool_op'], ratio= params['senet_ratio'])

    # combination layer & BI_interaction
    BI_org = Bilinear_layer(embedding_matrix, field_size, emb_size, type = params['bilinear_type'], name = 'org')
    BI_senet = Bilinear_layer(senet_embedding_matrix, field_size, emb_size, type = params['bilinear_type'], name = 'senet')

    combination_layer = tf.concat([BI_org, BI_senet] , axis =1)

    # Deep part
    dense_output = stack_dense_layer(combination_layer, params['hidden_units'],
                               params['dropout_rate'], params['batch_norm'],
                               mode, add_summary=True )

    with tf.variable_scope('output'):
        y = dense_output + linear_output
        add_layer_summary( 'output', y )

    return y

CTR學習筆記&代碼實現系列

https://github.com/DSXiangLi/CTR
CTR學習筆記&代碼實現1-深度學習的前奏 LR->FFM
CTR學習筆記&代碼實現2-深度ctr模型 MLP->Wide&Deep
CTR學習筆記&代碼實現3-深度ctr模型 FNN->PNN->DeepFM
CTR學習筆記&代碼實現4-深度ctr模型 NFM/AFM
CTR學習筆記&代碼實現5-深度ctr模型 DeepCrossing -> Deep&Cross

Ref

  1. Jianxun Lian, 2018, xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems
  2. Tongwen Huang, 2019, FiBiNET: Combining Feature Importance and Bilinear feature Interaction for Click-Through Rate Prediction
  3. Jie Hu, 2017, Squeeze-and-Excitation Networks
  4. https://zhuanlan.zhihu.com/p/72931811
  5. https://zhuanlan.zhihu.com/p/79659557
  6. https://zhuanlan.zhihu.com/p/57162373
  7. https://github.com/qiaoguan/deep-ctr-prediction

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

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

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

基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)_網頁設計公司

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

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

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全網各大平台的熱點新聞數據的抓取,本篇繼續圍繞抓取完成后的操作做一個提醒。當每次抓取完數據后,自動發送郵件進行提醒。

在開始正題之前還是先玩一玩之前的說到卻沒有用到的一個庫PuppeteerSharp

PuppeteerSharp:Headless Chrome .NET API ,它運用最多的應該是自動化測試和抓取異步加載的網頁數據,更多介紹可以看GitHub:https://github.com/hardkoded/puppeteer-sharp 。

我這裏主要來試試它的異步抓取功能,同時它還能幫我們生成網頁截圖或者PDF。

如果沒有安裝可以先安裝一下,在.BackgroundJobs層安裝PuppeteerSharpInstall-Package PuppeteerSharp

在Jobs文件夾下新建一個PuppeteerTestJob.cs,繼承IBackgroundJob,同樣是在ExecuteAsync()方法中執行操作。

//PuppeteerTestJob.cs
using System;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.PuppeteerTest
{
    public class PuppeteerTestJob : IBackgroundJob
    {
        public async Task ExecuteAsync()
        {
            throw new NotImplementedException();
        }
    }
}

使用 await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision); 第一次檢測到沒有瀏覽器文件會默認幫我們下載 chromium 瀏覽器。

DownloadAsync(...)可以指定 Chromium 版本,BrowserFetcher.DefaultRevision 下載當前默認最穩定的版本。

然後配置瀏覽器啟動的方式。

using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
    Headless = true,
    Args = new string[] { "--no-sandbox" }
});

感興趣的可以自己看看LaunchOptions有哪些參數,我這裏指定了Headless = true 以無頭模式運行瀏覽器,然後加了一個啟動參數 “–no-sandbox”。針對Linux環境下,如果是運行在 root 權限下,在啟動 Puppeteer 時要添加 “–no-sandbox” 參數,否則 Chromium 會啟動失敗。

我們打開一個異步加載的網頁,然後獲取到頁面加載完后的HTML,以我個人博客中的某個單頁為例:https://meowv.com/wallpaper 。

//PuppeteerTestJob.cs
using PuppeteerSharp;
using System.Threading.Tasks;

namespace Meowv.Blog.BackgroundJobs.Jobs.PuppeteerTest
{
    public class PuppeteerTestJob : IBackgroundJob
    {
        public async Task ExecuteAsync()
        {
            await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);

            using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                Args = new string[] { "--no-sandbox" }
            });

            using var page = await browser.NewPageAsync();

            await page.SetViewportAsync(new ViewPortOptions
            {
                Width = 1920,
                Height = 1080
            });

            var url = "https://meowv.com/wallpaper";
            await page.GoToAsync(url, WaitUntilNavigation.Networkidle0);

            var content = await page.GetContentAsync();
        }
    }
}

page.SetViewportAsync()設置網頁預覽大小,page.GoToAsync()語法打開網頁,WaitUntilNavigation.Networkidle0等待網頁加載完畢,使用page.GetContentAsync()獲取到HTML。

新建擴展方法,調用這個PuppeteerTestJobExecuteAsync()方法,調試看看效果。

HTML已經出來了,此時該幹嘛就幹嘛就可以了。

第一次運行可能會很慢,因為如果你本地不存在 Chromium 是會去幫我們下載的,因為網絡原因可能會下載的很慢,所以推薦大家手動下載。

可以使用淘寶的源:https://npm.taobao.org/mirrors/chromium-browser-snapshots/ 。

要注意的是,下載完成后的解壓的路徑不能出錯,默認下載地址是在啟動目錄下面。

Windows:..\.local-chromium\Win64-706915\chrome-win 、 Linux:../.local-chromium/Linux-706915/chrome-linux

接下來試試生成PDF和保存圖片功能,使用方式也很簡單。

await page.PdfAsync("meowv.pdf",new PdfOptions { });
await page.ScreenshotAsync("meowv.png", new ScreenshotOptions
{
    FullPage = true,
    Type = ScreenshotType.Png
});

這裏只做簡單的展示,page.PdfAsync()直接生成PDF文件,同時還有很多方法可以自己調用page.試試,PdfOptions選項中可以設置各種參數。

page.ScreenshotAsync()保存圖片,ScreenshotOptions中FullPage可以設置保存圖片為全屏模式,圖片格式為Png類型。

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

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

可以看到項目根目錄已經生成了圖片和PDF,感覺去試試吧。

接下里來實現發送郵件的功能。

我這裏發郵件的賬號是用的騰訊企業郵箱,也可以用普通郵箱開通SMTP服務即可。

appsettings.json配置收發郵件的賬號等信息。

//appsettings.json
  "Email": {
    "Host": "smtp.exmail.qq.com",
    "Port": 465,
    "UseSsl": true,
    "From": {
      "Username": "123@meowv.com",
      "Password": "[Password]",
      "Name": "MEOWV.COM",
      "Address": "123@meowv.com"
    },
    "To": [
      {
        "Name": "test1",
        "Address": "test1@meowv.com"
      },
      {
        "Name": "test2",
        "Address": "test2@meowv.com"
      }
    ]
  }

然後再AppSettings中讀取配置的項。

//AppSettings.cs
public static class Email
{
    /// <summary>
    /// Host
    /// </summary>
    public static string Host => _config["Email:Host"];

    /// <summary>
    /// Port
    /// </summary>
    public static int Port => Convert.ToInt32(_config["Email:Port"]);

    /// <summary>
    /// UseSsl
    /// </summary>
    public static bool UseSsl => Convert.ToBoolean(_config["Email:UseSsl"]);

    /// <summary>
    /// From
    /// </summary>
    public static class From
    {
        /// <summary>
        /// Username
        /// </summary>
        public static string Username => _config["Email:From:Username"];

        /// <summary>
        /// Password
        /// </summary>
        public static string Password => _config["Email:From:Password"];

        /// <summary>
        /// Name
        /// </summary>
        public static string Name => _config["Email:From:Name"];

        /// <summary>
        /// Address
        /// </summary>
        public static string Address => _config["Email:From:Address"];
    }

    /// <summary>
    /// To
    /// </summary>
    public static IDictionary<string, string> To
    {
        get
        {
            var dic = new Dictionary<string, string>();

            var emails = _config.GetSection("Email:To");
            foreach (IConfigurationSection section in emails.GetChildren())
            {
                var name = section["Name"];
                var address = section["Address"];

                dic.Add(name, address);
            }
            return dic;
        }
    }
}

分別介紹下每項的含義:

  • Host:發送郵件服務器地址。
  • Port:服務器地址端口號。
  • UseSsl:是否使用SSL方式。
  • From:發件人的賬號密碼,名稱及郵箱地址,一般郵箱地址和賬號是相同的。
  • To:收件人郵箱列表,也包含名稱和郵箱地址。

收件人郵箱列表我將其讀取為IDictionary<string, string>了,key是名稱,value是郵箱地址。

接着在.ToolKits層添加一個EmailHelper.cs,收發郵件我選擇了MailKitMailKit兩個庫,沒有安裝的先安裝一下,Install-Package MailKitInstall-Package MimeKit

直接新建一個發送郵件的方法SendAsync(),按照要求將基本的配置信息填進去,然後直接調用即可。

//EmailHelper.cs
using MailKit.Net.Smtp;
using Meowv.Blog.Domain.Configurations;
using MimeKit;
using System.Linq;
using System.Threading.Tasks;

namespace Meowv.Blog.ToolKits.Helper
{
    public static class EmailHelper
    {
        /// <summary>
        /// 發送Email
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static async Task SendAsync(MimeMessage message)
        {
            if (!message.From.Any())
            {
                message.From.Add(new MailboxAddress(AppSettings.Email.From.Name, AppSettings.Email.From.Address));
            }
            if (!message.To.Any())
            {
                var address = AppSettings.Email.To.Select(x => new MailboxAddress(x.Key, x.Value));
                message.To.AddRange(address);
            }

            using var client = new SmtpClient
            {
                ServerCertificateValidationCallback = (s, c, h, e) => true
            };
            client.AuthenticationMechanisms.Remove("XOAUTH2");

            await client.ConnectAsync(AppSettings.Email.Host, AppSettings.Email.Port, AppSettings.Email.UseSsl);
            await client.AuthenticateAsync(AppSettings.Email.From.Username, AppSettings.Email.From.Password);
            await client.SendAsync(message);
            await client.DisconnectAsync(true);
        }
    }
}

SendAsync(...)接收一個參數MimeMessage對象,這樣就完成了一個通用的發郵件方法,接着我們去需要發郵件的地方構造MimeMessage,調用SendAsync()

//WallpaperJob.cs
...
    // 發送Email
    var message = new MimeMessage
    {
        Subject = "【定時任務】壁紙數據抓取任務推送",
        Body = new BodyBuilder
        {
            HtmlBody = $"本次抓取到{wallpapers.Count()}條數據,時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"
        }.ToMessageBody()
    };
    await EmailHelper.SendAsync(message);
...
//HotNewsJob.cs
...
    // 發送Email
    var message = new MimeMessage
    {
        Subject = "【定時任務】每日熱點數據抓取任務推送",
        Body = new BodyBuilder
        {
            HtmlBody = $"本次抓取到{hotNews.Count()}條數據,時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}"
        }.ToMessageBody()
    };
    await EmailHelper.SendAsync(message);
...

分別在兩個爬蟲腳本中添加發送Email,MimeMessage中設置了郵件主題Subject,正文Body,最後調用await EmailHelper.SendAsync(message)執行發送郵件操作。

編譯運行執行兩個定時任務,看看能否收到郵件提醒。

成功了,郵箱收到了兩條提醒。

還有一種比較特殊的用法,也介紹一下,如果想要發送帶圖片的郵件怎麼操作呢?注意不是附件,是將圖片內嵌在郵箱中。

一般常規都是有郵件模板的,將圖片的具體地址插入到img標籤中,這就不說了,這裏選擇另外一種方式。以前面添加的PuppeteerTestJob為例,正好我們生成了一張圖片的。將這種圖片以郵件的形式發出去。

public class PuppeteerTestJob : IBackgroundJob
{
    public async Task ExecuteAsync()
    {
        var path = Path.Combine(Path.GetTempPath(), "meowv.png");
        
        ...
        
        await page.ScreenshotAsync(path, new ScreenshotOptions
        {
            FullPage = true,
            Type = ScreenshotType.Png
        });

        // 發送帶圖片的Email
        var builder = new BodyBuilder();

        var image = builder.LinkedResources.Add(path);
        image.ContentId = MimeUtils.GenerateMessageId();

        builder.HtmlBody = "當前時間:{0}.<img src=\"cid:{1}\"/>".FormatWith(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), image.ContentId);

        var message = new MimeMessage
        {
            Subject = "【定時任務】每日熱點數據抓取任務推送",
            Body = builder.ToMessageBody()
        };
        await EmailHelper.SendAsync(message);
    }
}

先確定我們生成圖片的路徑 path ,將圖片生成Message-Id,然後賦值給ContentId,給模板中<img src=\"cid:{1}\"/>圖片標籤cid賦上值在調用發送郵件方法即可。

成功收到郵件,搞定了,你學會了嗎?

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

基於 abp vNext 和 .NET Core 開發博客項目,截止到本篇所用到的基礎模塊算是寫完了,如果對您有些許幫助請多多分享,我的所有原創文章都首發於我發個人公眾號:阿星Plus 。

下面有二維碼可以直接掃一掃,如果你不想關注也沒有關係,博客園我也會同步過來的。

不管因為什麼,如果你在學習這個項目或者跟着我一起做這個項目,裏面肯定還是有瑕疵的,大家可以根據自己的需求自行修改。

接下來應該還會更新博客所用到的接口,這個純屬於CRUD,可以自己先行開發,我這邊目前也不知道以什麼樣的方式展現給大家是最好的選擇。

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

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

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

Redis詳解(十一)—— 過期刪除策略和內存淘汰策略_網頁設計公司

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

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

  在介紹這篇文章之前,我們先來看如下幾個問題:

  ①、如何設置Redis鍵的過期時間?

  ②、設置完一個鍵的過期時間后,到了這個時間,這個鍵還能獲取到么?假如獲取不到那這個鍵還佔據着內存嗎?

  ③、如何設置Redis的內存大小?當內存滿了之後,Redis有哪些內存淘汰策略?我們又該如何選擇?

  如果上面的幾個問題你都懂,那麼下面的內容你就不用看了;如果你不是很懂,那就帶着這些問題往下看。

1、設置Redis鍵過期時間

  Redis提供了四個命令來設置過期時間(生存時間)。

  ①、EXPIRE <key> <ttl> :表示將鍵 key 的生存時間設置為 ttl 秒。

  ②、PEXPIRE <key> <ttl> :表示將鍵 key 的生存時間設置為 ttl 毫秒。

  ③、EXPIREAT <key> <timestamp> :表示將鍵 key 的生存時間設置為 timestamp 所指定的秒數時間戳。

  ④、PEXPIREAT <key> <timestamp> :表示將鍵 key 的生存時間設置為 timestamp 所指定的毫秒數時間戳。

  PS:在Redis內部實現中,前面三個設置過期時間的命令最後都會轉換成最後一個PEXPIREAT 命令來完成。

  另外補充兩個知識點:

  一、移除鍵的過期時間

  PERSIST <key> :表示將key的過期時間移除。

  二、返回鍵的剩餘生存時間

  TTL <key> :以秒的單位返回鍵 key 的剩餘生存時間。

  PTTL <key> :以毫秒的單位返回鍵 key 的剩餘生存時間。

2、Redis過期時間的判定

  在Redis內部,每當我們設置一個鍵的過期時間時,Redis就會將該鍵帶上過期時間存放到一個過期字典中。當我們查詢一個鍵時,Redis便首先檢查該鍵是否存在過期字典中,如果存在,那就獲取其過期時間。然後將過期時間和當前系統時間進行比對,比系統時間大,那就沒有過期;反之判定該鍵過期。

3、過期刪除策略

  通常刪除某個key,我們有如下三種方式進行處理。

①、定時刪除

  在設置某個key 的過期時間同時,我們創建一個定時器,讓定時器在該過期時間到來時,立即執行對其進行刪除的操作。

  優點:定時刪除對內存是最友好的,能夠保存內存的key一旦過期就能立即從內存中刪除。

  缺點:對CPU最不友好,在過期鍵比較多的時候,刪除過期鍵會佔用一部分 CPU 時間,對服務器的響應時間和吞吐量造成影響。

②、惰性刪除

  設置該key 過期時間后,我們不去管它,當需要該key時,我們在檢查其是否過期,如果過期,我們就刪掉它,反之返回該key。

  優點:對 CPU友好,我們只會在使用該鍵時才會進行過期檢查,對於很多用不到的key不用浪費時間進行過期檢查。

  缺點:對內存不友好,如果一個鍵已經過期,但是一直沒有使用,那麼該鍵就會一直存在內存中,如果數據庫中有很多這種使用不到的過期鍵,這些鍵便永遠不會被刪除,內存永遠不會釋放。從而造成內存泄漏。

③、定期刪除

  每隔一段時間,我們就對一些key進行檢查,刪除裏面過期的key。

  優點:可以通過限制刪除操作執行的時長和頻率來減少刪除操作對 CPU 的影響。另外定期刪除,也能有效釋放過期鍵佔用的內存。

  缺點:難以確定刪除操作執行的時長和頻率。

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

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

     如果執行的太頻繁,定期刪除策略變得和定時刪除策略一樣,對CPU不友好。

     如果執行的太少,那又和惰性刪除一樣了,過期鍵佔用的內存不會及時得到釋放。

     另外最重要的是,在獲取某個鍵時,如果某個鍵的過期時間已經到了,但是還沒執行定期刪除,那麼就會返回這個鍵的值,這是業務不能忍受的錯誤。

4、Redis過期刪除策略

  前面討論了刪除過期鍵的三種策略,發現單一使用某一策略都不能滿足實際需求,聰明的你可能想到了,既然單一策略不能滿足,那就組合來使用吧。

  沒錯,Redis的過期刪除策略就是:惰性刪除和定期刪除兩種策略配合使用。

  惰性刪除:Redis的惰性刪除策略由 db.c/expireIfNeeded 函數實現,所有鍵讀寫命令執行之前都會調用 expireIfNeeded 函數對其進行檢查,如果過期,則刪除該鍵,然後執行鍵不存在的操作;未過期則不作操作,繼續執行原有的命令。

  定期刪除:由redis.c/activeExpireCycle 函數實現,函數以一定的頻率運行,每次運行時,都從一定數量的數據庫中取出一定數量的隨機鍵進行檢查,並刪除其中的過期鍵。

  注意:並不是一次運行就檢查所有的庫,所有的鍵,而是隨機檢查一定數量的鍵。

  定期刪除函數的運行頻率,在Redis2.6版本中,規定每秒運行10次,大概100ms運行一次。在Redis2.8版本后,可以通過修改配置文件redis.conf 的 hz 選項來調整這個次數。

  

 

  看上面對這個參數的解釋,建議不要將這個值設置超過 100,否則會對CPU造成比較大的壓力。

  我們看到,通過過期刪除策略,對於某些永遠使用不到的鍵,並且多次定期刪除也沒選定到並刪除,那麼這些鍵同樣會一直駐留在內存中,又或者在Redis中存入了大量的鍵,這些操作可能會導致Redis內存不夠用,這時候就需要Redis的內存淘汰策略了。

5、內存淘汰策略

①、設置Redis最大內存

  在配置文件redis.conf 中,可以通過參數 maxmemory <bytes> 來設定最大內存:

  

  不設定該參數默認是無限制的,但是通常會設定其為物理內存的四分之三。(這裡有個疑惑:為啥作者不考慮將此參數設定為百分比呢?)

②、設置內存淘汰方式

  當現有內存大於 maxmemory 時,便會觸發redis主動淘汰內存方式,通過設置 maxmemory-policy ,有如下幾種淘汰方式:

  1)volatile-lru   利用LRU算法移除設置過過期時間的key (LRU:最近使用 Least Recently Used ) 。

  2)allkeys-lru   利用LRU算法移除任何key (和上一個相比,刪除的key包括設置過期時間和不設置過期時間的)。通常使用該方式

  3)volatile-random 移除設置過過期時間的隨機key 。

  4)allkeys-random  無差別的隨機移除。

  5)volatile-ttl   移除即將過期的key(minor TTL) 

  6)noeviction 不移除任何key,只是返回一個寫錯誤 ,默認選項,一般不會選用。

  在redis.conf 配置文件中,可以設置淘汰方式:

  

6、總結

  通過上面的介紹,相信大家對Redis的過期數據刪除策略和內存淘汰策略有一定的了解了。這裏總結一下:

  Redis過期刪除策略是採用惰性刪除和定期刪除這兩種方式組合進行的,惰性刪除能夠保證過期的數據我們在獲取時一定獲取不到,而定期刪除設置合適的頻率,則可以保證無效的數據及時得到釋放,而不會一直佔用內存數據。

  但是我們說Redis是部署在物理機上的,內存不可能無限擴充的,當內存達到我們設定的界限后,便自動觸發Redis內存淘汰策略,而具體的策略方式要根據實際業務情況進行選取。

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

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

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

紫米 ZMI 無線充車載支架(自動版)通過 NCC 認證,近期將在台開賣_網頁設計公司

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

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

對於時時呵護手機,或者注重車室整體質感有相當要求的車主來說,即便只是選購款車載支架也是需要好好慎選一番才行。去年八月初,小米生態鏈企業 ZMI(紫米)推出了一款支援自動夾緊、 採用 Alcantara 材質表面的高質感無線車載支架,最近它也悄悄由紫米授權經銷 Kamera 佳美能科技申請通過 NCC 認證,未來也將在台灣開賣。

▲圖片來源:ZMI 紫米

紫米 ZMI 無線充車載支架(自動版)通過 NCC 認證,近期將在台開賣

上週,在 NCC 認證通過的資料庫中出現了一款 ZMI 型號 WCJ11 的無線車充設備,經比對附件外觀和產品型號後確認它就是去年八月初在中國率先發表的「ZMI 無線車充車在支架自動版」,申請的廠商正是紫米在台灣的授權經銷 Kamera 佳美能科技。
即便這款產品可能在些網拍平台有其他人購入販售,但建議如果對這款產品有興趣的讀者,為了產品的品質和售後服務,還是選購未來 Kamera 的公司貨才有保障。

▲圖片來源:NCC

ZMI 車載無線充車載支架(自動版),顧名思義就是他支持自動夾緊功能,藉由內建紅外線感應器和馬達,在手機靠近時兩側的夾臂可自動展開、鎖緊,讓手機可輕鬆放入進行充電。要取下手機時,輕觸側面按鍵即可單手取出手機。
另外,這款車載支架的夾臂張開的寬度可達 81.5mm ,理論上來說絕大多數的智慧型手機都能適用,但像是目前市面最常見的「大手機」 iPhone 12 Pro Max 裸機寬度為 78.1mm ,若要使用這款產品則切記不要使用過於寬厚的手機保護殼。

▲圖片來源:ZMI 紫米

多數車載支架在與手機背面接觸的表面都採用塑料材質,如果相當保護手機、害怕刮傷的用戶難免會擔心長久下來讓手機產生刮痕。而 ZMI 無線充車載支架(自動版)採用 Alcantara  的超纖面料作為充電面板,柔軟、耐磨、手感好的特性,也常在進口豪華車、超跑內裝常見到這類材質。
車載支架固定採用三角結構能穩固防止路面顛簸,而自動鎖緊也能加強手機牢牢固定在車載支架上進行無線充電,無線充電有效感應距離達 4mm ,即便使用保護殼也能進行充電。

▲圖片來源:ZMI 紫米

雖然這款車載支架最高僅支持到 10W 的無線快充,不過撇除小米、華為一些特殊機型,其實對於目前普遍支持 Qi 無線充電的主流手機來說, 10W 無線充電已經綽綽有餘,甚至許多機型只能支持到 7.5W 快充。
搭配的定製車充採用雙 USB-A 接口輸出,單口輸出功率可達 18W 、雙口同時輸出可達 36W ,能一次滿足車內駕駛和乘客兩位的同時充電需求。

▲圖片來源:ZMI 紫米

其他小細節部分, ZMI 無線充車載支架(自動版)夾臂內側採用矽膠墊能增添防滑、保護的作用。除了安裝在冷氣出風口的夾具固定,也標配黏貼底座以便一些希望能將支架黏貼固定在車內的族群。然而,這款車載支架還是能使用有線充電的,因為在支架採用 U 型開口,就是方便用戶使用充電線進行有線充電使用。

▲圖片來源:ZMI 紫米

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

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

消息來源:NCC

延伸閱讀:
小米 POCO 官方 Facebook 粉專成立、官網上線,即將以 POCO 品牌「重返」台灣市場

小米有品推出 Lydsto 手持吸塵打氣機,眾籌價約 860 元

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

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

數據結構 9 基礎數據結構 二叉堆 了解二叉堆的元素插入、刪除、構建二叉堆的代碼方式_網頁設計公司

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

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

是否記得我們在之前的學習中有學習到二叉樹 忘記的小夥伴們請查看:完全二叉樹的定義。

https://blogs.chaobei.xyz/archives/shuju2

二叉堆

二叉堆其實就是一個完全二叉樹 一起複習一下吧:關於二叉樹和滿二叉樹以及完全二叉樹的基本概念。

二叉樹

  • 每個節點下掛元素不超過2
  • 並且元素都是按照一定規律排列的

二叉樹規律

按照前人的總結,我們可以得出以下結論。

  • 一個深度為K 的二叉樹,最多包含節點數 2的k次方-1
  • 二叉樹指定n 層級所包含的節點數為 2的n-1次方

滿二叉樹

從字面意思我們可以理解到:這個二叉樹它是一種飽和的狀態,顧名思義稱作是滿二叉樹。

完全二叉樹

除去二叉樹的恭弘=叶 恭弘子節點,所有節點都包含有兩個節點,並且節點都是按照一定順序排列的,這樣的二叉樹被稱作是完全二叉樹

二叉堆類型

在上面我們已經提到過。二叉堆就是一種完全二叉樹、二叉樹的概念也已經了解到了。當然,現在應該分析二叉堆有有哪些性質

  • 最大堆
  • 最小堆

最大堆

最大堆的父節點元素的值都大於等於其兩個子元素的值

最小堆

反之,最小堆父節點元素的值,都小於等於其兩個子元素的值

二叉堆的堆頂部 則是這個堆序列最大或者最小的元素。

二叉堆的自我調整

二叉堆的自我調整,有以下幾種情況:

  • 新元素的插入
  • 元素的刪除
  • 構建二叉堆

我們以最上面的最小堆為例,講述如何將一個元素插入二叉堆、如何刪除一個元素、如何來構建一個二叉堆。

插入一個節點

按照上面最小堆的順序,我這裏再插入幾個其他元素方便觀察。假設這裏我插入一個元素1

元素1<元素6 進行上浮。子節點與父節點進行調換位置

元素1<元素3 進行上浮。子節點與父節點進行調換位置

元素1<元素2 進行上浮。到達堆頂部。

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

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

刪除一個元素

當前元素與子節點中最小元素進行比較、大於則交換位置下沉

假設我們刪除最頂層的元素1

二叉堆為了保證樹的結構、將二叉堆裏面尾部元素6填充到被刪除的位置。

當前元素6 與兩個子元素裏面最小元素 進行比較。大於則下沉

當前元素6 > 3 進行調換位置。元素6到達尾部。

規律:二叉堆的刪除元素和新增元素剛好是相反的

構建一個二叉堆

構建二叉堆、其實就是將一個原有的、無序的、完全二叉樹給他構建成有序的二叉堆。

假設我們來構建一個最小堆 我們這裏拿到一個無序的完全二叉樹如圖:

划重點:構建最小堆就是將非恭弘=叶 恭弘子節點進行下沉

1、操作元素5 元素5小於元素8 不進行移動

2、操作元素1 元素1小於元素5 不進行移動

3、操作元素7 省略步驟。最終結果如下:

4、操作元素3 省略步驟。最終結果如下:

至此,我們的無序完全二叉樹已經變成了一個有序的二叉最小堆

代碼實現

二叉堆雖然是一顆完全二叉樹,但是其存儲方式是順序存儲,使用的是數組、而不是鏈式指針。

我們可以發現如下規律:

  • 父元素左邊子元素位置 = 2*父元素下標 + 1
  • 父元素右邊子元素位置 = 2*父元素下標 + 2
public static void main(String[] args) {
        int[] array = {7, 1, 3, 5, 6, 4, 2, 8, 9};
        buildBinaryHeap(array);
        System.out.println(Arrays.toString(array));
    }

    public static void buildBinaryHeap(int[] array) {
        //除去恭弘=叶 恭弘子節點、將每個節點進行下沉操作
        for (int i = (array.length - 2) / 2; i >= 0; i--) {
            sinking(array, i);
        }
    }

    /**
     * 構建二叉堆、讓當前元素下沉
     *
     * @param array     被操作的數組
     * @param itemIndex 當前元素下標
     */
    public static void sinking(int[] array, int itemIndex) {
        //數組長度
        int length = array.length - 1;
        //父節點值
        int parent = array[itemIndex];

        //默認操作的是左孩子
        int childIndex = 2 * itemIndex + 1;

        while (childIndex < length) {

            //存在右邊子元素、並且右邊子元素值小於左邊
            if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
                //切換到右邊元素
                childIndex++;
            }

            //小於等於則無需交換
            if (parent <= array[childIndex]) break;

            //無需交換、只需要將子元素移動到父元素位置即可
            array[itemIndex] = array[childIndex];
            itemIndex = childIndex;

            //改變左右子元素的下標
            childIndex = 2 * itemIndex + 1;
        }
        //最終將父元素移動到指定位置即可。
        array[itemIndex] = parent;
    }

代碼示例

https://gitee.com/mrc1999/Data-structure

小結

通過本節的學習,應該需要掌握二叉堆這個重要的數據結構、如何將一個完全二叉樹構建成一個二叉堆、並且二叉堆在插入元素、和刪除元素時候如何將原來的結構保持不變的。這該是我們學習的。
下一節將繼續學習二叉堆的堆排序、我們一起加油!

參考

https://mp.weixin.qq.com/s/cq2EhVtOTzTVpNpLDXfeJg

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

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

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

我天!xx.equals(null) 是什麼騷操作??_網頁設計公司

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

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

問題背景

我的天,最近做 Code Review 看到一個同事的騷操作,他寫了一個工具類,大概是這樣的:

public static boolean isNull(Object object){
    return null == object || object.equals(null);
}

判斷空,一般不是 null == object 就夠了,object.equals(null) 是什麼騷操作?

寫程序這麼多年,第一次看這樣的寫法,當時我就提出質疑了,同事拍着胸脯和我說,有個銀行的請求參數必須得這麼寫,不然就驗證不了。

我當時還在想,這是 JDK 出的什麼新類型么,覺得還是不科學,考慮去跟下同事寫的代碼,然後用他所說的情況我親自去驗證一下。

看了下,這是個老業務系統,同事用了 json-lib 這個包,歷史的江湖確實有這個包的存在,棧長之前也用過,不過後來這玩意就沒怎麼用了,現在都是 GsonJackson 的天下了。

如下面 json-lib 例子所示:

public static void main(String[] args) {
    String jsonString = "{\"name\": \"hi\",\"sex\": \"boy\", \"age\": null}";

    JSONObject jsonObject = net.sf.json.JSONObject.fromObject(jsonString);
    Object age = jsonObject.get("age");
    
    // 輸出:null
    System.out.println("age: " + age);

    // 輸出:false
    System.out.println("age == null: " + (age == null));
    
    // 輸出:true
    System.out.println("age.equals(null): " + (age.equals(null)));
}

我天!大家看到結果了吧,問題確實也如同事所說,一定要用 object.equals(null) 寫法才行,不相信結果的大家也可以親自驗證一下。

納了悶了,這樣寫,我傳一個 null 值過去不是報空指針了么?這樣寫肯定有問題,繼續深挖!

問題分析

fromObject 方法加載 JSON 串開始源碼深入分析,找到了這個神奇解析 null 值的源碼:

原來,JSON 串中的 null 值被解析成了它內部的 JSONNull 對象,然後再看下這個 JSONNull 的 equals 方法源碼:

public boolean equals(Object object) {
    return object == null || 
           object == this || 
           object == instance || 
           object instanceof JSONObject &&
           ((JSONObject)object).isNullObject() ||
           "null".equals(object);
}

問題就出在他所用的 JSON 工具類了!!!

equals 方法被重寫了……終於揭開了 object.equals(null) 的神秘面紗……

再來看下是否有新的更新包:

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

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

最新的版本停留在 2010 年 12 月,已經是被淘汰的東西了。

另外,json-lib 在 JDK 1.7+ 有性能影響。
推薦閱讀:請不要在 JDK 7+ 中使用這個 JSON 包了

解決方案

方法1:

換掉 object.equals(null),用 JSONNull 的實例去判斷:

public static boolean isNull(Object object){
    return null == object || JSONNull.getInstance().equals(object);
}

方法2:

換掉 json-lib 庫,用主流的 GsonJackson

具體看下這篇:Java常用的幾個Json庫,性能強勢對比,另外 FastJson 也不建議用了,漏洞比較多。

這個由於是老系統,太多業務使用了這個庫,換掉的開發、測試成本和風險比較大,暫時考慮先用方案1先解決這個問題。

關注Java技術棧微信公眾號,棧長將繼續分享好玩的 Java 技術,公眾號第一時間推送,在公眾號後台回復:Java,可以獲取歷史 Java 教程,都是乾貨。

推薦去我的博客閱讀更多:

1.Java JVM、集合、多線程、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、後端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!

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

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

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

一次FGC導致CPU飆高的排查過程_網頁設計公司

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

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

    今天測試團隊反饋說,服務A的響應很慢,我在想,測試環境也會慢?於是我自己用postman請求了一下接口,真的很慢,竟然要2s左右,正常就50ms左右的。

    於是去測試服務器看了一下,發現服務器負載很高,並且該服務A佔了很高的cpu。先用top命令,看了load average,發現都到了1.5左右(雙核cpu)了,並且有一個java進程(20798)佔用cpu一直很高,如下圖:

    於是,用命令jps -l看了一下java的20798,剛好就是服務A。

    究竟服務A在跑什麼,畢竟是測試環境。於是使用top -Hp 20798看一下是哪個線程在跑,如下圖:

    

    發現線程20840佔用cpu非常高,其他幾乎都是0。通過以下命令輸出該線程id(20840)的16進制:

printf "%x\n" 20840

  

輸出如下:

    線程id(20840)的16進制是5186。

    然後使用以下命令打印出該線程的堆棧信息:

jstack -l 20798 | grep -A 20 5168

  

    輸入如下:

    發現佔用cpu的進程是jvm的GC線程,於是猜測是不是由於一直在進行FGC導致cpu飆高,於是使用以下命令看下FGC的頻率和耗時:

jstat -gc 20798 1000

  

輸出如下:

    發現,果然是不斷地在進行着FGC,並且每次FGC的時間一直在升高。是什麼導致一直都在FGC呢?是有大對象一直在創建,回收不了?於是使用以下命令看下heap中的對象情況:

jmap -histo:live 20798 | head -20

  

輸出如下:

    發現一個業務類對象竟然有150w+個,並且佔用了264M的堆大小,什麼情況,並且這150w+個對象還是存活的(注意jmap使用的時候,已經帶上了:live選項,只輸出存活的對象),嚇我一跳。於是趕緊使用以下命令打出線程堆棧來看一下:

jstack -l 20798 > jstack_tmp.txt

  

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

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

輸出如下:

然後使用如下命令在輸出的線程堆棧中根據對象類查找一下:

grep -C 30 'omments' jstack_tmp.txt

  

輸出如下:

    猜測是由於一下次從db load出了太多的CommentsEntity。

    於是使用以下命令dump出heapdump出來重複確認一下:

jmap -dump:live,format=b,file=news_busy_live.hprof 20798

  

    把heapdump文件news_busy_live.hprof下載到windows本地,使用mat工具進行分析,第一次打開發現打不開,畢竟news_busy_live.hprof有3G那麼大,mat直接報OOM打不開,發現mat的配置文件MemoryAnalyzer.ini裏面的配置-Xmx1024m,heap size才1G,太小了,於是改成-Xmx4096m,保存,重新打開mat,再打開news_busy_live.hprof文件即可,如下圖:

    發現mat已經幫我們分析出了內存泄漏的可以對象,233w+個對象(前面通過jmap命令輸出的150W+個,是後面為了寫文章而專門重現的操作,這裏的233w+個是當時真的出問題的時候dump出來的heap dump文件),太恐怖了。

    通過以下操作,查看

點擊exclude all ….,因為弱引用,軟引用,虛引用等都可以被GC回收的,所以exclude,輸出如下:

    發現一共有6個線程引用了那233w+個對象,於是去前面dump出來的線程堆棧跟蹤以下這幾個線程的情況,發現堆棧裏面剛好這幾個線程也是在處理comments相關的邏輯,這個是剛好碰巧,一般線程id都對不上的,畢竟線程處理完之後就釋放了的。所以我們還是看回前麵線程堆棧的信息,這裏貼出根據關鍵字”omment”搜索出來的線程堆棧的信息,如下:

"XNIO-5 task-77" #248 prio=5 os_prio=0 tid=0x00007fc4511be800 nid=0x8f7 runnable [0x00007fc3e5af2000]   java.lang.Thread.State: RUNNABLE       ...        at cn.xxxxxx.news.commons.redis.RedisUtil.setZSet(RedisUtil.java:1080)        at cn.xxxxxx.news.service.impl.CommentsServiceV2Impl.setCommentIntoRedis(CommentsServiceV2Impl.java:1605)        at cn.xxxxxx.news.service.impl.CommentsServiceV2Impl.loadCommentsFromDB(CommentsServiceV2Impl.java:386)        ...        at cn.xxxxxx.xxxs.controller.vxxx.xxxxController.getxxxxxx(NewsContentController.java:404)        at cn.xxxxxx.xxx.controller.vxxx.xxxxxController$$FastClassBySpringCGLIB$$e7968481.invoke(<generated>)        ...        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)        at java.lang.Thread.run(Thread.java:745)​   Locked ownable synchronizers:        - <0x00000000f671ecd0> (a java.util.concurrent.ThreadPoolExecutor$Worker)​

  

    

    從上面的堆棧信息,結合前面的猜測(猜測是一次性從db load出太多的CommentsEntity),猜測應該是函數loadCommentsFromDB一次性從db load出太多CommentsEntity了。於是看了一下業務代碼,發現load出來的commentsEntity會放到redis的某一個zset,於是使用redis destopmanger看一下這個zset的數據,發現這個zset有22w的數據,從中找出幾條,發現對應的newsPk都是同一個,根據newsPk在db中找一下該newsPk的comments總記錄,發現該newsPk的comments記錄數是38w+條,那就是這個問題了,一次性從db中load了38w+的數據到內存。

    一次性load那麼多數據到內存,這肯定是一個慢查詢,不管是db還是網絡io,都肯定很慢。然後發現業務代碼還會有一個for循環,把這個CommentsEntityList遍歷一遍,一條一條放到redis,這也是一個非常慢的過程。

    然後我去看了服務A的access log,發現在短時間內,請求了該newsPk多次數據,所以就導致了jvm的heap空間不夠,然後出現不斷FGC的現象,並且該newsPk的請求,由於超時,都在網關超時返回了。

    為了驗證這個問題,我把相關的redis緩存刪除,然後調用該newsPk的接口獲取數據,發現很慢,並且cpu立刻飈上去了,然後調多幾次,並且不斷地進行FGC,至此已經復現了該問題,和猜測的一樣。等數據load到redis之後,再訪問該接口,就很正常沒問題。

    上面發現問題的代碼,找時間做一下優化才行,先重啟服務A,讓服務可用先。

 

                    歡迎關注微信公眾號“ismallboy”,請掃碼並關注以下公眾號,並在公眾號下面回復“FGC”,獲得本文最新內容。

                                                           

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

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

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

ASP.NET Core Blazor Webassembly 之 數據綁定_網頁設計公司

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

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

上一次我們學習了Blazor組件相關的知識(Asp.net Core Blazor Webassembly – 組件)。這次繼續學習Blazor的數據綁定相關的知識。當代前端框架都離不開數據綁定技術。數據綁定技術以數據為主導來驅動UI界面,用戶對數據的修改會實時提現在UI上,極大的提高了開發效率,讓開發者從繁瑣的dom操作中解脫出來。對於數據綁定.NET開發者並不會陌生,WPF里大量應用數據綁定技術,有過WPF開發經驗的同學其實很容易理解前端的數據綁定。總之數據綁定技術及其概念、思維極其重要。下面讓我們看看Blazor的數據綁定技術。

單向綁定

Blazor的數據綁定官方文檔是直接從雙向綁定開始的,但我覺得有必要說一下單向綁定。因為其他框架一般都會區分單向、雙向,比如vue的v-bind單向,v-model就是雙向。我們這裏分開講也有利於跟其他框架進行對比。下面我們實現一個計數器組件來演示下單向數據綁定。

使用@進行綁定

@page "/counter"

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

這個Counter組件默認的項目就自帶。跟我們使用服務端Razor一樣,使用@符號在需要替換值的地方插入對應的變量。這個值就會被渲染在相應的地方。當我們在前端修改變量的時候,對應的ui界面會同步進行修改。

使用@bind-{attribute}進行綁定

除了直接使用@進行綁定,我們還可以使用@bind-{attribute}來實現對html元素屬性的綁定,比如對style,class內容進行綁定。下面演示下對class進行綁定。我們把p元素的class綁定到“currentClass”字段。

@page "/counter"

<h1>Counter</h1>

<p @bind-class="currentClass" @bind-class:event="onchange">
    current count: @currentCount
</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private string currentClass = "text-danger";

    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

使用@bind-{attribute}進行綁定有個比較奇怪的問題,當你使用@bind-{attribute}進行綁定的時候必須同時指定@bind-{attribute}:event。@bind-{attribute}:event是用來指定雙向綁定的時候控件在發生某個事件的時候回寫值到綁定的字段上。可是p,div這種元素根本不可能會激發onchange,oninput這種事件,也不可能去修改綁定的字段的值,這個用法感覺有點多此一舉。
Blazor的單向數據綁定的用法跟ASP.NET Core MVC的Razor基本相似,不同點就是Blazor不需要Http回發到服務器就可以實時渲染新的界面出來。

雙向綁定

雙向綁定主要使用在一些輸入控件上,比如input,select等。當我們對這些控件上的值進行修改後會回寫綁定的字段。這種特性在表單場景中非常有用。我們定義一個用戶信息編輯的組件來演示下:

@page "/infoedit"

<p>
    userName: @userName
</p>
<p>
    sex: @sex
</p>
<p>
    userName: <input @bind="userName" />
</p>
<p>
    sex:
    <select @bind="sex">
        <option value="m">男</option>
        <option value="f">女</option>
    </select>
</p>

@code {
    private string userName="abc";
    private string sex="f";
}

當我們運行這個組件,在文本框進行修改后,鼠標點擊其他地方讓文本框失去焦點值就會回寫到綁定的字段上,上面的單向綁定信息會自動同步。但是如果你用過VUE或者Angularjs的雙向綁定就會覺得失去焦點再回寫字段數據太慢了,一點也不酷。要知道VUE的雙向綁定可是實時同步的,那麼Blazor如何做到在輸入的同時就更新值呢,答案是使用@bind:event來指定回寫的激發事件,我們改成“oninput”事件就可以實現:

<p>
    userName: <input @bind="userName" @bind:event="oninput"/>
</p>

雙向綁定的多種寫法

看到這裏也許你也明白了,@bind真正的本質是由對value的綁定和對某個事件的綁定協同完成的。這點跟VUE非常相似。@bind其實是@bind-value的縮寫,我們可以用@bind-value來實現雙向綁定:

<p>
    userName: <input @bind-value="userName" @bind-value:event="oninput"/>
</p>

以上寫法的效果跟@bind一模一樣。再進一步,@bind-value也只是對@的包裝,我們可以使用@來實現雙向綁定:

@page "/infoedit"

<p>
    userName: @userName
</p>
<p>
    sex: @sex
</p>
<p>
    userName: <input value="@userName" @oninput="oninput"/>
</p>
<p>
    sex:
    <select @bind="sex">
        <option value="m">男</option>
        <option value="f">女</option>
    </select>
</p>

@code {
    private string userName="abc";
    private string sex="f";

    private void oninput(ChangeEventArgs e)
    {
        userName = e.Value.ToString();
    }

}

以上代碼的效果跟@bind一模一樣。通過使用@對value直接進行綁定以及綁定一個oninput事件進行值的回寫,同樣實現了雙向綁定。

格式化時間字符串

使用@bind:format 可以對綁定時間類型字段的時候進行格式化:

出生日期:<input @bind="birthDay" @bind:format="yyyy-MM-dd" />

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

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

這個功能有點類似Angularjs的filter功能,但是目前只能對時間進行格式化,功能很弱。

父組件綁定數據到子組件

組件之間往往都是嵌套的,很多子組件都依賴父組件的數據來決定如何呈現,這種場景非常常見。我們還是繼續修改上面的編輯組件,用戶信息不在自己初始化,而是從父組件傳遞過來:
子組件:

====================child==================

<p>
    userName: <input @bind="UserInfo.UserName" />
</p>
<p>
    sex:
    <select @bind="UserInfo.Sex">
        <option value="m">男</option>
        <option value="f">女</option>
    </select>
</p>

<p>
    BrithDay:<input @bind="UserInfo.BrithDay" />
</p>
@code {

    [Parameter]
    public UserInfo UserInfo { get; set; }

    [Parameter]
    public EventCallback<UserInfo> UserInfoChanged { get; set; }
}

子組件定義一個UserInfo對象並且使用[Parameter]進行標記,同時如果父組件使用@bind-UserInfo來綁定的話,還必須實現一個UserInfoChanged事件。
父組件:

@page "/"
====================parent==================

<p>
    userName: @userInfo.UserName
</p>
<p>
    sex: @userInfo.Sex
</p>
<p>
    brithday: @userInfo.BrithDay
</p>


<InfoEdit @bind-UserInfo="userInfo"></InfoEdit>

@code {

    private UserInfo userInfo;

    protected override void OnInitialized()
    {
        userInfo = new UserInfo
        {
            UserName = "abc",
            Sex = "f",
            BrithDay = DateTime.Now
        };
        base.OnInitialized();
    }
}

父組件初始化一個UserInfo對象后通過@bind-UserInfo綁定給子組件。注意這裏我們修改子組件的值並不會同步給父組件,所以可以看到@bind-UserInfo的傳值還是單向的。

子組件傳值給父組件 ??

原來我以為父組件使用@bind-UserInfo並且子組件實現了對應的changed方法就可以實現子組件跟父組件的自動傳值,就跟input的雙向綁定一樣。但是不管我怎麼試都沒有卵用。如果只是單向的那為什麼要這麼大費周章?我直接使用屬性賦值不就可以了么?像下面這樣:

<InfoEdit UserInfo="userInfo" ></InfoEdit>

直接通過組件的屬性直接把父組件的數據傳遞到子組件,效果跟上面是一樣的,而且這樣子組件我還能少寫一個changed事件。我原本以為使用基本類型,比如string可以自動雙向綁定,然後並沒有什麼卵用。沒有辦法我繼續嘗試父組件監聽UserInfoChanged事件來接受子組件的數據,然後VS提示我同一個事件不能綁定兩次。

我已經無語了,難道要我再定義一個事件嗎?於是我放棄了@bind-來實現子組件給父組件傳值,我直接使用屬性賦值難道不比這個簡單嗎?
子組件修改數據的時候不斷對外拋事件:

====================child==================

<p>
    userName: <input @bind="UserInfo.UserName"  @oninput="InvokeChanged"/>
</p>

<p>
    sex:
    <select @bind="UserInfo.Sex">
        <option value="m">男</option>
        <option value="f">女</option>
    </select>
</p>

<p>
    BrithDay:<input @bind="UserInfo.BrithDay" />
</p>
@code {

    [Parameter]
    public UserInfo UserInfo { get; set; }

    [Parameter]
    public EventCallback<UserInfo> UserInfoChanged { get; set; }

    private void InvokeChanged()
    {
        UserInfoChanged.InvokeAsync(this.UserInfo);
        Console.WriteLine("InvokeChanged");
    }

}

父組件監聽事件后更新數據:

@page "/"
====================parent```==================

<p>
    userName: @userInfo.UserName
</p>
<p>
    sex: @userInfo.Sex
</p>
<p>
    brithday: @userInfo.BrithDay
</p>
<p>
    title: @title
</p>


<InfoEdit UserInfo="userInfo" UserInfoChanged="HandleUserInfoChanged"></InfoEdit>

@code {

    private UserInfo userInfo;

    private string title;

    protected override void OnInitialized()
    {
        userInfo = new UserInfo
        {
            UserName = "abc",
            Sex = "f",
            BrithDay = DateTime.Now
        };
        base.OnInitialized();
    }

    private void HandleUserInfoChanged(UserInfo info)
    {
        this.userInfo.UserName = info.UserName;

        Console.WriteLine("HandleUserInfoChanged");
    }


}


我原以為這樣就沒什麼問題了,可奇怪的是,父組件頁面重新渲染需要在子組件第二次修改數據后呈現且呈現的是前一次的。

到這裏我已經無語了,最後我只能在子組件直接添加一個按鈕,修改完後點擊保存來觸發InvokeChanged事件,這樣子是可以的:

====================child==================

<p>
    userName: <input @bind="UserInfo.UserName" />
</p>

<p>
    sex:
    <select @bind="UserInfo.Sex">
        <option value="m">男</option>
        <option value="f">女</option>
    </select>
</p>

<p>
    BrithDay:<input @bind="UserInfo.BrithDay" />
</p>

<button class="btn btn-danger" @onclick="InvokeChanged">保存</button>

@code {

    [Parameter]
    public UserInfo UserInfo { get; set; }

    [Parameter]
    public EventCallback<UserInfo> UserInfoChanged { get; set; }

    private void InvokeChanged()
    {
        UserInfoChanged.InvokeAsync(this.UserInfo);
        Console.WriteLine("InvokeChanged");
    }

}

到此數據綁定也演示完了,可是關於子組件往父組件傳值的事我實在沒像明白,難道是我哪裡錯了?

最後附上代碼:BlazorWasmDataBind

相關內容:
ASP.NET Core Blazor Webassembly 之 組件
ASP.NET Core Blazor 初探之 Blazor WebAssembly
ASP.NET Core Blazor 初探之 Blazor Server

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

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

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

小心 HttpClient 中 FormUrlEncodeContent 的 bug_網頁設計公司

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

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

小心 HttpClient 中 FormUrlEncodeContent 的 bug

Intro

最近發現活動室預約項目里的上傳圖片有時候會有問題,周末找時間測試了一下,發現小圖片的上傳沒問題,大圖片上傳會有問題,而且異常信息還很奇怪,System.UriFormatException: Invalid URI: The Uri string is too long 看這個錯誤的信息還以為是請求的 url 過長導致的,但是實際請求的 url 很短,詭異的異常信息

測試示例

為了方便大家了解和測試這個bug,我在 Github 上提供了一個示例 https://github.com/WeihanLi/SamplesInPractice/blob/master/HttpClientTest/FormUrlEncodeContentTest.cs

HttpClient 示例代碼:

public class FormUrlEncodeContentTest
{
    private const string TestUrl = "https://cnblogs.com";

    public static async Task FormUrlEncodedContentLengthTest()
    {
        using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
        {
            using (var response = await httpClient.PostAsync(TestUrl, new FormUrlEncodedContent(new Dictionary<string, string>()
            {
                {"bigContent", new string('a', 65535)},
            })))
            {
                Console.WriteLine($"response status code:{response.StatusCode}");
            }
        }
    }

    public static async Task ByteArrayContentLengthTest()
    {
        using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
        {
            var postContent = $"bigContent={new string('a', 65535)}";
            using (var response = await httpClient.PostAsync(TestUrl, new ByteArrayContent(postContent.GetBytes())))
            {
                Console.WriteLine($"response status code:{response.StatusCode}");
            }
        }
    }

    public static async Task StringContentLengthTest()
    {
        using (var httpClient = new HttpClient(new NoProxyHttpClientHandler()))
        {
            var postContent = $"bigContent={new string('a', 65535)}";
            using (var response = await httpClient.PostAsync(TestUrl, new StringContent(postContent)))
            {
                Console.WriteLine($"response status code:{response.StatusCode}");
            }
        }
    }
}

測試代碼:

InvokeHelper.OnInvokeException = Console.WriteLine;

await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.FormUrlEncodedContentLengthTest);
Console.WriteLine();
await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.StringContentLengthTest);
Console.WriteLine();
await InvokeHelper.TryInvokeAsync(FormUrlEncodeContentTest.ByteArrayContentLengthTest);

Console.WriteLine("Completed!");

輸出結果如下:

揪出異常始末

上傳圖片的時候會調用一個碼雲的一個 POST 接口來保存上傳的圖片,參數是通過 form-data 的方式傳遞的,在 POST 的時候報異常了,異常信息很詭異,具體信息和上面的是一樣的:

這個異常信息看上去像是 url 過長導致的,但是實際的 url 很短只有幾百,而且從調用的堆棧上來看是 FormUrlEncodedContent 的 bug,然後根據異常堆棧信息去看了一下源碼,部分源碼如下:

首先看 FormUrlEncodedContent 做了什麼:

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

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

然後再找上一層堆棧信息,Uri是一個分部類(partial),你如果直接在 Github 上 Find 的話會找到多個 Uri 相關的文件,最後在 UriExt 中找到了上面的 EscapeDataString 方法:

最後來看最上層的堆棧信息 UriHelper.EsacpeString 方法,找到異常拋出的地方

在 Uri 這個類中可以找到上面定義的 c_MaxUriBufferSize,它的值是 0xFFF0 轉成十進制就是 65520

找到問題所在之後,就可以避免這個問題了,再遇到這個問題也就知道是怎麼回事了,上面的問題就是 post 的數據太大了,超過了這個限制,所以引發的異常

More

既然知道這個是 FormUrlEncodedContent 的 bug,那麼修復它就可以通過避免使用它,可以直接使用 ByteArray Content,或者不需要 Encode 處理直接用 StringContent 也是可以的

後來在 Github 搜 issue 的時候發現也有很多人遇到了這個問題,這個問題會在 net5 中得到修復,詳見 PR https://github.com/dotnet/corefx/pull/41686

文中一些源碼的鏈接在文章最後的 Reference 的部分可以找到

Reference

  • https://github.com/dotnet/corefx/blob/release/3.1/src/System.Net.Http/src/System/Net/Http/FormUrlEncodedContent.cs#L53
  • https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/UriExt.cs#L597
  • https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/UriHelper.cs#L134
  • https://github.com/dotnet/corefx/blob/release/3.1/src/System.Private.Uri/src/System/Uri.cs
  • https://github.com/dotnet/corefx/pull/41686
  • https://github.com/dotnet/corefx/tree/release/3.1
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/HttpClientTest/Program.cs
  • https://github.com/OpenReservation/ReservationServer/commit/0262b2a6ce20c3ec12acc9548235757c18b20690#diff-dd926ccf347a255671a64f9e3edd5a88

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

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

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

這些狂拽酷炫吊炸天的自主SUV,讓合資SUV哭暈在廁所_網頁設計公司

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

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

38-15。98萬。對於無數的年輕人來說,沒有什麼能夠比低廉的價格來得更實在、吸引。7。38-15。98萬的售價意味着什麼。意味着你將能夠得到一個跟奧迪Q3完全一模一樣的外觀,在大學城把妹的時候,成功的幾率將會幾何級數地增長。

古語有雲:“走別人的路,讓別人無路可走。”這句話經常被我們引用在“一個絕世罕有的屌毛霸道且蠻不講理地侵佔別人的勞動成果,並且還自我得意洋洋“等情況上。不過讓人震驚的是,這句看似賤的毫無底線的話,竟然在我們高大上的汽車工業得到了充分的實踐。把這句話的效果發揮到淋漓精緻的,到底是哪家車企?各位吃瓜群眾趕緊來圍觀。

1雙環SRV

價格:9-12萬(停產)

真正的復刻,是不需要皮尺的,依靠的,僅僅是設計師的靈魂。雙環SRV就是基於這一哲學宗旨所誕生的偉大作品。之所以說它偉大,是因為在那個本田CRV風靡全國,動不動加價好多萬的年代(2004年左右),雙環汽車深知道普羅大眾錢包緊張的窘迫,頗為上進地不知道從哪裡搞來一輛本田CRV,照貓畫虎地造出了外形跟本田CRV一模一樣的雙環SRV。而這款車上市的時間,正正是東本本田國產CRV的那一年(2004年)。這樣的速度,起碼得堪稱是奇迹吧?

相對於本田CRV二十多萬的售價,雙環SRV的售價只需要9-12萬,這麼便宜的一輛城市SUV,瞬間就在全國各地炸開了鍋。不僅如此,雙環SRV還標誌了在當時看起來特牛逼的實木內飾、自動空調、真皮座椅等配置,哄得消費者不要不要的。據悉在很長的一段時間,雙環汽車有90%以上的銷量都是由雙環SRV所貢獻的。本田看不過眼了,開始了曠日持久的侵權狀告,但不曾讓人意料到的是,法院在2015年判決本田反賠償雙環汽車1600萬。

2眾泰SR7

價格: 7.38-15.98萬

相比起雙環SRV這種泰山級人物,眾泰SR7顯然就是一個不折不扣的的小學雞。不過兩者名字上的不謀而合,

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

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

似乎也已經昭示了眾泰SR7的非凡實力。當奧迪絞盡腦汁在思考如何慫恿更多的年輕人去購買旗下的緊湊型SUV—Q3的時候,拿着皮尺的眾泰設計師們便已經提前幫奧迪解決了這一個煩惱。為什麼?因為眾泰SR7的售價僅需要7.38-15.98萬。對於無數的年輕人來說,沒有什麼能夠比低廉的價格來得更實在、吸引。

7.38-15.98萬的售價意味着什麼?意味着你將能夠得到一個跟奧迪Q3完全一模一樣的外觀,在大學城把妹的時候,成功的幾率將會幾何級數地增長。其次,你能夠擁有一塊媲美特斯拉的12寸超大中控屏,你與那些開着特斯拉的人一樣,都是科技生活最忠實的擁躉。再次之,你能夠得到上坡輔助、自動駐車、全景天窗、全景攝像頭、定速巡航、電動座椅等一系列在這個級別車型上屬於無法想象的酷炫屌配置。這樣一輛的車,能夠體現出你非凡的豪華品位,能夠滿足你對前沿科技的渴望,能夠讓你毫無負擔地踏入一個更高層次的用車體驗。

3眾泰SR9

價格:未知

眾泰是中國近代汽車工業史上,最具有典型意義的汽車品牌。因為它不僅能抄、會抄,更重要的是,在他眼中,模仿是不應該有條條框框的,模仿是一項藝術。如果說眾泰SR7的推出,讓中國的年輕人提前50年踏入了豪門。那眾泰SR9的推出,無疑是讓中國的年輕人提前100年踏入了豪門。你是否擁有一個可望不可即的保時捷夢?如今眾泰的“保時捷”來了。

根據最新的消息,外形已經最終確定的眾泰SR9與保時捷的高性能SUV—Macan高度一致,各位悶騷的少年只需要後期到汽配城自行更換車標,即可升級為保時捷Macan。動力方面配備了2.0T的發動機,變速箱有5MT以及6DCT可選,要跑贏宏光S相信不是一件難事。至於在配置上,眾泰SR9再度實現了高度的突破,分別搭載了自動大燈、19寸鋁合金輪轂、全景天窗、車道偏離、盲點監測、電動尾門,做到了完全對標保時捷Macan。在大家關心的價格上,眾泰官方還沒有透露,不過根據以往的定價猜測,起售價應該會在11萬的範圍。不過我還是有一點建議,如果眾泰是不是應該把保時捷的車標作為後期選裝件?

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

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

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