碎片化的時代,如何學習

今天周末,和大家聊聊學習這件事情。

在如今這個社會,我們的時間被各類 APP 撕的粉碎。

刷知乎、刷微博、刷朋友圈;

看論壇、看博客、看公號;

等等形形色色的信息和知識獲取方式一個都不錯過。

貌似學了很多,但是卻感覺沒什麼用。

要解決上面這些問題,首先要分清楚一點,什麼是信息,什麼是知識。

  • 那什麼是信息呢?

你一切聽到的、看到的,都是信息,比如微博上的明星出軌、微信中的表情大戰、抖音上的段子視頻。

  • 那什麼是知識呢?

就是指那些被驗證過的、正確的、被人們相信的概念、規律、方法論。

概念是什麼很好理解,我們上學的時候做的最多的一件事情就是背概念。

規律是事物背後的運行法則,例如當市場上某一種貨物供應量減少后,就會導致價格的上升。

方法論俗稱【套路】,解決某一類問題的時候,有效的解決方案。

信息有真假,有時效,而知識有積累、有迭代。我們要學習的是知識,而不是信息。

時間複利

再說一個概念,複利

這個大家都應該知道,比較多的應該是在銀行存錢或者是購買理財產品的時候,比如下圖的銀行複利增長曲線:

或者是下面這個公式:

1.01^365 = 37.8

隨公式還附贈一句雞湯:

如果一個人每天都能進步 1%,一年之後他的能力會提升 38 倍。

這句話雖然聽起來很雞湯,但是卻想不到有什麼問題,對吧?

反過來想一下這句話的前提,一個人都想要每天都能進步 1% ,這可能么?當然排除一些極端情況,對於看到我文章的大多數人來說,這是一件不可能的事情。

每天會形形色色的事情去阻止我們進步這 1% ,比如:

  • 今天上班被領導批了,心情不好,不想學習了
  • 今天工作太忙了,下班后時間都比較晚了,想要休息了
  • 今天和朋友一起出去玩了,玩的很開心

等等,然後我們看到身邊優秀的人的時候:

  • 看到他們隨隨便便就考試考出來好成績
  • 看到他們隨手寫的文章就是網絡爆文
  • 看到他們對於某一項技術非常精通的時候

是不是會有一種無力感,好像他們平時和我們一樣,該吃吃該喝喝該玩玩,但是人家就是很厲害的樣子。

於是,就產生了強烈的焦慮感,要學習,要提高自己,開始看更多的信息,關注更多的學習圈子,焦慮感加重,負向循環開始了。

但是,如果你肯相信時間複利的效應,就不會焦慮。

Why?

你現在看到身邊的人的成績,看到他輕輕鬆松做到的事情,即使你拼盡全力也不可能做到。

從一開始你就錯了,他們已經完成了自己的原始積累,他們已經到達了這件事情的複利拐點

什麼是複利拐點?

圖中這個小人站的位置就是複利拐點,

如果一個人到達複利拐點(圖中小人站的地方),那他的收益,會急劇增長,可能比之前所有時間的收益總和還要多。

這裏的收益可以是錢,是能力,是認知。

知識

上面我們介紹了如何區分信息和知識,這裏再分享一個個人理解,有效知識。

判斷一個知識是否有效,就要看這個知識和你之前的已有的知識是否能產生聯繫。

如果可以產生聯繫,那麼你對這個新的知識理解速度會非常的快。

就好比編程語言,小編的本職工作是一名 Java 軟件工程師,但是當小編去學習 Python 的相關基礎知識的時候,速度是非常快的,用旁人的視角看起來就好像小編的學習效率非常的高效。

當然,我們不可能只學習和自己已有知識相關方向的知識,可能會接觸一些完全陌生的領域,那麼這個時候如何還能保持效率較高的學習?

在進入一個新的領域的時候,所有的知識都是一個點一個點的,好像是散落在沙子里的石頭,中間是毫無關聯的,這個時間段的學習是非常痛苦的,小編一般稱為原始積累階段。

很多人在原始積累的階段因為過程過於痛苦,就慢慢的放棄了。

在痛苦的原始積累階段,很多時候看不到盡頭,之前的學習感覺都學過,但是仔細一想,又好像什麼都沒學。

這時,我們可以藉助工具去加強知識之間的關聯和加深自己的記憶——思維導圖。

這個工具小編也經常在用的,比如很多人可能都見過我的公眾號上小編自己整理的 Java 進階相關的思維導圖。

當學習一個新的領域的時候,每當一些基礎的概念能產生聯繫的時候,就可以去畫這麼一張思維導圖,思維導圖能讓我們更清晰直觀的理解不同的事物之間的內在聯繫。

長時間的原始積累太過痛苦怎麼辦?

這個是所有人都會遇到的問題,做一件事情,尤其是學習,當我們無法取得一些成果的時候,對興趣和自信的打擊都會非常的大。

首先在做這件事情之前,你需要為自己找到足夠開始這件事情的理由,也就是要有強大的內驅力。

就好比考研這件事情,如果是身邊的人要考,所以你也要考,那麼我覺得你到底要不要考研這件事情值得再思考一下。

但如果是說你想通過考研,來改變自己的人生軌跡,想要獲得更高的起點,那麼,我覺得這個事兒十有八九你是能堅持下去的。

好比學習 Python ,好像身邊的人都在學,那我也要了解一下,和那種我想要學 Python 來換一份工作,擺脫目前的工作狀態,獲取更高的薪水。

大家可能看着沒什麼感覺,但是想一想,自己的人生中,到底有沒有過堅持某一件事情,並最終獲得了一個還不錯的結果,最後收穫的這份快感,是不是無與倫比的。

當然,除了強大的內驅力以外,小編還可以友情提供一點小技巧。

獲取階段性的正向反饋。

如果一件事情需要耗費較長的時間,那麼再大的內驅力也可能會被時間的流逝給磨平了。

靜靜思考下放棄的原因,沒有獲得成就感。

還是拿考研舉例子,一般考研需要準備大半年到一年左右的時間,很多人都堅持不過半年時間就放棄掉了,為什麼,因為他們看不到成效,看不到曙光。但是高三的高考很多人還是能堅持下來的,為什麼?

因為高三有月考啊,每次月考完,都能準確的知道自己的水平提升了多少,自己一個月的努力是沒有白費的,自己一個月的努力是真真實實的化成了卷子上的分數。

所以,做一件需要長時間奮戰的事情,最好能提前為自己設定一些階段性的成果檢驗方式。

時間管理

時間管理是一個繞不開的話題,這裏小編其實也沒資格談這件事情,因為小編本身的時間管理也做的並不好,小編也是人,加完班也會感覺到累,回到家也會只想着休息,人非聖賢,對吧。

還是分享一些經驗吧。

嘗試將自己一天做的事情和耗費的時間列一個表格出來(小編之前列舉過,時間有些久遠,找不到了,這裏就不放圖了)。

當這個表格列出來以後,不管多麼自律的人,肯定會發現,一天之中,有相當部分的時間是被浪費掉的,比如刷朋友圈,刷抖音,刷微博。

在生活中,肯定會經常性的出現這樣事情。

“再玩5分鐘手機就睡覺!”

結果12點了還在玩手機。

周末早晨起來,“先玩一局遊戲,在做xxx”。

結果就是玩到了下午。

相信我,那些墮落的人,並不是一開始就想墮落的。

他們只是在被生活中形形色色的誘惑給誘惑到了,因為現在的社會,各個 APP 在掏空了心思去搶佔用戶的留存時長,它們費勁心力的去討好用戶,讓用戶用最簡單最不需要付出的方式去獲得這種毫無意義的低成本的快樂。

所以,開始記錄自己的時間,是做時間管理的第一步。

當然,並不是要我們完全的放棄娛樂時間,這不可能,人不是機器,不可能是只要有電,就能工作,人也是需要休息的,記錄時間只是為了讓我們在面臨選擇的時候,做出正確的選擇。

第一次寫這種長篇內容分享,有內容不當的地方請各位同學海涵。

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

SpEL + AOP實現註解的動態賦值.

一、自定義註解

先聊聊這個需求,我需要根據用戶的權限對數據進行一些處理,但是痛點在哪裡呢?用戶的權限是在請求的時候知道的,我怎麼把用戶的權限傳遞給處理規則呢?想了以下幾種方案:

  1. Mybatis 攔截器:如果你的權限參數可以滲透到 Dao 層,那麼這是最好的處理方式,直接在 Dao 層數據返回的時候,根據權限做數據處理。
  2. Dubbo 過濾器:如果 Dao 層沒辦法實現的話,只好考慮在 service 層做數據處理了。
  3. ResponseBodyAdvice :要是 service 層也沒辦法做到,只能在訪問層數據返回的時候,根據權限做數據處理。(以下介紹的正是這種方式)

那麼現在有個難點就是:我怎麼把 request 的權限參數傳遞到 response 中呢?當然可以在 Spring 攔截器中處理,但是我不想把這段代碼侵入到完整的鑒權邏輯中。突然想到,我能不能像 spring-data-redis 中 @Cacheable 一樣,利用註解和 SpEL 表達式動態的傳遞權限參數呢?然後在 ResponseBodyAdvice 讀取這個註解的權限參數,進而對數據進行處理。

首先,我們需要有個自定義註解,它有兩個參數:key 表示 SpEL 表達式;userType 表示權限參數。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseSensitiveOverride {

    /**
     * SPEL 表達式
     *
     * @return
     */
    String key() default "";

    /**
     * 1:主賬號、2:子賬號
     */
    int userType() default 1;
}

然後,把這個註解放在路由地址上,key 寫入獲取權限參數的 SpEL 表達式:

    @ResponseSensitiveOverride(key = "#driverPageParam.getUserType()")
    @RequestMapping(value = "/queryPage", method = RequestMethod.POST)
    public ResponseData<PageVo<AdminDriverVo>> queryPage(@RequestBody AdminDriverPageParam driverPageParam) {
        return driverService.queryPageAdmin(driverPageParam);
    }

二、SpEl + AOP 註解賦值

現在 SpEL 表達式是有了,怎麼把 SpEL 表達式的結果賦值給註解的 userType 參數呢?這就需要用 、 和 的知識。

@Aspect
@Component
public class SensitiveAspect {

    private SpelExpressionParser spelParser = new SpelExpressionParser();

    /**
     * 返回通知
     */    
    @AfterReturning("@annotation(com.yungu.swift.base.model.annotation.ResponseSensitiveOverride) && @annotation(sensitiveOverride)")
    public void doAfter(JoinPoint joinPoint, ResponseSensitiveOverride sensitiveOverride) throws Exception {
        //獲取方法的參數名和參數值
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        List<String> paramNameList = Arrays.asList(methodSignature.getParameterNames());
        List<Object> paramList = Arrays.asList(joinPoint.getArgs());

        //將方法的參數名和參數值一一對應的放入上下文中
        EvaluationContext ctx = new StandardEvaluationContext();
        for (int i = 0; i < paramNameList.size(); i++) {
            ctx.setVariable(paramNameList.get(i), paramList.get(i));
        }

        // 解析SpEL表達式獲取結果
        String value = spelParser.parseExpression(sensitiveOverride.key()).getValue(ctx).toString();
        //獲取 sensitiveOverride 這個代理實例所持有的 InvocationHandler
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(sensitiveOverride);
        // 獲取 invocationHandler 的 memberValues 字段
        Field hField = invocationHandler.getClass().getDeclaredField("memberValues");
        // 因為這個字段是 private final 修飾,所以要打開權限
        hField.setAccessible(true);
        // 獲取 memberValues
        Map memberValues = (Map) hField.get(invocationHandler);
        // 修改 value 屬性值
        memberValues.put("userType", Integer.parseInt(value));

    }
}

通過這種方式,我們就實現了為註解動態賦值。

三、ResponseBodyAdvice 處理數據

現在要做的事情就是在 ResponseBody 數據返回前,對數據進行攔截,然後讀取註解上的權限參數,從而對數據進行處理,這裏使用的是 SpringMVC 的 ResponseBodyAdvice 來實現:

@Slf4j
@RestControllerAdvice
@Order(-1)
public class ResponseBodyAdvice implements org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return SysUserDto.USER_TYPE_PRIMARY;
        }
    };

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        if (returnType.hasMethodAnnotation(ResponseSensitiveOverride.class)) {
            ResponseSensitiveOverride sensitiveOverride = returnType.getMethodAnnotation(ResponseSensitiveOverride.class);
            threadLocal.set(sensitiveOverride.userType());
            return true;
        }
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body != null && SysUserDto.USER_TYPE_SUB.equals(threadLocal.get())) {
            // 業務處理
        }
        return body;
    }
}

題外話,其實我最後還是擯棄了這個方案,選擇了 Dubbo 過濾器的處理方式,為什麼呢?因為在做數據導出的時候,這種方式沒辦法對二進制流進行處理呀!汗~ 但是該方案畢竟耗費了我一個下午的心血,還是在此記錄一下,可能有它更好的適用場景!

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

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

[UWP]通過自定義XamlCompositionBrushBase實現圖片平鋪

1. 什麼是XamlCompositionBrushBase

我早就想試試自定義XamlCompositionBrushBase,但一直沒機會。上一篇文章介紹到,原理很簡單,但每次都要寫這些代碼很繁瑣,正好就用這個作為例子試試XamlCompositionBrushBase。

CompositionBrush靈活多變,它的基本用法如下:

  1. 通過Compositor創建CompositionBrush;
  2. 配置CompositionBrush;
  3. 創建SpriteVisual並將它的Brush設置為CompositionBrush;
  4. 使用 將SpriteVisual設置到某個UIElement的可視化層里。

這些步驟很繁瑣,而且不能用在XAML中。XamlCompositionBrushBase提供了將CompositionBrush用在XAML中一個橋樑,他繼承自Brush類,可以直接像普通的XAML 畫筆(如SolidColorBrush)那樣直接用在XAML中。

如上圖所示,中已經提了很不少XamlCompositionBrushBase的實現,它們的使用方式已經有很多文章介紹,這裏不一一列舉。

2. 自定義XamlCompositionBrushBase

這篇文章將介紹一個自定義的畫筆:TiledImageBrush,它的主要目標是實現ImageBrush沒有的圖片平鋪功能,並且它可以在XAML中使用,使用方式如下:

<Rectangle IsHitTestVisible="False">
    <Rectangle.Fill>
        <controls:TiledImageBrush Source="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

順便複習下普通的ImageBrush的用法:

<Rectangle >
    <Rectangle.Fill>
        <ImageBrush ImageSource="ms-appx:///Assets/flutter.png"/>
    </Rectangle.Fill>
</Rectangle>

看起來TiledImageBrush的用法是不是和ImageBrush很像?接下來講解TiledImageBrush的實現步驟。TiledImageBrush繼承自XamlCompositionBrushBase,而實現XamlCompositionBrushBase的一般步驟如下:

protected override void OnConnected()
{
    // Delay creating composition resources until they're required.
    if (CompositionBrush == null)
    {
         CompositionBrush = CreateCompositionBrush();//Create A CompositionBrush.
    }
}

protected override void OnDisconnected()
{
    // Dispose of composition resources when no longer in use.
    if (CompositionBrush != null)
    {
        CompositionBrush.Dispose();
        CompositionBrush = null;
    }
}

首先重寫,當畫筆在屏幕上首次用於繪製元素時會調用這個函數。在這個函數里創建CompositionBrush並賦值給。

然後重寫,它在畫筆不再用於繪製任何元素時被調用。在這個函數里盡可能地釋放各種資源,例如CompositionBrush。這兩步就是實現XamlCompositionBrushBase的基本步驟。

創建CompositionBrush有很多種玩法,我之前寫過兩篇文章分別介紹 及 。這裏使用這篇文章里介紹到的代碼,首先使用LoadedImageSurface.StartLoadFromUri創建CompositionSurfaceBrush,然後加入到BorderEffect里實現圖片平鋪,然後把產生的CompositionEffectBrush賦值給XamlCompositionBrushBase.CompositionBrush

TiledImageBrush中添加了Source屬性用於設置圖片Uri(實際上是個ImageSource類型),模仿ImageBrush,這裏的Source也是一個ImageSource類型的屬性,雖然實際上使用的是它的UriSource。詳細代碼如下:

public ImageSource Source
{
    get => (ImageSource)GetValue(SourceProperty);
    set => SetValue(SourceProperty, value);
}

private void UpdateSurface()
{
    if (Source != null && _surfaceBrush != null)
    {
        var uri = (Source as BitmapImage)?.UriSource ?? new Uri("ms-appx:///");
        _surface = LoadedImageSurface.StartLoadFromUri(uri);
        _surfaceBrush.Surface = _surface;
    }
}

OnConnected的詳細代碼如下:

protected override void OnConnected()
{
    base.OnConnected();

    if (CompositionBrush == null)
    {
        _surfaceBrush = Compositor.CreateSurfaceBrush();
        _surfaceBrush.Stretch = CompositionStretch.None;

        UpdateSurface();

        _borderEffect = new BorderEffect()
        {
            Source = new CompositionEffectSourceParameter("source"),
            ExtendX = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap,
            ExtendY = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap
        };

        _borderEffectFactory = Compositor.CreateEffectFactory(_borderEffect);
        _borderEffectBrush = _borderEffectFactory.CreateBrush();
        _borderEffectBrush.SetSourceParameter("source", _surfaceBrush);
        CompositionBrush = _borderEffectBrush;
    }
}

這樣一個基本的XamlCompositionBrush就完成了,完整的代碼可以在這裏查看:

3. 參考

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

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

計算機網絡原理超詳解

計算機網絡重磅來襲——一文讓你撥開迷霧,直擊網絡原理

前言

為了保持學習網絡編程的連貫性和系統性,我在之前已經更新了《》《》,我只能說寫的確實很詳細,能讓你全盤吸收,不過你要沒看也不要緊,我會在此篇再來一個前情回顧,不會像上兩篇那樣詳細,但也能讓你理解。

一、前情回顧——計算機網絡概述

時代不同了,大家現在生活都好了,家家戶戶都有電腦了,而且我們都習慣了使用電腦辦公,打打遊戲,聊聊天。那我們來想一個事兒:如果沒有了網絡,我們是不是就不能使用電腦進行聊天了啊。那網絡到底是如何幫助我們來完成網絡聊天的?此篇我就跟大家繼續聊計算機網絡到底是怎麼回事兒。

這回我和以往反着來,先說官方定義,再解釋:

計算機網絡是由通信介質地理位置不同的且相互獨立的計算機連接起來,實現數據通信資源共享

我們知道兩個獨立且毫不相關的計算機,一台在青海,一台在河南,想要進行數據傳輸(聊天),沒網是不行的。這個網指的就是互聯網(Internet)。這個Internet它是一個通信協議。什麼是協議?打個比方,就好比我們打電話,在中國,中國有十幾億人,地大物博,全國各地都有自己的方言,還有些地方使用自己的民族語言,這時候想要良好的溝通就必須使用統一的一個標準,就是普通話。大家都講普通話,溝通起來就沒有問題了。那如果是不同國家進行溝通呢?我們可以選擇使用英語進行交流,那英語就是全世界國家通用的一個標準,計算機就好比是分佈在全世界各個角落的人,計算機之間通話也要找一個統一的標準,這個標準就是Internet標準,又叫做Internet協議。

二、網絡介紹及分類之隔壁老王的故事

先給大家講一個虛構版隔壁老王的故事:
有一個人叫隔壁老王,他有一個愛好就是看電影。有一天,這個隔壁老王想看一部電影,可是電腦裏面存儲的電影太多了,他費了老大勁才从里面找到,覺得很不爽。於是他想,我能不能把所有電影做個分類,把同一種類型的電影放在同一個文件夾下,然後把所有的文件夾整合在一起,自己寫個瀏覽器軟件,把文件夾信息放到瀏覽器上,到時候找電影的時候就好找了,只要找到相應的文件夾直接點進去就能找到電影了,老王說干就干,沒多久就把所有的文件夾整理好了,然後把所有文件夾放到同一個頁面上,到時候他想點哪個就點哪個,So easy!(那些網站可能就是這樣來的)。

這回出來一個新人物,就叫小王吧,不是小王八,老王就是住在小王隔壁,有一天小王看到老王電腦上有那麼多電影,就跟老王商量,能不能在他電腦上也弄個跟老王一樣的,讓他也看看。老王也不是個小氣的人,好東西就是要分享的嘛,想都沒想就答應了,給小王說在你電腦上插根線接到我電腦上,然後下載我這個軟件,直接訪問我的電腦就行了。小王很高興,他馬上回家按老王說的做,沒多久他的電腦上也能看老王電腦上的東西了。有了小王,就還會有小張,小李,互相插根網線,他們都能互相共享電影了。這樣,局域網也就產生了,就比如在一個公司或者一個學校用的網絡,都稱為局域網。那學校有很多呀,不同的學校用的局域網是不同的,不同城市的有不同的局域網,以局域網為單位,散布在一個區或一個城市的各個局域網加一起叫城域網,然後全世界所有城市的城域網加一起就叫廣域網

過了一段時間,老王去小王家,看到小王電腦里的電影比自己的還多,而且還好看,然後老王跟小王說,把你的這些電影給我一份吧,我也想看看。不用想,小王肯定立馬答應了,說你自己弄吧,想看哪個隨便看。姜還是老的辣,老王想到一個好辦法,他跟小王說,我再找一台電腦,把我倆電腦上的資源全部放到那台電腦里,我倆只要在那個電腦上插根線連到自己電腦上,就都能訪問那電腦上的東西了,這樣豈不是很方便。小王一拍腦門,呀!我咋就沒想到,那還不趕緊的。老王另外找了一台電腦,然後他倆把自己的電腦和那台電腦用線連起來,再把自己電腦里的所有資源全傳進那台電腦中,最後他倆就能共享資源了。(那台電腦就是服務器)(附加內容)

總結一下就是:

 

網絡按地域分類:根據參照物不同、類型不同分為

  • 局域網:一個公司、一個家庭、一個學校······
  • 城域網:一個地區、一個城市······
  • 廣域網:一個國家、全世界······

三、互聯網協議是如何分佈和設計的

我在上面說了Internet協議,互聯網協議按照功能的不同,分為osi七層tcp/ip五層tcp/ip四層協議。如下圖:

osi的七層協議體繫結構的概念清楚,理論也比較完善,但它既複雜又不實用,ISO制定的osi協議參考模型的過於龐大、複雜招致了許多批評。於此對照,由技術人員自己開發的TCP/IP協議獲得了更為廣泛的應用。因此,我們只需要弄明白TCP/IP五層協議就能了解和明白計算機最底層的通信是怎麼回事。

 

四、TCP/IP五層協議

如圖,從最下方的物理層到最上方的應用層,對於我們用戶而言,最直接的是應用層。從上到下每一層都依賴於下一層,所以我從最下一層開始給大家講解:

 

注意:每一層都運行着一個特定的協議,共同組合成互聯網協議

一、物理層

物理層主要是由雙絞線光纜電纜無線電波組成,其作用很簡單,就是連接不同的計算機,並傳遞底層電信號,高電壓:1,低電壓:0。

二、數據鏈路層

我們從物理層上接收或者發送單純的0、1是沒有意義的,為什麼呢?想想哈,我想給女朋友發送一句話:“你好漂亮”,那我們要把“你好漂亮”轉換成01之後,交給網卡,網卡就懵逼了,發給誰???不知道。那怎麼辦?必須要確定數據發給誰。就像我們的快遞一樣,是不是在外層包裝上有商家地址和個人地址,這樣我們不管是還是,都能準確定位了。網絡傳送數據也一樣,我們就在數據前面加上目標地址,為了能接收到回信,也要把自己的地址也加上。但是,如果數據和地址放在一起,又亂了,比如,我給你一堆01,1010101000101010101,你也分不清哪裡是數據,哪裡是地址。這時我們就要對要發送的01進行分組,規定前面xxx位是地址,後面xxx位是數據,並且,大家想互相都能通信,就必須都遵守這樣的規則(協議),這個協議叫以太網協議。在以太網協議出現之前,各個公司都有自己的分組規則,後來都統一使用以太網協議了。

以太網協議規定:一組電信號構成一個數據包,叫,每一幀分為報頭(head)和數據(data)兩部分。


報頭(head):固定18個字節

發送者/
源地址: 6個字節

接收者/
目標地址: 6個字節


數據類型:6個字節


數據(data):最短46個字節,最長1500字節

數據包的具體內容(發送給女朋友的話/快遞貨物)

 

以太網協議中的地址叫MAC地址,MAC地址是每台計算機唯一的物理地址,是被寫在網卡上的。以太網協議規定,每一台接收和發送數據的設備必須要裝有網卡,負責發送和接收數據的設備,發送端和接收端的地址,指的就是網卡的地址,即MAC地址

MAC地址

MAC地址是每個網卡在出廠的時候,由各個廠商直接燒錄在網卡上的,而且,這個地址必須是全世界唯一的。
MAC地址是由12位16進制的數字錶示(前六位是廠商編號,后六位是流水線號),這樣不同的廠商之間就不會產生衝突了,自己生產自己的就好了。

交換機

在這給大家介紹一個東西,我們說兩個電腦要通信要先連根線,但是如果電腦多了之後,電腦間通信連的線也就多了,這樣太亂了,我介紹的這個東西就是交換機,它是負責組件局域網,研究的是MAC地址,它有什麼用你看下面圖片中的接口就知道了

有了MAC地址,以太網就可以進行工作了.理論上講,我們可以和世界上的每一台連接了互聯網的計算機進行通信了,此時通信的方案是:廣播

廣播

廣播又是怎麼一回事?其實廣播的方式很原始,基本通信就是靠吼。就像你想跟女朋友求婚一樣,你會大喊:“xxx,嫁給我吧”,旁邊能聽到這句話的人有很多,但是只有你女朋友會回復你。其他人會把你當傻逼一樣看待。
沒錯,廣播就是這樣進行通信的。首先組織好了一個數據包之後,把這個數據包通過電信號發出去,這時整個網絡上所有的人都會收到你發的這條數據,然後看看這個數據是不是自己的。如果不是就當他不存在,如果是,就接收。雖然效率低點,但畢竟能通信了。

廣播帶來什麼問題呢?如果是在一個小的網絡環境里。比方說,你們宿舍幾個人,一起玩CS,沒問題,你喊一嗓子,你室友也能回應你,也就效率低點。但是如果你連接到全世界的互聯網上,還使用廣播的方式來通信,就不是效率問題了,而是一個巨大的通信災難。全世界60多億人,每個人吼一嗓子,每個人發送一條信息,那每個人都會收到60多億條信息,網絡瞬間癱瘓,這種問題被稱為廣播風暴,那如何解決呢?

三、網絡層

首先,我們要了解一個事情,世界大網絡(廣域網)是由一個一個的互相隔離的小型局域網(子網)組成的,不同的局域網之間使用路由來連接。

 

路由器

上面說的交換機是負責組建局域網,研究的是MAC地址,而路由器是負責組件廣域網,研究的是IP地址,這個IP地址下面我再解釋。

剛才說的廣播,只能在一個局域網內進行通信,不可以在大網絡上進行廣播,有了路由器,就避免了廣播風暴的問題。每個局域網被稱為一個廣播域,局域網和局域網之間使用路由的方式進行通信(向不同的廣播域/子網發送數據包),用路由器把一個局域網裡的所有計算機劃分成一個個子網。

 

現在大家先想一想生活中,如果是之前說的廣播,就相當於在一間教室里你正在上課,你要給某個女孩傳紙條,那你要在紙條外面寫上你和你要傳的女孩的名字,再在裏面寫上你要寫的話,寫好后折起來,你和女孩的名字在外,內容在內,然後讓同學一個一個幫你傳,每個同學在傳的時候都會看外面寫的名字,如果不是他,就繼續傳,直到傳到的人是你寫的那個女孩為止。那這間教室就相當於一個局域網。好,現在我們要實現局域網和局域網之間的通信,比如此時位於青海的你要給河南的朋友送東西,你不可能用廣播的方式了,你只能快遞,這個青海和河南以及還有很多不同的省份是我國的土地劃分,每一個省也可以看作是一個個局域網,那在網絡中,我國就相當於廣域網,那不同的局域網是如何劃分的呢?MAC地址是沒辦法區分的,因為MAC地址上只有廠商的流水號,這就引出我要說的網絡層,網絡層引出了一套新的地址來區分不同的局域網/子網,這一套地址就是網絡地址。

規定網絡地址的協議叫IP協議,它定義的地址叫IP地址。其實跟我國的省份名差不多,繼續說送快遞,你要把送的東西包裝好,在外面寫上你自己的地址和省份地址,還有朋友的地址和省份地址,交給青海的快遞公司,然後青海的快遞公司轉交給河南的快遞公司,最後由河南的快遞公司分配給你朋友,你朋友就收到快遞了。在這有兩點需要注意:

  • 你要同時寫兩個地址,自己的地址(MAC地址)和省份地址(IP地址),這樣就能確定所要接收和發送人的具體的位置了。
  • 青海的快遞公司和河南的快遞公司就相當於路由器
子網掩碼

在這給大家普及一下子網掩碼,我們上面說有了IP地址MAC地址,我們就能讓任何計算機之間進行通信了,那現在再想想,如果我要用我的計算機給另外一台計算機實現通信,我是不是要判斷要通信的計算機是否和我的計算機在同一個IP地址中,相當於上面例子中我和我要送東西的朋友是否在一個省,這個時候就需要用子網掩碼,我拿着我的IP地址和對方的子網掩碼通過計算,判斷是否在同一個IP地址下,如果在同一個IP地址下,我可以用廣播的形式進行通信,如果不在同一個IP地址下,我可以先把數據傳給我方的路由器,再由我方的路由器把數據傳給對方的路由器,最後由對方的路由器把數據傳給要接收數據的計算機。這樣說,再聯繫上面已經說過的,我想不難理解了。

IP地址

目前我們普遍使用的是IPV4,它規定,一個網絡地址由32位二進制組成,把32位平均分成四份,每一份8位,8位最大能表示的數據是255,所以IP地址的範圍:0.0.0.0-255.255.255.255.

一個IP地址分為兩部分,分別是網絡位主機位
網絡位用來標識不同的子網
主機位用來標識子網下主機的編號

為什麼要分兩部分呢?很簡單,就好比你想寫信給你的女朋友,假設你女朋友的地址是西寧市平安路128號,那麼網絡位就會直接找到西寧市,主機位幫你找到你的女朋友。

網絡位和主機位是如何劃分的?使用子網掩碼來劃分。子網掩碼和IP地址差不多,都是由32位二進制數來表示,子網掩碼也分為網絡部分主機部分,網絡部分由1組成,主機部分由0組成。

那說了這麼多,IP協議是如何發送數據的?協議規定,IP協議使用IP數據包進行發送數據。IP數據包同樣把數據分為了兩部分,headdata,並且在發送數據的時候,直接用IP數據包直接裝載以太網的data部分。

head:長度為20到600字節
data:最長為65515字節
而以太網數據包的“數據”部分,最長只有1500字節,因此,如果IP數據包超過了1500字節,它就需要分割成幾個以太網數據包,分開發送了。

 

再回顧一下啊,以太網的頭是包含了自己的MAC地址目標MAC地址的,那如何查找目標MAC地址呢?前輩們弄了一個叫ARP協議的東西專門來解析目標MAC地址。它是如何工作的?首先,它是數據鏈路層的東西,在我們發送一個數據包的時候是包含着對方的IP地址的。例如我(172.13.4.58)想發送一條數據給女朋友(172.13.4.90),首先,我得先拿到女朋友的MAC地址才可以通信。此時,我們會先計算一下我和女朋友是否在一個子網內(子網掩碼)

  • 在一個子網內,直接廣播發送一個數據包

    子網內的計算機發現了這個包之後會返回一個數據包並且帶有MAC地址,這樣就通過IP地址找到了目標主機的MAC地址,接下來就可以進行數據傳輸了。

  • 不在一個子網內,單純的用廣播就不行了,因為廣播只是針對自己內網而言。那怎麼辦,此時會把數據包發給網關,由網關發給其它路由,這樣在整個萬維網裡就可以找到你想要的那個計算機的MAC地址了。
總結
  • ARP就是通過IP地址來查找MAC地址的一套固定協議,它是數據鏈路層的內容。
  • 網絡層的意義:定義了子網, 區分各個局域網
  • IP地址:網絡地址
  • 子網掩碼:計算是否是同一個子網

四、傳輸層

到目前為止,前三層內容已經可以進行數據傳輸了。但是,我們的一台計算機上可以一次性運行多個網絡應用程序,比如,QQ、微信、LOL三個軟件一起運行,都要進行網絡傳輸,但是就前面學習的這三層內容,是沒辦法區分開數據是要發送給哪一個軟件的。那怎麼辦?引入第四層,傳輸層,傳輸層定義了端口的概念,每一個網絡應用程序佔用一個網絡端口,不同的程序就用端口把數據隔離,兩兩互相不影響。

  • 端口:應用程序和網卡的關聯編號
  • 傳輸層:建立端口到端口的通信。
  • 傳輸層有兩種協議:TCPUDP
TCP協議

TCP協議:可靠傳輸,TCP數據包沒有長度限制,理論上可以無限長,但是為了保證網絡的效率,通常TCP數據包的長度不會超過IP數據包的長度,以確保單個TCP數據包不必再分割。

TCP頭放的主要是
源端口
目標端口

 

UDP協議

UDP協議:不可靠傳輸,“報頭”部分一共只有8個字節,總長度不超過65535字節,正好放進一個IP數據包。

其實和郵信是一樣的,寫好地址,寫好接收人,直接裝進信封里,丟進郵箱里就不用你管了,對方什麼時候收,收沒收到,你不知道。

 

五、應用層

用戶使用的都是應用程序,均工作於應用層,大家都可以開發自己的應用程序,數據多種多樣,必須規定好數據的組織形式。對於用於而言最直觀的就是應用層。

  • 應用層:規定應用程序的數據格式

:TCP協議可以為各種各樣的程序傳遞數據,比如Email、WWW、FTP等,那麼,必須有不同協議規定电子郵件、網頁、FTP數據的格式,這些應用程序協議就構成了“應用層”。

總結

以上是對TCP/IP5層協議的解讀,總結一下:

發送數據其實就是一個封裝數據的過程

最後從物理層發出,對方接收到了之後再自下而上一層一層打開拿到數據,以上內容就是一個網絡傳輸的大致過程,其中還有好多細節沒有闡述,但大家知道和了解以上內容,對開發而言足夠了。

 

結束

本來還想加上TCP協議的三次握手四次斷開,奈何考慮到篇幅過大,對讀者不利,權衡再三,決定單獨去寫這部分。

看完有收穫?那麼希望老鐵別吝嗇你的三連擊哦
1、點個推薦,讓更多的人看到這篇文章
2、關注我的原創微信公眾號【泰斗賢若如】,第一時間閱讀我的文章
3、歡迎關注我的博客

 【原創聲明】:本人原創:

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

一、netcore跨平台之 Linux上部署netcore和webapi

這幾天閑着的時候在linux上部署了一下netcore webapi,下面就紀要一下這個過程。

中間遇到不少的坑,心裏都是淚啊。

 話不多說,開始幹活。

————————————————————————

第一步,你得先創建一個netcore的接口,這個我就簡單創建一個接口。

關於開發工具,我用的是vs2017,當然最新的vs2019也出來了,你可以用新的,都沒關係。

開始選擇創建項目,如圖所示,這個入門的程序員都應該懂

 

 

 選擇API

 

 

 點擊確定按鈕就創建成功。

如圖打開 Program.cs 

 

 

 在這裏添加一段代碼

 

代碼添加后

 

 

 這樣代碼就寫好了。

接下來就是發布。

 

選擇文件夾,選擇你要發布的項目的位置。

 

 

 點擊高級配置如下,注意下,這裏的目標框架是2.2版本,所以我們在linux上安裝的也是2.2。

 這裏我就遇到過坑,我vs發布的是2.0的版本,結果我linux是2.2,就各種運行報錯,後來改成2.2就好了。

 

 最後保存併發布就好了。

netcore項目的創建和發布就這樣結束了。

第二步,你得準備一個linux服務器,然後安裝環境

如果你條件允許,可以直接在阿里雲或者騰訊雲、華為雲、百度雲上買一個服務器。

新用戶是白菜價哦,(這裏真不是打廣告)當然你可以在你電腦上安裝一個VMware虛擬機。

具體安裝步驟百度一下一大把,這裏就不演示了。

我就在在百度雲買了一個linux服務器,嗯,價格還算便宜,畢竟新用戶,為什麼用百度雲??

當然不是因為他好,而是我阿里雲已經不是新用戶了

好了,我們繼續。

用xshell登錄到你的linux服務器上。(如果不懂linux,沒關係,你總會百度吧)

登錄成功后,你可以在直接輸入如下命令

sudo yum install dotnet-sdk-2.2

 

 點擊確認,你需要等一段時間,如果你服務器網速很差,那麼你可以會等很久。

 如下圖示,遇到這裏你需要點擊敲一下你的鍵盤上的 y 回車即可

 這個時候系統開始慢慢的下載了,請耐心等待即可。

 

 

 終於下載完成了

 

你可以輸入下面的命令看看是否成功

dotnet --version

显示如下,表示按照成功

 

 

 

然後我們把發布包上傳到服務器上來

我這裏用的是xftp工具,當然也有其他工具可,下圖所示是我安裝的兩個工具,大家可以去下載安裝。

 

 

這裏給大家提供一些我在網盤保存的一些工具

Xshell+Xftp真正破解版    https://pan.baidu.com/s/1Ew1XPg11sakpc8mvK6QsHg 

 打開xftp並連接到服務器,如下所示

 

 

 

我這裏用的root權限,這裏進來就直接就是root根目錄了

然後右鍵點擊創建一個目錄用來保存你上傳的netcore文件,嗯,就取名netcore吧

 

 

 

 

 

 

 然後在左邊找到你剛剛發布的那個包的位置,並且點擊右邊的netcore進入到對應的目錄中

 

 

 

然後全選左邊的所有文件,並右鍵然後點擊傳輸,如圖所示

 

 

然後文件就開始傳輸了

 

 

 

 

 等下面的傳輸沒有記錄了,那麼恭喜你,你傳遞完成了。

 

 有人可能會問為啥這麼多文件,我vs2017本來沒有2.2版本,後來我在本機安裝了2.2,結果發布后就這麼多……

然後我們再次回到xshell看看

輸入命令 ll 可以看到我們新加了一個文件 netcore

 

 然後輸入命令cd  進入到我們上傳的這個發布包中。

然後在輸入命令 ll

cd netcore
ll

我們可以找到WebApiTest.dll文件

 

 然後輸入命令

dotnet WebApiTest.dll

如圖所示,就恭喜你你的項目運行正常。

 

 然後你Ctrl+C結束掉這個程序,這裏只能在服務器內部訪問,外面是不能訪問的。

 然後輸入如下命令

dotnet WebApiTest.dll   --server.urls "http://*:6666"

如圖所示

 

 然後用postman或者一些在線工具訪問這個接口,如圖所示,那麼恭喜你成功了

這個測試工具的網站:https://www.sojson.com/httpRequest/

 

 

 好了,到這裏netcore在linux上配置就基本完成了,接下來下一篇我們開始講nginx的配置,以及讓netcore運行在nginx下。

 

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

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

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

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

用這個庫 3 分鐘實現讓你滿意的表格功能:Bootstrap-Table

本文作者:HelloGitHub-kalifun

這是 HelloGitHub 推出的系列,今天給大家推薦一個基於 Bootstrap 和 jQuery 的表格插件:Bootstrap-Table

一、介紹

從項目名稱就可以知道,這是一款 Bootstrap 的表格插件。表格的展示的形式所有的前端幾乎在工作中都有涉及過,Bootstrap Table 提供了快速的建表、查詢、分頁、排序等一系列功能。

項目地址:https://github.com/wenzhixin/bootstrap-table

可能 Bootstrap 和 jQuery 技術有些過時了,但如果因為歷史的技術選型或者舊的項目還在用這兩個庫的話,那這個項目一定會讓你的嘴角慢慢上揚,拿下錶格展示方面的需求易如反掌!

二、模式

Boostatrp Table 分為兩種模式:客戶端(client)模式、服務端(server)模式。

  • 客戶端:通過數據接口將服務器需要加載的數據一次性展現出來,然後裝換成 json 然後生成 table。我們可以自己定義显示行數,分頁等,此時就不再會向服務器發送請求了。

  • 服務器:根據設定的每頁記錄數和當前显示頁,發送數據到服務器進行查詢。

三、實戰操作

Tips: 解釋說明均在代碼中以註釋方式展示,請大家注意閱讀。

我們採用的是最簡單的 CDN 引入方式,代碼可直接運行。複製代碼並將配置好 json 文件的路徑即可看到效果。

3.1 快速上手

註釋中的星號表示該參數必寫,話不多說上代碼。示例代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello, Bootstrap Table!</title>
    // 引入 css
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
</head>
<body>
    // 需要填充的表格
    <table id="tb_departments" data-filter-control="true" data-show-columns="true"></table>
// 引入js
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
<script>
        window.operateEvents = {
            // 當點擊 class=delete 時觸發
            'click .delete': function (e,value,row,index) {
                // 在 console 打印出整行數據
                console.log(row);
            }
        };

        $('#tb_departments').bootstrapTable({
            url: '/frontend/bootstrap-table/user.json',         //請求後台的 URL(*)
            method: 'get',                      //請求方式(*)
            // data: data,                      //當不使用上面的後台請求時,使用data來接收數據
            toolbar: '#toolbar',                //工具按鈕用哪個容器
            striped: true,                      //是否显示行間隔色
            cache: false,                       //是否使用緩存,默認為 true,所以一般情況下需要設置一下這個屬性(*)
            pagination: true,                   //是否显示分頁(*)
            sortable: false,                    //是否啟用排序
            sortOrder: "asc",                   //排序方式
            sidePagination: "client",           //分頁方式:client 客戶端分頁,server 服務端分頁(*)
            pageNumber:1,                       //初始化加載第一頁,默認第一頁
            pageSize: 6,                        //每頁的記錄行數(*)
            pageList: [10, 25, 50, 100],        //可供選擇的每頁的行數(*)
            search: true,                       //是否顯示錶格搜索,此搜索是客戶端搜索,不會進服務端,所以個人感覺意義不大
            strictSearch: true,                 //啟用嚴格搜索。禁用比較檢查。
            showColumns: true,                  //是否显示所有的列
            showRefresh: true,                  //是否显示刷新按鈕
            minimumCountColumns: 2,             //最少允許的列數
            clickToSelect: true,                //是否啟用點擊選中行
            height: 500,                        //行高,如果沒有設置 height 屬性,表格自動根據記錄條數覺得表格高度
            uniqueId: "ID",                     //每一行的唯一標識,一般為主鍵列
            showToggle:true,                    //是否显示詳細視圖和列表視圖的切換按鈕
            cardView: false,                    //是否显示詳細視圖
            detailView: false,                  //是否显示父子表
            showExport: true,                   //是否显示導出
            exportDataType: "basic",            //basic', 'all', 'selected'.
            columns: [{
                checkbox: true     //複選框標題,就是我們看到可以通過複選框選擇整行。
            }, {
                field: 'id', title: 'ID'       //我們取json中id的值,並將表頭title設置為ID
            }, {
                field: 'username', title: '用戶名'         //我們取 json 中 username 的值,並將表頭 title 設置為用戶名
            },{
                field: 'sex', title: '性別'                //我們取 json 中 sex 的值,並將表頭 title 設置為性別
            },{
                field: 'city', title: '城市'               //我們取 json 中 city 的值,並將表頭 title 設置為城市
            },{
                field: 'sign', title: '簽名'               //我們取 json 中 sign 的值,並將表頭 title 設置為簽名
            },{
                field: 'classify', title: '分類'           //我們取 json 中 classify 的值,並將表頭 title 設置為分類
            },{
                //ormatter:function(value,row,index) 對後台傳入數據 進行操作 對數據重新賦值 返回 return 到前台
                // events 觸發事件
                field: 'Button',title:"操作",align: 'center',events:operateEvents,formatter:function(value,row,index){
                    var del = '<button type="button" class="btn btn-danger delete">刪除</button>'
                    return del;
                }
            }
            ],
            responseHandler: function (res) {
                return res.data      //在加載遠程數據之前,處理響應數據格式.
                // 我們取的值在data字段中,所以需要先進行處理,這樣才能獲取我們想要的結果
            }
        });
</script>
</body>
</html>

上面的代碼展示通過基本 API 實現基礎的功能,示例代碼並沒有羅列所有的 API。該庫還有很多好玩的功能等着大家去發現,正所謂師父領進門修行靠個人~

3.2 拆解講解

下面對關鍵點進行闡述,為了更方便使用的小夥伴清楚插件的用法。

3.2.1 初始化部分

選擇需要初始化表格。
$('#tb_departments').bootstrapTable({})
這個就像table的入口一樣。
<table id="tb_departments" data-filter-control="true" data-show-columns="true"></table>

3.2.2 閱讀數據部分

columns:[{field: 'Key', title: '文件路徑',formatter: function(value,row,index){} }]
  • field json 中鍵值對中的 Key
  • title 是表格頭显示的內容
  • formatter 是一個函數類型,當我們對數據內容需要修改時會用它。例:編碼轉換

3.2.3 事件觸發器

events:operateEvents
 window.operateEvents = {
        'click .download': function (e,value,row,index) {
            console.log(row);
        }
   }

因為很多時候我們需要針對錶格進行處理,所以事件觸發器是一個不錯的選擇。比如:它可以記錄我們的行數據,可以利用觸發器進行定製函數的執行等。

四、擴展

介紹幾個擴展可以讓我們便捷的實現更多的表格功能,而不需要自己造輪子讓我們的工作更加高效(也可以進入官網查看擴展的具體使用方法,官方已經收集了大量的擴展)。老規矩直接上代碼:

4.1 表格導出

<script src="js/bootstrap-table-export.js"></script> 
showExport: true,                                           //是否显示導出
exportDataType: basic,                                      //導出數據類型,支持:'基本','全部','選中'
exportTypes:['json', 'xml', 'csv', 'txt', 'sql', 'excel']   //導出類型

4.2 自動刷新

<script src="extensions/auto-refresh/bootstrap-table-auto-refresh.js"></script>
autoRefresh: true,                              //設置 true 為啟用自動刷新插件。這並不意味着啟用自動刷新
autoRefreshStatus: true,                        //設置 true 為啟用自動刷新。這是表加載時狀態自動刷新
autoRefreshInterval: 60,                        //每次發生自動刷新的時間(以秒為單位)
autoRefreshSilent: true                         //設置為靜默自動刷新

4.3 複製行

<script src="extensions/copy-rows/bootstrap-table-copy-rows.js"></script>
showCopyRows: true,                                 //設置 true 為显示複製按鈕。此按鈕將所選行的內容複製到剪貼板
copyWithHidden: true,                               //設置 true 為使用隱藏列進行複製
copyDelimiter: ', ',                                //複製時,此分隔符將插入列值之間
copyNewline: '\n'                                   //複製時,此換行符將插入行值之間

五、總結

本篇文章只是簡單的闡述 Bootstrap-Table 如何使用,正在對錶格功能實現而憂愁的小夥伴,可以使用 HelloGitHub 推薦的這款插件。你會發現網頁製作表格還可以如此快捷,期待小夥伴挖掘出更加有意思的功能哦。

注:上面 js 部分並沒有採用函數形式,建議在使用熟悉之後還是採用函數形式,這樣也方便復用及讓代碼看起來更加規範。

六、參考資料

『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟着我們的文章,你會發現編程的樂趣、使用和發現參与開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~

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

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

Vue躬行記(9)——Vuex

  Vuex是一個專為Vue.js設計的狀態管理庫,適用於多組件共享狀態的場景。Vuex能集中式的存儲和維護所有組件的狀態,並提供相關規則保證狀態的獨立性、正確性和可預測性,這不僅讓調試變得可追蹤,還讓代碼變得更結構化且易維護。本文所使用的Vuex,其版本是3.1.1。

一、基本用法

  首先需要引入Vue和Vuex兩個庫,如果像下面這樣在Vue之後引入Vuex,那麼Vuex會自動調用Vue.use()方法註冊其自身;但如果以模塊的方式引用,那麼就得顯式地調用Vue.use()。注意,因為Vuex依賴Promise,所以對於那些不支持Promise的瀏覽器,要使用Vuex的話,得引入相關的polyfill庫,例如es6-promise。

<script src="js/vue.js"></script>
<script src="js/vuex.js"></script>

  然後創建Vuex應用的核心:Store(倉庫)。它是一個容器,保存着大量的響應式狀態(State),並且這些狀態不能直接修改,需要顯式地將修改請求提交到Mutation(變更)中才能實現更新,因為這樣便於追蹤每個狀態的變化。在下面的示例中,初始化了一個digit狀態,並在mutations選項中添加了兩個可將其修改的方法。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    add: state => state.digit++,
    minus: state => state.digit--
  }
});

  接着創建根實例,並將store實例注入,從而讓整個應用都能讀寫其中的狀態,在組件中可通過$store屬性訪問到它,如下所示,以計算屬性的方式讀取digit狀態,並通過調用commit()方法來修改該狀態。

var vm = new Vue({
  el: "#container",
  store: store,
  computed: {
    digit() {
      return this.$store.state.digit;
    }
  },
  methods: {
    add() {
      this.$store.commit("add");
    },
    minus() {
      this.$store.commit("minus");
    }
  }
});

  最後將根實例中的方法分別註冊到兩個按鈕的點擊事件中,如下所示,每當點擊這兩個按鈕時,狀態就會更新,並在頁面中显示。

<div id="container">
  <p>{{digit}}</p>
  <button @click="add">增加</button>
  <button @click="minus">減少</button>
</div>

二、主要組成

  Vuex的主要組成除了上一節提到的Store、State和Mutation之外,還包括Getter和Action,本節會對其中的四個做重點講解,它們之間的關係如圖2所示。

圖2  四者的關係

1)State

  State是一個可存儲狀態的對象,在應用的任何位置都能被訪問到,並且作為單一數據源(Single Source Of Truth)而存在。

  當組件需要讀取大量狀態時,一個個的聲明成計算屬性會顯得過於繁瑣而冗餘,於是Vuex提供了一個名為mapState()的輔助函數,用來將狀態自動映射成計算屬性,它的參數既可以是數組,也可以是對象。

  當計算屬性的名稱與狀態名稱相同,並且不需要做額外處理時,可將名稱組成一個字符串數組傳遞給mapState()函數,在組件中可按原名調用,如下所示。

var vm = new Vue({
  computed: Vuex.mapState([ "digit" ])
});

  當計算屬性的名稱與狀態名稱不同,或者計算屬性讀取的是需要處理的狀態時,可將一個對象傳遞給mapState()函數,其鍵就是計算屬性的名稱,而其值既可以是函數,也可以是字符串,如下代碼所示。如果是函數,那麼它的第一個參數是state,即狀態對象;如果是字符串,那麼就是從state中指定一個狀態作為計算屬性。

var vm = new Vue({
  computed: Vuex.mapState({
    digit: state => state.digit,
    alias: "digit"        //相當於state => state.digit
  })
});

  因為mapState()函數返回的是一個對象,所以當組件內已經包含計算屬性時,可以對其應用擴展運算符(…)來進行合併,如下所示,這是一種極為簡潔的寫法。

var vm = new Vue({
  computed: {
    name() {},
    ...Vuex.mapState([ "digit" ])
  }
});

2)Getter

  Getter是從State中派生出的狀態,當多個組件要對同一個狀態進行相同的處理時,就需要將狀態轉移到Getter中,以免產生重複的冗餘代碼。

  Getter相當於Store的計算屬性,它能接收兩個參數,第一個是state對象,第二個是可選的getters對象,該參數能讓不同的Getter之間相互訪問。Getter的返回值會被緩存,並且只有當依賴值發生變化時才會被重新計算。不過當返回值是函數時,其結果就不會被緩存,如下所示,其中caculate返回的是個数字,而sum返回的是個函數。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  getters: {
    caculate: state => {
      return state.digit + 2;
    },
    sum: state => right => {
      return state.digit + right;
    }
  }
});

  在組件內可通過this.$store.getters訪問到Getter中的數據,如下所示,讀取了上一個示例中的兩個Getter。

var vm = new Vue({
  methods: {
    add() {
      this.$store.getters.caculate;
      this.$store.getters.sum(1);
    }
  }
});

  Getter也有一個輔助函數,用來將Getter自動映射為組件的計算屬性,名字叫mapGetters(),其參數也是數組或對象。但與之前的mapState()不同,當參數是對象時,其值不能是函數,只能是字符串,如下所示,為了對比兩種寫法,聲明了兩個computed選項。

var vm = new Vue({
  computed: Vuex.mapGetters([ "caculate" ]),
  computed: Vuex.mapGetters({
    alias: "caculate"
  })
});

3)Mutation

  更改狀態的唯一途徑是提交Mutation,Vuex中的Mutation類似於事件,也包含一個類型和回調函數,在函數體中可進行狀態更改的邏輯,並且它能接收兩個參數,第一個是state對象,第二個是可選的附加數據,叫載荷(Payload)。下面這個Mutation的類型是“interval”,接收了兩個參數。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    interval: (state, payload) => state.digit += payload.number
  }
});

  在組件中不能直接調用Mutation的回調函數,得通過this.$store.commit()方法觸發更新,如下所示,採用了兩種提交方式,第一種是傳遞type參數,第二種是傳遞包含type屬性的對象。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.commit("interval", { number: 2 });             //第一種
      this.$store.commit({ type: "interval", number: 2 });       //第二種
    }
  }
});

  當多人協作時,Mutation的類型適合寫成常量,這樣更容易維護,也能減少衝突。

const INTERVAL = "interval";

  Mutation有一個名為mapMutations()的輔助函數,其寫法和mapState()相同,它能將Mutation自動映射為組件的方法,如下所示。

var vm = new Vue({
  methods: Vuex.mapMutations(["interval"])
  //相當於
  methods: {
    interval(payload) {
      this.$store.commit(INTERVAL, payload);
    }
  }
});

  注意,為了能追蹤狀態的變更,Mutation只支持同步的更新,如果要異步,那麼得使用Action。

4)Action

  Action類似於Mutation,但不同的是它可以包含異步操作,並且只能用來通知Mutation,不會直接更新狀態。Action的回調函數能接收兩個參數,第一個是與Store實例具有相同屬性和方法的context對象(注意,不是Store實例本身),第二個是可選的附加數據,如下所示,調用commit()方法提交了一個Mutation。

const store = new Vuex.Store({
  actions: {
    interval(context, payload) {
      context.commit("interval", payload);
    }
  }
});

  在組件中能通過this.$store.dispatch()方法分發Action,如下所示,與commit()方法一樣,它也有兩種提交方式。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.dispatch("interval", { number: 2 });            //第一種
      this.$store.dispatch({type: "interval", number: 2});        //第二種
    }
  }
});

  注意,由於dispatch()方法返回的是一個Promise對象,因此它能以一種更優雅的方式來處理異步操作,如下所示。

var vm = new Vue({
  methods: {
    interval() {
      this.$store.dispatch("interval", { number: 2 }).then(() => {
        console.log("success");
      });
    }
  }
});

  Action有一個名為mapActions()的輔助函數,其寫法和mapState()相同,它能將Action自動映射為組件的方法,如下所示。

var vm = new Vue({
  methods: Vuex.mapActions([ "interval" ])
});

三、模塊

  當應用越來越大時,為了避免Store變得過於臃腫,有必要將其拆分到一個個的模塊(Module)中。每個模塊就是一個對象,包含屬於自己的State、Getter、Mutation和Action,甚至還能嵌套其它模塊。

1)局部狀態

  對於模塊內部的Getter和Mutation,它們接收的第一個參數是模塊的局部狀態,而Getter的第三個參數rootState和Action的context.rootState屬性可訪問根節點狀態(即全局狀態),如下所示。

const moduleA = {
  state: { digit: 0 },
  mutations: {
    add: state => state.digit++
  },
  getters: {
    caculate: (state, getter, rootState) => {
      return state.digit + 2;
    }
  },
  actions: {
    interval(context, payload) {
      context.commit("add", payload);
    }
  }
};

2)命名空間

  默認情況下,只有在訪問State時需要帶命名空間,而Getter、Mutation和Action的調用方式不變。將之前的moduleA模塊註冊到Store實例中,如下所示,modules選項的值是一個子模塊對象,其鍵是模塊名稱。

const store = new Vuex.Store({
  modules: {
    a: moduleA
  }
});

  如果要訪問模塊中的digit狀態,那麼可以像下面這樣寫。

store.state.a.digit;

  當模塊的namespaced屬性為true時,它的Getter、Mutation和Action也會帶命名空間,在使用時,需要添加命名空間前綴,如下代碼所示,此舉大大提升了模塊的封裝性和復用性。

const moduleA = {
  namespaced: true
};
var vm = new Vue({
  el: "#container",
  store: store,
  methods: {
    add() {
      this.$store.commit("a/add");
    },
    caculate() {
      this.$store.getters["a/caculate"];
    }
  }
});

  如果要在帶命名空間的模塊中提交全局的Mutation或分發全局的Action,那麼只要將{root: true}作為第三個參數傳給commit()或dispatch()就可實現,如下所示。

var vm = new Vue({
  methods: {
    add() {
      this.$store.dispatch("add", null, { root: true });
      this.$store.commit("add", null, { root: true });
    }
  }
});

  如果要在帶命名空間的模塊中註冊全局的Action,那麼需要將其修改成對象的形式,然後添加root屬性並設為true,再將Action原先的定義轉移到handler()函數中,如下所示。

const moduleA = {
  actions: {
    interval: {
      root: true,
      handler(context, payload) {}
    }
  }
};

3)輔助函數

  當使用mapState()、mapGetters()、mapMutations()和mapActions()四個輔助函數對帶命名空間的模塊做映射時,需要顯式的包含命名空間,如下所示。

var vm = new Vue({
  computed: Vuex.mapState({
    digit: state => state.a.digit
  }),
  methods: Vuex.mapMutations({
    add: "a/add"
  })
});

  這四個輔助函數的第一個參數都是可選的,用於綁定命名空間,可簡化映射過程,如下所示。

var vm = new Vue({
  computed: Vuex.mapState("a", {
    digit: state => state.digit
  }),
  methods: Vuex.mapMutations("a", {
    add: "add"
  })
});

  Vuex還提供了另一個輔助函數createNamespacedHelpers(),可創建綁定命名空間的輔助函數,如下所示。

const { mapState, mapMutations } = Vuex.createNamespacedHelpers("a");

四、動態註冊

  在創建Store實例后,可通過registerModule()方法動態註冊模塊,如下代碼所示,調用了兩次registerModule()方法,第一次註冊了模塊“a”,第二次註冊了嵌套模塊“a/b”。

const store = new Vuex.Store();
store.registerModule("a", moduleA);    
store.registerModule(["a", "b"], moduleAB);

  通過store.state.a和store.state.a.b可訪問模塊的局部狀態。如果要卸載動態註冊的模塊,那麼可以通過unregisterModule()方法實現。

  registerModule()方法的第三個參數是可選的配置對象,當preserveState屬性的值為true時(如下所示),在註冊模塊時會忽略模塊中的狀態,即無法在store中讀取模塊中的狀態。

store.registerModule("a", moduleA, { preserveState: true });

五、表單處理

  表單默認能直接修改組件的狀態,但是在Vuex中,狀態只能由Mutation觸發更新。為了能更好的追蹤狀態的變化,也為了能更符合Vuex的思維,需要讓表單控件與狀態綁定在一起,並通過input或change事件監聽狀態更新的行為,如下所示。

<div id="container">
  <input :value="digit" @input="add" />
</div>

  然後在Store實例中初始化digit狀態,並添加更新狀態的Mutation,如下所示。

const store = new Vuex.Store({
  state: {
    digit: 0
  },
  mutations: {
    add: (state, value) => {
      state.digit = value;
    }
  }
});

  最後在創建根實例時,將digit狀態映射成它的計算屬性,在事件處理程序add()中調用commit()方法,並將控件的值作為第二個參數傳入,如下所示。

var vm = new Vue({
  el: "#container",
  store: store,
  computed: Vuex.mapState(["digit"]),
  methods: {
    add(e) {
      this.$store.commit("add", e.target.value);
    }
  }
});

  還有一個方法也能實現相同的功能,那就是在控件上使用v-model指令,但需要與帶setter的計算屬性配合,如下所示(只給出了關鍵部分的代碼)。

<div id="container">
  <input v-model="digit" />
</div>
<script>
  var vm = new Vue({
    computed: {
      digit: {
        get() {
          return this.$store.state.digit;
        },
        set(value) {
          this.$store.commit("add", value);
        }
      }
    }
  });
</script>

 

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表

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

[ASP.NET Core 3框架揭秘] 文件系統[2]:總體設計

在《》中,我們通過幾個簡單的實例演示從編程的角度對文件系統做了初步的體驗,接下來我們繼續從設計的角度來進一步認識它。這個抽象的文件系統以目錄的形式來組織文件,我們可以利用它讀取某個文件的內容,還可以對目錄或者文件實施監控並及時得到變化的通知。由於IFileProvider對象提供了針對文件系統變換的監控功能,在.NET Core下里類似的功能大都利用一個IChangeToken對象來實現,所以我們在對IFileProvider進行深入介紹之前有必要先來了解一下IChangeToken。

一、IChangeToken

從字面上理解的IChangeToken對象就是一個與某組監控數據關聯的“令牌(Token)”,它能夠在檢測到數據改變的時候及時地對外發出一個通知。如果IChangeToken關聯的數據發生改變,它的HasChanged屬性將變成True。我們可以調用其RegisterChangeCallback方法註冊一個在數據發生改變時可以自動執行的回調,該方法會返回一個IDisposable對象,我們通過用其Dispose方法解除註冊的回調。至於IChangeToken接口的另一個屬性ActiveChangeCallbacks,它表示當數據發生變化時是否需要主動執行註冊的回調操作。

public interface IChangeToken
{
    bool HasChanged { get; }
    bool ActiveChangeCallbacks { get; }
    IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

.NET Core提供了若干原生的IChangeToken實現類型,我們最常使用的是一個名為CancellationChangeToken的實現。CancellationChangeToken的實現原理很簡單,它基本上就是按照如下的形式藉助我們熟悉的CancellationToken對象來發送通知。

public class CancellationChangeToken : IChangeToken
{
    private readonly CancellationToken _token;
    public CancellationChangeToken(CancellationToken token)  => _token = token;
    public bool HasChanged  => _token.IsCancellationRequested; 
    public bool ActiveChangeCallbacks  => true;    
    public IDisposable RegisterChangeCallback(Action<object> callback, object state)  => _token.Register(callback, state);
}

除了CancellationChangeToken,有時也我們也會使用到一個名為CompositeChangeToken的實現。顧名思義,CompositeChangeToken代表由多個IChangeToken組合而成的複合型IChangeToken對象。如下面的代碼片段所示,我們在調用構造函數創建一個CompositeChangeToken對象的時候,需要提供這些IChangeToken對象。對於一個CompositeChangeToken對象來說,只要組成它的任何一個IChangeToken發生改變,其HasChanged屬性將會變成True,而註冊的回調自然會被執行。至於ActiveChangeCallbacks屬性,只要任何一個IChangeToken的同名屬性返回True,該屬性就會返回True。

public class CompositeChangeToken : IChangeToken
{
    public bool  ActiveChangeCallbacks { get; }
    public IReadOnlyList<IChangeToken>  ChangeTokens { get; }
    public bool  HasChanged { get; }
   
    public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens);   
    public IDisposable RegisterChangeCallback(Action<object> callback, object state);   
}

我們可以直接調用IChangeToken提供的RegisterChangeCallback方法來註冊在接收到數據變化通知后的回調操作,但是更常用的方式則是直接調用靜態類型ChangeToken提供的如下兩個OnChange方法重載來進行回調註冊,這兩個方法的第一個參數需要被指定為一個用來提供IChangeToken對象的Func<IChangeToken>委託。

public static class ChangeToken
{
    public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer,  Action changeTokenConsumer) ;
    public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer,  Action<TState> changeTokenConsumer, TState state) ;
}

二、IFileProvider

在了解了IChangeToken是怎樣一個對象之後,我們將關注轉移到文件系統的核心接口IFileProvider上,該接口定義在NuGet包“Microsoft.Extensions.FileProviders.Abstractions”中。我們在《》做了幾個簡單的實例演示,它們實際上體現了文件系統承載的三個基本功能,而這三個基本功能分別體現在IFileProvider接口如下所示的三個方法中。

public interface IFileProvider
{    
    IFileInfo GetFileInfo(string subpath);
    IDirectoryContents GetDirectoryContents(string subpath);
    IChangeToken Watch(string filter);
}

三、IFileInfo

雖然文件系統採用目錄來組織文件,但是不論是目錄還是文件都通過一個IFileInfo對象來表示,至於具體是目錄還是文件則通過IFileInfo的IsDirectory屬性來確定。對於一個IFileInfo對象,我們可以通過只讀屬性Exists判斷指定的目錄或者文件是否真實存在。至於另外兩個屬性NamePhysicalPath,它們分別表示文件或者目錄的名稱和物理路徑。屬性LastModified返回一個時間戳,表示目錄或者文件最終一次被修改的時間。對於一個表示具體文件的IFileInfo對象來說,我們可以利用屬性Length得到文件內容的字節長度。如果我們希望讀取文件的內容,可以藉助於CreateReadStream方法返回的Stream對象來完成。

public interface IFileInfo
{
    bool Exists { get; }
    bool  IsDirectory { get; }
    string Name { get; }
    string PhysicalPath { get; }
    DateTimeOffset LastModified { get; }
    long Length { get; }

    Stream CreateReadStream();
}

IFileProvider接口的GetFileInfo方法會根據指定的路徑得到表示所在文件的IFileInfo對象。換句話說,雖然一個IFileInfo對象可以用於描述目錄和文件,但是GetFileInfo方法的目的在於得到指定路徑返回的文件而不是目錄(我個人不太認同這種令人產生歧義的API設計)。一般來說,不論指定的文件是否存在,該方法總會返回一個具體的IFileInfo對象,因為目標文件的存在與否是由該對象的Exists屬性來確定的。

四、IDirectoryContents

如果希望得到某個目錄的內容,比如需要查看多少文件或者子目錄包含在這個目錄下,我們可以調用IFileProvider對象的GetDirectoryContents方法並將所在目錄的路徑作為參數。目錄內容通過該方法返回的IDirectoryContents對象來表示。如下面的代碼片段所示,一個IDirectoryContents對象實際上是一組IFileInfo對象的集合,組成這個集合的所有IFileInfo自然就是對包含在這個目錄下的所有文件和子目錄的描述。和GetFileInfo方法一樣,不論指定的目錄是否存在,GetDirectoryContents方法總是會返回一個具體的IDirectoryContents對象,它的Exists屬性會幫助我們確定指定目錄是否存在。

public interface IDirectoryContents : IEnumerable<IFileInfo>
{
    bool Exists { get; }
}

五、監控目錄或者文件更新

如果我們希望監控IFileProvider所在目錄或者文件的變化,我們可以調用它的Watch方法,當然前提是對應的IFileProvider對象提供了這樣的監控功能。這個方法接受一個字符串類型的參數filter,我們可以利用這個參數指定一個針對“文件匹配模式(File Globing Pattern)”表達式(以下簡稱Globing Pattern表達式)來篩選需要監控的目標目錄或文件。

Globing Pattern表達式比正則表達式簡單多了,它只包含“*”一種“通配符”,如果硬說它包含兩種通配符的話,那麼另一個通配符是“**”。Globing Pattern表達式體現為一個文件路徑,其中“*”代表所有不包括路徑分隔符(“/”或者“\”)的所有字符,而“**”則代表包含路徑分隔符在內的所有字符。下錶給出了幾個典型的Globing Pattern表達式和它們代碼的文件匹配語義。






















Globing Pattern表達式

匹配的文件

src/foobar/foo/settings.*

 

子目錄“src/foobar/foo/”(不含其子目錄)下名為“settings”的所有文件,比如settings.jsonsettings.xmlsettings.ini等。

src/foobar/foo/*.cs

 

子目錄“src/foobar/foo/”(不含其子目錄)下的所有.cs文件。

src/foobar/foo/*.*

子目錄“src/foobar/foo/”(不含其子目錄)下所有文件。

src/**/*.cs

子目錄“src”(含其子目錄)下的所有.cs文件。

一般來說,不論是調用IFileProvider對象的GetFileInfo或GetDirectoryContents方法所指定的目標文件或目錄的路徑,還是調用Watch方法指定的篩選表達式,都是一個針對當前IFileProvider對象映射根目錄的相對路徑。指定的這個路徑可以採用“/”字符作為前綴,但是這個前綴是不必要的。換句話說,如下所示的這兩組程序是完全等效的。

路徑不包含前綴“/”

var dirContents = fileProvider.GetDirectoryContents("foobar");
var fileInfo = fileProvider.GetFileInfo("foobar/foobar.txt");
var changeToken = fileProvider.Watch("foobar/*.txt");

路徑包含前綴“/”

var dirContents = fileProvider.GetDirectoryContents("/foobar");
var fileInfo = fileProvider.GetFileInfo("/foobar/foobar.txt");
var changeToken = fileProvider.Watch("/foobar/*.txt");

總的來說,以IFileProvider對象為核心的文件系統在設計上看是非常簡單的。除了IFileProvider接口之外,文件系統還涉及到其他一些對象,比如IDirectoryContents、IFileInfo和IChangeToken等,下圖所示的UML展示了這些接口以及它們之間的關係。

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

【其他文章推薦】

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

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

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

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

go中的數據結構-通道channel

1. channel的使用

  很多文章介紹channel的時候都和併發揉在一起,這裏我想把它當做一種數據結構來單獨介紹它的實現原理。

  channel,通道。golang中用於數據傳遞的一種數據結構。是golang中一種傳遞數據的方式,也可用作事件通知。

1.1 聲明、傳值、關閉

  使用chan關鍵字聲明一個通道,在使用前必須先創建,操作符 <- 用於指定通道的方向,發送或接收。如果未指定方向,則為雙向通道。

 1 //聲明和創建
 2 var ch chan int      // 聲明一個傳遞int類型的channel
 3 ch := make(chan int) // 使用內置函數make()定義一個channel
 4 ch2 := make(chan interface{})         // 創建一個空接口類型的通道, 可以存放任意格式
 5 
 6 type Equip struct{ /* 一些字段 */ }
 7 ch2 := make(chan *Equip)             // 創建Equip指針類型的通道, 可以存放*Equip
 8 
 9 //傳值
10 ch <- value          // 將一個數據value寫入至channel,這會導致阻塞,直到有其他goroutine從這個channel中讀取數據
11 value := <-ch        // 從channel中讀取數據,如果channel之前沒有寫入數據,也會導致阻塞,直到channel中被寫入數據為止
12 
13 ch := make(chan interface{})  // 創建一個空接口通道
14 ch <- 0 // 將0放入通道中
15 ch <- "hello"  // 將hello字符串放入通道中
16 
17 //關閉
18 close(ch)            // 關閉channel

  把數據往通道中發送時,如果接收方一直都沒有接收,那麼發送操作將持續阻塞。Go 程序運行時能智能地發現一些永遠無法發送成功的語句並報錯:

fatal error: all goroutines are asleep - deadlock! 
//運行時發現所有的 goroutine(包括main)都處於等待 goroutine。

1.2 四種重要的通道使用方式

無緩衝通道

  通道默認是無緩衝的,無緩衝通道上的發送操作將會被阻塞,直到有其他goroutine從對應的通道上執行接收操作,數據傳送完成,通道繼續工作。

package main

import (
    "fmt"
    "time"
)
var done chan bool
func HelloWorld() {
    fmt.Println("Hello world goroutine")
    time.Sleep(1*time.Second)
    done <- true
}
func main() {
    done = make(chan bool)  // 創建一個channel
    go HelloWorld()
    <-done
}
1 //輸出
2 //Hello world goroutine

  由於main不會等goroutine執行結束才返回,前文專門加了sleep輸出為了可以看到goroutine的輸出內容,那麼在這裏由於是阻塞的,所以無需sleep。

  將代碼中”done <- true”和”<-done”,去掉再執行,沒有上面的輸出內容。

管道

  通道可以用來連接goroutine,這樣一個的輸出是另一個輸入。這就叫做管道:

 

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 var echo chan string
 8 var receive chan string
 9 
10 // 定義goroutine 1 
11 func Echo() {
12     time.Sleep(1*time.Second)
13     echo <- "這是一次測試"
14 }
15 
16 // 定義goroutine 2
17 func Receive() {
18     temp := <- echo // 阻塞等待echo的通道的返回
19     receive <- temp
20 }
21 
22 
23 func main() {
24     echo = make(chan string)
25     receive = make(chan string)
26 
27     go Echo()
28     go Receive()
29 
30     getStr := <-receive   // 接收goroutine 2的返回
31 
32     fmt.Println(getStr)
33 }

  輸出字符串:”這是一次測試”。

  在這裏不一定要去關閉channel,因為底層的垃圾回收機制會根據它是否可以訪問來決定是否自動回收它。(這裏不是根據channel是否關閉來決定的)

單向通道類型
 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 // 定義goroutine 1
 9 func Echo(out chan<- string) {   // 定義輸出通道類型
10     time.Sleep(1*time.Second)
11     out <- "這又是一次測試"
12     close(out)
13 }
14 
15 // 定義goroutine 2
16 func Receive(out chan<- string, in <-chan string) { // 定義輸出通道類型和輸入類型
17     temp := <-in // 阻塞等待echo的通道的返回
18     out <- temp
19     close(out)
20 }
21 
22 
23 func main() {
24     echo := make(chan string)
25     receive := make(chan string)
26 
27     go Echo(echo)
28     go Receive(receive, echo)
29 
30     getStr := <-receive   // 接收goroutine 2的返回
31 
32     fmt.Println(getStr)
33 }

  輸出:這又是一次測試。

緩衝管道

  goroutine的通道默認是是阻塞的,那麼有什麼辦法可以緩解阻塞? 答案是:加一個緩衝區。

  創建一個緩衝通道:

1 ch := make(chan string, 3) // 創建了緩衝區為3的通道
2 
3 //==
4 len(ch)   // 長度計算
5 cap(ch)   // 容量計算

  緩衝通道傳遞數據示意圖:

 

2. 內部結構 

  Go語言channel是first-class的,意味着它可以被存儲到變量中,可以作為參數傳遞給函數,也可以作為函數的返回值返回。作為Go語言的核心特徵之一,雖然channel看上去很高端,但是其實channel僅僅就是一個數據結構而已,具體定義在 $GOROOT/src/runtime/chan.go里。如下:

 1 type hchan struct {
 2   qcount uint   // 隊列中的總數據
 3   dataqsiz uint   // 循環隊列的大小
 4   buf unsafe.Pointer // 指向dataqsiz元素數組
 5   elemsize uint16  // 
 6   closed uint32 
 7   elemtype *_type // 元素類型
 8   sendx uint // 發送索引
 9   recvx uint // 接收索引
10   recvq waitq // 接待員名單, 因recv而阻塞的等待隊列。
11   sendq waitq // 發送服務員列表, 因send而阻塞的等待隊列。
12   //鎖定保護hchan中的所有字段,以及幾個在此通道上阻止的sudogs中的字段。
13   //按住此鎖定時不要更改另一個G的狀態(尤其是不要準備G),因為這可能會導致死鎖堆棧縮小。
14   lock mutex 
15 }

   其中一個核心的部分是存放channel數據的環形隊列,由qcount和elemsize分別指定了隊列的容量和當前使用量。dataqsize是隊列的大小。elemalg是元素操作的一個Alg結構體,記錄下元素的操作,如copy函數,equal函數,hash函數等。

  如果是帶緩衝區的chan,則緩衝區數據實際上是緊接着Hchan結構體中分配的。不帶緩衝的 channel ,環形隊列 size 則為 0。

1 c = (Hchan*)runtime.mal(n + hint*elem->size);

  另一重要部分就是recvq和sendq兩個鏈表,一個是因讀這個通道而導致阻塞的goroutine,另一個是因為寫這個通道而阻塞的goroutine。如果一個goroutine阻塞於channel了,那麼它就被掛在recvq或sendq中。WaitQ是鏈表的定義,包含一個頭結點和一個尾結點:

1 struct    WaitQ
2 {
3     SudoG*    first;
4     SudoG*    last;
5 };

  隊列中的每個成員是一個SudoG結構體變量:

1 struct    SudoG
2 {
3     G*    g;        // g和selgen構成
4     uint32    selgen;        // 指向g的弱指針
5     SudoG*    link;
6     int64    releasetime;
7     byte*    elem;        // 數據元素
8 };

  該結構中主要的就是一個g和一個elem。elem用於存儲goroutine的數據。讀通道時,數據會從Hchan的隊列中拷貝到SudoG的elem域。寫通道時,數據則是由SudoG的elem域拷貝到Hchan的隊列中。

 

  基本的寫channel操作,在底層運行時庫中對應的是一個runtime.chansend函數。

1 c <- v
  在運行時庫中會執行:
1 void runtime·chansend(ChanType *t, Hchan *c, byte *ep, bool *pres, void *pc)

  其中c就是channel,ep是取變量v的地址。這裏的傳值約定是調用者負責分配好ep的空間,僅需要簡單的取變量地址就夠了。pres參數是在select中的通道操作使用的。

  這個函數首先會區分是同步還是異步。同步是指chan是不帶緩衝區的,因此可能寫阻塞,而異步是指chan帶緩衝區,只有緩衝區滿才阻塞。在同步的情況下,由於channel本身是不帶數據緩存的,這時首先會查看Hchan結構體中的recvq鏈表時否為空,即是否有因為讀該管道而阻塞的goroutine。如果有則可以正常寫channel,否則操作會阻塞。

  recvq不為空的情況下,將一個SudoG結構體出隊列,將傳給通道的數據(函數參數ep)拷貝到SudoG結構體中的elem域,並將SudoG中的g放到就緒隊列中,狀態置為ready,然後函數返回。如果recvq為空,否則要將當前goroutine阻塞。此時將一個SudoG結構體,掛到通道的sendq鏈表中,這個SudoG中的elem域是參數eq,SudoG中的g是當前的goroutine。當前goroutine會被設置為waiting狀態並掛到等待隊列中。

  在異步的情況,如果緩衝區滿了,也是要將當前goroutine和數據一起作為SudoG結構體掛在sendq隊列中,表示因寫channel而阻塞。否則也是先看有沒有recvq鏈表是否為空,有就喚醒。跟同步不同的是在channel緩衝區不滿的情況,這裏不會阻塞寫者,而是將數據放到channel的緩衝區中,調用者返回。

  讀channel的操作也是類似的,對應的函數是runtime.chansend。一個是收一個是發,基本的過程都是差不多的。

  當協程嘗試從未關閉的 channel 中讀取數據時,內部的操作如下:

  • 當 buf 非空時,此時 recvq 必為空,buf 彈出一個元素給讀協程,讀協程獲得數據後繼續執行,此時若 sendq 非空,則從 sendq 中彈出一個寫協程轉入 running 狀態,待寫數據入隊列 buf ,此時讀取操作 <- ch 未阻塞;
  • 當 buf 為空但 sendq 非空時(不帶緩衝的 channel),則從 sendq 中彈出一個寫協程轉入 running 狀態,待寫數據直接傳遞給讀協程,讀協程繼續執行,此時讀取操作 <- ch 未阻塞;
  • 當 buf 為空並且 sendq 也為空時,讀協程入隊列 recvq 並轉入 blocking 狀態,當後續有其他協程往 channel 寫數據時,讀協程才會重新轉入 running 狀態,此時讀取操作 <- ch 阻塞。

  類似的,當協程嘗試往未關閉的 channel 中寫入數據時,內部的操作如下:

  • 當隊列 recvq 非空時,此時隊列 buf 必為空,從 recvq 彈出一個讀協程接收待寫數據,此讀協程此時結束阻塞並轉入 running 狀態,寫協程繼續執行,此時寫入操作 ch <- 未阻塞;
  • 當隊列 recvq 為空但 buf 未滿時,此時 sendq 必為空,寫協程的待寫數據入 buf 然後繼續執行,此時寫入操作 ch <- 未阻塞;
  • 當隊列 recvq 為空並且 buf 為滿時,此時寫協程入隊列 sendq 並轉入 blokcing 狀態,當後續有其他協程從 channel 中讀數據時,寫協程才會重新轉入 running 狀態,此時寫入操作 ch <- 阻塞。

  當關閉 non-nil channel 時,內部的操作如下:

  • 當隊列 recvq 非空時,此時 buf 必為空,recvq 中的所有協程都將收到對應類型的零值然後結束阻塞狀態;
  • 當隊列 sendq 非空時,此時 buf 必為滿,sendq 中的所有協程都會產生 panic ,在 buf 中數據仍然會保留直到被其他協程讀取。

  空通道是指將一個channel賦值為nil,或者定義后不調用make進行初始化。按照Go語言的語言規範,讀寫空通道是永遠阻塞的。其實在函數runtime.chansend和runtime.chanrecv開頭就有判斷這類情況,如果發現參數c是空的,則直接將當前的goroutine放到等待隊列,狀態設置為waiting。

  讀一個關閉的通道,永遠不會阻塞,會返回一個通道數據類型的零值。這個實現也很簡單,將零值複製到調用函數的參數ep中。寫一個關閉的通道,則會panic。關閉一個空通道,也會導致panic。

3. channel的高級用法

3.1 條件變量(condition variable)

  類型於 POSIX 接口中線程通知其他線程某個事件發生的條件變量,channel 的特性也可以用來當成協程之間同步的條件變量。因為 channel 只是用來通知,所以 channel 中具體的數據類型和值並不重要,這種場景一般用 strct {} 作為 channel 的類型。

一對一通知

  類似 pthread_cond_signal() 的功能,用來在一個協程中通知另個某一個協程事件發生:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     ch := make(chan struct{})
10     nums := make([]int, 100)
11 
12     go func() {
13         time.Sleep(time.Second)
14         for i := 0; i < len(nums); i++ {
15             nums[i] = i
16         }
17         // send a finish signal
18         ch <- struct{}{}
19     }()
20 
21     // wait for finish signal
22     <-ch
23     fmt.Println(nums)
24 }
廣播通知

  類似 pthread_cond_broadcast() 的功能。利用從已關閉的 channel 讀取數據時總是非阻塞的特性,可以實現在一個協程中向其他多個協程廣播某個事件發生的通知:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     N := 10
10     exit := make(chan struct{})
11     done := make(chan struct{}, N)
12 
13     // start N worker goroutines
14     for i := 0; i < N; i++ {
15         go func(n int) {
16             for {
17                 select {
18                 // wait for exit signal
19                 case <-exit:
20                     fmt.Printf("worker goroutine #%d exit\n", n)
21                     done <- struct{}{}
22                     return
23                 case <-time.After(time.Second):
24                     fmt.Printf("worker goroutine #%d is working...\n", n)
25                 }
26             }
27         }(i)
28     }
29 
30     time.Sleep(3 * time.Second)
31     // broadcast exit signal
32     close(exit)
33     // wait for all worker goroutines exit
34     for i := 0; i < N; i++ {
35         <-done
36     }
37     fmt.Println("main goroutine exit")
38 }

3.2 信號量

  channel 的讀/寫相當於信號量的 P / V 操作,下面的示例程序中 channel 相當於信號量:

 1 package main
 2 
 3 import (
 4     "log"
 5     "math/rand"
 6     "time"
 7 )
 8 
 9 type Seat int
10 type Bar chan Seat
11 
12 func (bar Bar) ServeConsumer(customerId int) {
13     log.Print("-> consumer#", customerId, " enters the bar")
14     seat := <-bar // need a seat to drink
15     log.Print("consumer#", customerId, " drinks at seat#", seat)
16     time.Sleep(time.Second * time.Duration(2+rand.Intn(6)))
17     log.Print("<- consumer#", customerId, " frees seat#", seat)
18     bar <- seat // free the seat and leave the bar
19 }
20 
21 func main() {
22     rand.Seed(time.Now().UnixNano())
23 
24     bar24x7 := make(Bar, 10) // the bar has 10 seats
25     // Place seats in an bar.
26     for seatId := 0; seatId < cap(bar24x7); seatId++ {
27         bar24x7 <- Seat(seatId) // none of the sends will block
28     }
29 
30     // a new consumer try to enter the bar for each second
31     for customerId := 0; ; customerId++ {
32         time.Sleep(time.Second)
33         go bar24x7.ServeConsumer(customerId)
34     }
35 }

3.3 互斥量

  互斥量相當於二元信號里,所以 cap 為 1 的 channel 可以當成互斥量使用:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     mutex := make(chan struct{}, 1) // the capacity must be one
 7 
 8     counter := 0
 9     increase := func() {
10         mutex <- struct{}{} // lock
11         counter++
12         <-mutex // unlock
13     }
14 
15     increase1000 := func(done chan<- struct{}) {
16         for i := 0; i < 1000; i++ {
17             increase()
18         }
19         done <- struct{}{}
20     }
21 
22     done := make(chan struct{})
23     go increase1000(done)
24     go increase1000(done)
25     <-done; <-done
26     fmt.Println(counter) // 2000
27 }

4. 關閉 channel

  關閉不再需要使用的 channel 並不是必須的。跟其他資源比如打開的文件、socket 連接不一樣,這類資源使用完后不關閉後會造成句柄泄露,channel 使用完后不關閉也沒有關係,channel 沒有被任何協程用到后最終會被 GC 回收。關閉 channel 一般是用來通知其他協程某個任務已經完成了。golang 也沒有直接提供判斷 channel 是否已經關閉的接口,雖然可以用其他不太優雅的方式自己實現一個:

1 func isClosed(ch chan int) bool {
2     select {
3     case <-ch:
4         return true
5     default:
6     }
7     return false
8 }

  不過實現一個這樣的接口也沒什麼必要。因為就算通過 isClosed() 得到當前 channel 當前還未關閉,如果試圖往 channel 里寫數據,仍然可能會發生 panic ,因為在調用 isClosed() 后,其他協程可能已經把 channel 關閉了。
關閉 channel 時應該注意以下準則:

  • 不要在讀取端關閉 channel ,因為寫入端無法知道 channel 是否已經關閉,往已關閉的 channel 寫數據會 panic ;
  • 有多個寫入端時,不要再寫入端關閉 channle ,因為其他寫入端無法知道 channel 是否已經關閉,關閉已經關閉的 channel 會發生 panic ;
  • 如果只有一個寫入端,可以在這個寫入端放心關閉 channel 。

  關閉 channel 粗暴一點的做法是隨意關閉,如果產生了 panic 就用 recover 避免進程掛掉。稍好一點的方案是使用標準庫的 sync 包來做關閉 channel 時的協程同步,不過使用起來也稍微複雜些。下面介紹一種優雅些的做法。

4.1 一寫多讀

  這種場景下這個唯一的寫入端可以關閉 channel 用來通知讀取端所有數據都已經寫入完成了。讀取端只需要用 for range 把 channel 中數據遍歷完就可以了,當 channel 關閉時,for range 仍然會將 channel 緩衝中的數據全部遍歷完然後再退出循環:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6 )
 7 
 8 func main() {
 9     wg := &sync.WaitGroup{}
10     ch := make(chan int, 100)
11 
12     send := func() {
13         for i := 0; i < 100; i++ {
14             ch <- i
15         }
16         // signal sending finish
17         close(ch)
18     }
19 
20     recv := func(id int) {
21         defer wg.Done()
22         for i := range ch {
23             fmt.Printf("receiver #%d get %d\n", id, i)
24         }
25         fmt.Printf("receiver #%d exit\n", id)
26     }
27 
28     wg.Add(3)
29     go recv(0)
30     go recv(1)
31     go recv(2)
32     send()
33 
34     wg.Wait()
35 }

4.2 多寫一讀

  這種場景下雖然可以用 sync.Once 來解決多個寫入端重複關閉 channel 的問題,但更優雅的辦法設置一個額外的 channel ,由讀取端通過關閉來通知寫入端任務完成不要再繼續再寫入數據了:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6 )
 7 
 8 func main() {
 9     wg := &sync.WaitGroup{}
10     ch := make(chan int, 100)
11     done := make(chan struct{})
12 
13     send := func(id int) {
14         defer wg.Done()
15         for i := 0; ; i++ {
16             select {
17             case <-done:
18                 // get exit signal
19                 fmt.Printf("sender #%d exit\n", id)
20                 return
21             case ch <- id*1000 + i:
22             }
23         }
24     }
25 
26     recv := func() {
27         count := 0
28         for i := range ch {
29             fmt.Printf("receiver get %d\n", i)
30             count++
31             if count >= 1000 {
32                 // signal recving finish
33                 close(done)
34                 return
35             }
36         }
37     }
38 
39     wg.Add(3)
40     go send(0)
41     go send(1)
42     go send(2)
43     recv()
44 
45     wg.Wait()
46 }

4.2 多寫多讀

  這種場景稍微複雜,和上面的例子一樣,也需要設置一個額外 channel 用來通知多個寫入端和讀取端。另外需要起一個額外的協程來通過關閉這個 channel 來廣播通知:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6     "time"
 7 )
 8 
 9 func main() {
10     wg := &sync.WaitGroup{}
11     ch := make(chan int, 100)
12     done := make(chan struct{})
13 
14     send := func(id int) {
15         defer wg.Done()
16         for i := 0; ; i++ {
17             select {
18             case <-done:
19                 // get exit signal
20                 fmt.Printf("sender #%d exit\n", id)
21                 return
22             case ch <- id*1000 + i:
23             }
24         }
25     }
26 
27     recv := func(id int) {
28         defer wg.Done()
29         for {
30             select {
31             case <-done:
32                 // get exit signal
33                 fmt.Printf("receiver #%d exit\n", id)
34                 return
35             case i := <-ch:
36                 fmt.Printf("receiver #%d get %d\n", id, i)
37                 time.Sleep(time.Millisecond)
38             }
39         }
40     }
41 
42     wg.Add(6)
43     go send(0)
44     go send(1)
45     go send(2)
46     go recv(0)
47     go recv(1)
48     go recv(2)
49 
50     time.Sleep(time.Second)
51     // signal finish
52     close(done)
53     // wait all sender and receiver exit
54     wg.Wait()
55 }

  channle 作為 golang 最重要的特性,用起來還是比較方便的。傳統的 C 里要實現類似的功能的話,一般需要用到 socket 或者 FIFO 來實現,另外還要考慮數據包的完整性與併發衝突的問題,channel 則屏蔽了這些底層細節,使用者只需要考慮讀寫就可以了。 channel 是引用類型,了解一下 channel 底層的機制對更好的使用 channel 還是很用必要的。雖然操作原語簡單,但涉及到阻塞的問題,使用不當可能會造成死鎖或者無限制的協程創建最終導致進程掛掉。

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

【其他文章推薦】

※專營大陸空運台灣貨物推薦

台灣空運大陸一條龍服務

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

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