abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之一(二十七)

 



 

一.前言

       通過前面的文章的學習,我們已經有實現了傳統的ASP.NET Core MVC+EasyUI的增刪改查功能。本篇文章我們要實現了使用ABP提供的WebAPI方式+EasyUI來實現增刪改查的功能。本文中我們將不在使用DataGrid表格控件,而是使用樹形表格(TreeGrid)控件。

二、樹形表格(TreeGrid)介紹

       我先上圖,讓我們來看一下功能完成之後的組織管理信息列表頁面。如下圖。

       這個組織管理列表頁面使用TreeGrid來實現的。我們接下來介紹一下TreeGrid。

     首先、在定義TreeGrid時有兩個屬性必須要有一個是idField,這個要唯一;另一個是treeField的定義,這是樹節點的值,必須要有。 

     其次、easyui加載treegrid的json數據格式有三種,我就介紹我常用的這種。其他兩種方式,請查看easyui的相關文檔。

  {"total":7,"rows":[

           {"id":1,"name":"All Tasks","begin":"3/4/2010","end":"3/20/2010","progress":60,"iconCls":"icon-ok"},      
{"id":2,"name":"Designing","begin":"3/4/2010","end":"3/10/2010","progress":100,"_parentId":1,"state":"closed"},
{"id":21,"name":"Database","persons":2,"begin":"3/4/2010","end":"3/6/2010","progress":100,"_parentId":2},
{"id":22,"name":"UML","persons":1,"begin":"3/7/2010","end":"3/8/2010","progress":100,"_parentId":2}, {"id":23,"name":"Export Document","persons":1,"begin":"3/9/2010","end":"3/10/2010","progress":100,"_parentId":2},
{"id":3,"name":"Coding","persons":2,"begin":"3/11/2010","end":"3/18/2010","progress":80},
{"id":4,"name":"Testing","persons":1,"begin":"3/19/2010","end":"3/20/2010","progress":20} ],"footer":[ {"name":"Total Persons:","persons":7,"iconCls":"icon-sum"} ]}

     下面介紹一下上面數據中的幾個重要屬性:

     1)  _parentId :字段_parentId必不可少,且名稱唯一。記得前面有“_” ,他是用來記錄父級節點,沒有這個屬性,是沒法展示父級節點 其次就是這個父級節點必須存在,不然信息也是展示不出來,在後台遍歷組合的時候,如果父級節點不存在或為0時,此時 _parentId 應該不賦值,或設為“”。如果賦值 “0” 則在表格中不显示數據。

    2) state:是否展開

     3) checked:是否選中(用於複選框)

    4) iconCls:選項前面的圖標,如果自己不設定,父級節點默認為文件夾圖標,子級節點為文件圖標

 

    下面我們來開始實現組織管理頁面的相關功能。首先我們要創建一個組織信息實體。

三、創建Org實體

       1. 在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.Core”項目的“Entitys”文件夾,在彈出菜單中選擇“添加” >

 > “類”。 將類命名為 Org,然後選擇“添加”。

      2.創建Org類繼承自Entity<int>,通過實現審計模塊中的IHasCreationTime來實現保存創建時間。根據TreeGrid所需要的數據格式的要求。代碼如下:

using Abp.Domain.Entities;
using Abp.Domain.Entities.Auditing;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
 

namespace ABP.TPLMS.Entitys
{

   public partial class Org : Entity<int>, IHasCreationTime
{
      int m_parentId = 0;
        public Org()
        {

            this.Id = 0;
            this.Name = string.Empty;
            this.HotKey = string.Empty;
            this.ParentId = 0;
            this.ParentName = string.Empty;

            this.IconName = string.Empty;

            this.Status = 0;

            this.Type = 0;

            this.BizCode = string.Empty;
            this.CustomCode = string.Empty;
            this.CreationTime = DateTime.Now;
            this.UpdateTime = DateTime.Now;
            this.CreateId = 0;

            this.SortNo = 0;

        }

        [Required]
        [StringLength(255)]
        public string Name { get; set; }

        [StringLength(255)]
        public string HotKey { get; set; }

        public int ParentId { get { return m_parentId; } set { m_parentId = value; } }

        [Required]
        [StringLength(255)]
        public string ParentName { get; set; }

        public bool IsLeaf { get; set; }

        public bool IsAutoExpand { get; set; }

        [StringLength(255)]
        public string IconName { get; set; } 

        public int Status { get; set; }

        public int Type { get; set; }

        [StringLength(255)]
        public string BizCode { get; set; }

        [StringLength(100)]
        public string CustomCode { get; set; }

        public DateTime CreationTime { get; set; }
        public DateTime UpdateTime { get; set; }
        public int CreateId { get; set; }

        public int SortNo { get; set; }
public int? _parentId {
            get {
                if (m_parentId == 0)                

                {
                    return null;
                }

                return m_parentId;
            }           

        }
    }
}

      3.定義好實體之後,我們去“ABP.TPLMS.EntityFrameworkCore”項目中的“TPLMSDbContext”類中定義實體對應的DbSet,以應用Code First 數據遷移。添加以下代碼

 

using Microsoft.EntityFrameworkCore;
using Abp.Zero.EntityFrameworkCore;
using ABP.TPLMS.Authorization.Roles;
using ABP.TPLMS.Authorization.Users;
using ABP.TPLMS.MultiTenancy;
using ABP.TPLMS.Entitys;
 

namespace ABP.TPLMS.EntityFrameworkCore
{

    public class TPLMSDbContext : AbpZeroDbContext<Tenant, Role, User, TPLMSDbContext>
    {

        /* Define a DbSet for each entity of the application */
    
        public TPLMSDbContext(DbContextOptions<TPLMSDbContext> options)
            : base(options)

        {
        }

        public DbSet<Module> Modules { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
  public DbSet<Cargo> Cargos { get; set; }
          public DbSet<Org> Orgs { get; set; }

    }
}

 

 

 

      4.從菜單中選擇“工具->NuGet包管理器器—>程序包管理器控制台”菜單。

     5. 在PMC中,默認項目選擇EntityframeworkCore對應的項目后。輸入以下命令:Add-Migration AddEntityOrg,創建遷移。如下圖。

 

       6. 在上面的命令執行完畢之後,創建成功后,會在Migrations文件夾下創建時間_AddEntityOrg格式的類文件,這些代碼是基於DbContext指定的模型。如下圖。

 

     7.在程序包管理器控制台,輸入Update-Database,回車執行遷移。執行成功后,如下圖。

 

     8. 在SQL Server Management Studio中查看數據庫,Orgs表創建成功。

 

 

 

 

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

工信部第285批新車公示218款新能源入選

根據《中華人民共和國行政許可法》和《國務院對確需保留的行政審批專案設定行政許可的決定》的規定,工信部日前將許可的汽車、摩托車、三輪汽車和低速貨車生產企業及產 品(第285批)予以了公告,共有218款新能源車型入選。進入該公告的新能源汽車可開展生產銷售,但是要獲得補貼,還需再獲得《新能源汽車推廣應用推薦車型目錄》准入。

純電動轎車/乘用車方面,北汽、長城、禦捷馬、卡威、吉利等12款車型入選。

插電式乘用車方面,比亞迪、寶馬、之諾、上汽等7款車型入選。

純電動客車方面,安凱、江淮、安源、北奔、北方、福田、比亞迪、白雲、五菱、陸地方舟、尼歐凱、友誼、青年、海格、開沃、依維柯、飛燕、大通、象牌、野馬、華新、金龍、金旅、宇通、黃河、中通、中植汽車、穗通等28個品牌75款車型入選。

插電式混動客車方面, 安凱、海格、易聖達、金龍、金旅、宇通6個品牌21款車型入選。

新能源專用車方面,福田、北京、大運、黃海、東風、華神、揚子江、東風、福建、環球、藍速、田野、中悅 、卡威、陸地方舟、青年曼、康迪、五菱、暢達、躍進、金龍、凱馬、時風、太行成功、東風、金杯、邢牛、海德、解放、神州、宇通、長帆汽車、迪馬、炫虎等33個品牌103款車型入選。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

財政部將公開新能源汽車推廣騙補核查和處理結果

5月28日,財政部發佈聲明稱,關於新能源汽車推廣騙補核查,現場核查已經完成,目前處於會審階段。核查及處理情況,將按資訊公開有關規定及時公開。

中國新能源汽車產業的發展受到政策的強力推動。從2010年開始,我國便實施新能源汽車補貼政策,由於監督機制不完善,騙補隨之愈演愈烈。2016年1月份,工信部、財政部、科技部、發改委聯合啟動對新能源汽車相關情況的專項核查工作,新能源汽車生產企業、運營企業、租賃企業、企事業單位等新能源汽車使用者全部列入核查物件。國務院也把遏制新能源汽車騙補行為作為重點工作之一。

此前,央視曝光了10家涉及騙補的企業,分別是蘇州吉姆西客車製造有限公司、陝西通家汽車股份有限公司、重慶力帆乘用車有限公司、江蘇陸地方舟新能源電動汽車有限公司、奇瑞萬達貴州客車股份有限公司、國宏汽車有限公司、江蘇奧新新能源汽車有限公司、蕪湖寶騏汽車製造有限公司、重慶力帆汽車有限公司,以及金華青年汽車製造有限公司。這些企業的共同特點是,2015年12月單月產量(主要依據是機動車出廠合格證)均超過全年產量的50%。

對於騙補行為,工信部部長苗圩曾公開表示,“局部地區確實存在少部分企業騙補的現象,對於騙補企業,沒補貼的錢不會下發,已補貼的錢一定要扣回。依法進行處置,直至取消這些企業的資質”。

對於騙補的企業到底有哪些?將受到怎樣的處罰?這些問題一直受到業界的關注和猜測。此次,財政部公開發佈新能源汽車推廣核查有關情況的聲明,表示“現場核查已經完成,目前處於會審階段”。聲明特別強調“財政部和部內有關司局至今未接受過媒體採訪,核查及處理情況,將按資訊公開有關規定及時公開”。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

別翻了,這篇文章就是要讓你入門java多線程!

目錄

就在前幾天,有位讀者朋友私信宜春,說期待出一篇多線程的文章,我當時內心是小鹿亂撞啊….於是這幾天茶不思飯不想,好幾天深夜皆是輾轉反側,兩目深凝,以至於這幾天走起路來格外飄飄然,左搖右晃的,魔鬼般的步伐,一般兩步,走在大馬路中央上差點被打~我承認太誇張了,感覺又要被打~。最終還是君意不可違,答應了這位讀者朋友,從這位讀者朋友的博客頭像可以看的出來,這位朋友絕bi歷經滄桑,對生活無盡的坦然浩對,看透俗世凡塵、世態炎涼、趨炎附勢,擁有着極高的安心恬盪情懷…啥?啥子?這個是系統默認頭像….嗯嗯嗯呃。。。那個那個宜春啥都沒說哈,別把什麼事都扯宜春身上,你們一天天的,我啥都沒說(理直氣壯)…

@

1. 理解線程與進程

由於併發肯定涉及到多線程,因此在進入併發編程主題之前,我們先來了解一下進程和線程的由來,這對後面對併發編程的理解將會有很大的幫助。

進程和線程的對比這一知識點由於過於基礎,正因為過於基礎,所以我們更應該透徹它!我們必須掌握什麼是線程和進程,掌握線程與進程的關係、區別及優缺點 !

1.1、何為進程?

首先我們來看一下進程的概念:

進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

看完之後,是不是感覺很抽象?很懵bi?懵bi就對了,說明你和我智商一樣高….~開個玩笑~

不妨先憋棄上面的概念,放鬆一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出遊戲並且保持微笑,然後正襟危坐心平氣和的看宜春寫的博客….

這個時候的你不僅僅是愉快的擼了一把遊戲,而且還親自體驗擼了一把進程…其實在你雙擊打開LOL的時候就已經創建了進程,此話怎講?眾所周知,我們的電腦安裝的軟件比如:LOL、微信、谷歌等等都是存儲在我們的硬盤上的,硬盤上的數據可以說是永久存儲(ORM),當我們雙擊LOL的時候,LOL程序執行就進入了內存中,所有的程序必須進入內存中才能執行,內存屬於臨時存儲(RAM),而進入內存的程序都可以叫做是進程,把LOL程序退出的時候,LOL程序就會退出內存,進程也就隨之銷毀了!因此說各位擼了一把進程也不為過吧。

啥?字太多了,看的不夠明了,不如看圖得勁….額。。。

上面主要是通過抽象的描述了進程,其實進程是可以很直觀的看的到的,我們可以再電腦底部任務欄,右鍵—–>打開任務管理器,可以查看當前任務的進程:

其實,關於線程博主我完全可以一兩句話概括,但是這樣並不負責,畢竟這篇文章標題就是要讓你徹底入門java多線程。如果連進程都理解不好談何徹底理解多線程?

1.2、何為線程?

同樣的,我們先來看線程的概念

線程是進程中的一個執行單位,負責當前進程中程序的執行。一個進程中至少有一個線程,也就是說一個進程可以有多個線程的,而多個線程的進程運用程序就叫做多線程程序

線程的概念稍微好理解很多,但是想更深層次的去理解光靠上面一段文字的概述是完全不夠的!

這不打LOL的過程中,屬實卡的一批,果然花高價998買的6手戴爾筆記本打LOL屬實像極了愛情。這個時候不得不雙擊打開電腦安全管家進行殺毒,果然2500天沒有進行過病毒查殺,我天。。。其實我相信很多人都用過電腦管家或者手機管家之類的安全軟件,我們都很清楚我們開啟病毒查殺之後一般要幾分鐘掃描查殺,這個時候我們是可以讓它後台進行的,我們不會等而是開啟另一個垃圾清理的功能,這個時候我們也不會等而是再去啟動電腦加速功能。等到 這些操作都完成之後果斷退出電腦管家,繼續LOL,果然高價998買的6手戴爾筆記本再怎麼殺毒打LOL還是照樣的卡….

其實清楚線程必然涉及到CPU的相關概念了,將上面文字所描述的用圖片概括,大致為:

1.3、何為多線程?

從上一節中,我們也提到過多線程,所以理解起來應該不難。

多線程就是多個線程同時運行交替運行

單核CPU:交替運行。
多核CPU:同時運行。

其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。

1.4、何為線程調度優先級?

說起線程調度優先級這個概念,就讓我想到現在我們大部分人投簡歷一樣。如果你的學歷或者工作經驗越高,那麼你的優先級就越高,面試官很大幾率就會讓你去面試但也不是一定只是幾率特別大,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性)!在我們每個人的電腦中線程是可以設置線程的優先級的,但是生活中沒有優先級(學歷、工作經驗)的孩子就只能靠自己的能力了~媽耶,太真實了…~

線程優先級具有繼承特性比如A線程啟動B線程,則B線程的優先級和A是一樣的。

線程優先級具有隨機性也就是說線程優先級高的不一定每一次都先執行完,只是被執行的可能性更大。

在今後的多線程學習旅遊中我們會使用到getPriority()方法獲取線程的優先級。

1.5、為什麼提倡使用多線程而不是多進程?

線程與進程相似,但線程是一個比進程更小的執行單位,是程序執行的最小單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。同時線程是程序執行的最小單位。使用多線程而不是用多進程去進行併發程序的設計,是因為線程間的切換和調度的成本遠遠小於進程。

而使用多線程,多線程會將程序運行方式從串行運行變為併發運行,效率會有很大提高。

2、理解并行和併發

在博主認為併發和并行是兩個非常容易被混淆的概念。為了防止繞暈大家,所以我選擇長話短說!

  1. 併發:一個時間段內同時發生(並不是同時發生)。
  2. 并行:同一時刻發生(真正的同時發生)。

它們都可以表示兩個或者多個任務一起執行,但是偏重點有些不同。

於此同時,我們不妨回顧一下上面所提到過的CPU,並再次理解併發與并行的區別,從而溫故知新 ~我TM簡直是個天才!~

單核CPU:交替運行【併發】
多核CPU:同時運行【并行】

併發給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的!

3、特殊的一個單線程:主線程(Main線程)

我們常說的主線程就是Main線程,它是一個特殊的單線程,話不多說,直接擼碼:

定義一個用於測試的demo類Person

package demo;

public class Person {
   public String name;

   public Person(String name){
       this.name=name;
   }

   public void run(){
       int i=1;
       while (i<5){
           System.out.println(name+i);
           i++;
       }
   }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

編寫Main方法

package demo;

public class MainThreadDemo {
    public static void main(String[] args) {
        Person per=new Person("常威");
        per.run();

        Person Per2=new Person("來福");
        Per2.run();
    }
}

運行結果就已經很顯而易見了,放心我不是靠你們運行結果而是單純的先分析主線程。

運行結果:
    常威1
    常威2
    常威3
    常威4
    來福1
    來福2
    來福3
    來福4

3.1、分析主線程原理

3.2、 單線程的局限性

單線程不僅效率低下,而且存在很大的局限性,惟一的優點就是安全。所以說女孩子長得安全其實也是一種優點,噗哈哈哈…

如何體現出單線程效率低下以及它的局限性呢?其實只要一句代碼即可,還是以上面的單線程Main線程為例:

package demo;

public class MainThreadDemo {
    public static void main(String[] args) {
        Person per=new Person("常威");
        per.run();
        int a=6/0;  //=====================特別注意這行代碼
        Person Per2=new Person("來福");
        Per2.run();
    }
}

試想一下運行結果…

如果對上面的運行結果有問題,或者疑問。那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),很有可能異常機制沒學好,好吧我給你貼出來:

言歸正傳,效率低下何以見得?這是數據少,如果是一億條數據呢,單線程就是一個一個打印。那局限性又何以見得呢?從上面運行結果來看也能看出,只因為一行代碼而導致下面代碼不再執行。已經很明顯了。

4、 創建多線程的四種方式

說是說創建多線程有四種方式,但考慮到是入門文章還是主要寫入門的兩種方式,剩下的兩個暫時忽略。忽略的兩種方法有:實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorServiceCallableFuture實現有返回結果的線程。現在可能對於入門的童鞋來說是接收不了的,以後再去了解也不晚!

4.1、繼承Thread類

Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來代表這段程序流。

Java中通過繼承Thread類來創建啟動多線程的步驟如下:

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱為線程執行體。
  2. 創建Thread子類的實例,即創建了線程對象
  3. 調用線程對象的start()方法來啟動該線程

代碼如下:

測試類:

public class Demo01 {
    public static void main(String[] args) {
        //創建自定義線程對象
        MyThread mt = new MyThread("新的線程!");
        //開啟新線程
        mt.start();
        //在主方法中執行for循環
        for (int i = 0; i < 10; i++) {
            System.out.println("main線程!"+i);
        }
    }
}

自定義線程類:

public class MyThread extends Thread {
    //定義指定線程名稱的構造方法
    public MyThread(String name) {
        //調用父類的String參數的構造方法,指定線程的名稱
        super(name);
    }
    /**
     * 重寫run方法,完成該線程執行的邏輯
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+":正在執行!"+i);
        }
    }
}

Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新線程並執行自己定義的run()方法。

4.2、實現Runnable接口

如果自己的類已經繼承另一個類,就無法直接繼承Thread,此時,可以實現一個Runnable接口來創建線程,顯然實現Runnable接口方式創建線程的優勢就很明顯了。

直接擼碼:

自定義一個類實現Runnable接口,並重寫接口中的run()方法,併為run方法添加要執行的代碼方法。

public class RunableDemo implements Runnable{

    @Override
    public void run() {
        int a = 1;
        while (a<20){
            System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()為獲取當前線程的名字
            a++;
        }
    }
}

編寫Main方法

為了啟動自定義類RunableDemo ,需要首先實例化一個Thread,並傳入RunableDemo 實例

public class MainThreadDemo {

    public static void main(String[] args) {
        RunableDemo runn=new RunableDemo();
        
        //實例化一個Thread並傳入自己的RunableDemo 實例
        Thread thread=new Thread(runn);
        thread.start();

        int a = 1;
        while (a<20){
            //Thread.currentThread().getName()為獲取當前線程的名字
            System.out.println(Thread.currentThread().getName()+ a);
            a++;
        }
    }
}

運行結果:

main1
main2
main3
Thread-01
Thread-02
Thread-03
Thread-04
Thread-05
Thread-06
....

其實多運行幾遍,你會方法每次運行的結果順序都不一樣,這主要是由於多線程會去搶佔CPU的資源,誰搶到了誰就執行,而Main和Thread兩個線程一直在爭搶。

實際上,當傳入一個Runnable target(目標)參數給Thread后,Threadrun()方法就會調用target.run(),參考JDK源代碼:

public void run() {  
  if (target != null) {  
   target.run();  
  }  
}  

4.3、兩種入門級創建線程的區別

採用繼承Thread類方式:

(1)優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程。
(2)缺點:因為線程類已經繼承了Thread類,所以不能再繼承其他的父類。

採用實現Runnable接口方式:

(1)優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相
同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
(2)缺點:編程稍微複雜,如果需要訪問當前線程,必須使用Thread.currentThread()方法。

小結:
如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

實現Runnable接口比繼承Thread類的優勢:

1.適合多個相同代碼的線程去處理同一個資源。

2.可以避免java中單繼承的限制。

3.增加代碼的健壯性,實現解耦。代碼可以被多個線程共享,代碼和數據獨立。

4.線程池中只能放入實現Runnable或Callable類線程,不能放入繼承Thread的類【線程池概念之後會慢慢涉及】

所以,如果選擇哪種方式,盡量選擇實現Runnable接口

其實學到後面的線程池,你會發現上面兩種創建線程的方法實際上很少使用,一般都是用線程池的方式比較多一點。使用線程池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節併發處理這一部分也強調到“線程資源必須通過線程池提供,不允許在應用中自行显示創建線程”。不過處於入門階段的童鞋博主還是強烈建議一步一個腳印比較好!

5、使用匿名內部類方式創建線程

談起匿名內部類,可能很多小白是比較陌生的,畢竟開發中使用的還是比較少,但是同樣是非常重要的一個知識!於此同時我就貼出關於匿名內部類的文章如果小白童鞋能看懂下面這個代碼,真的你不需要看那篇文章了,你T喵的簡直是個天才!

package AnonymousInner;

public class NiMingInnerClassThread {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i<5;i++){
                    System.out.println("熊孩子:"+i);
                }
            }
        };
        new Thread(r).start();
        for (int i = 0; i < 5 ; i++){
            System.out.println("傻狍子:"+i);
        }
    }
}

小白童鞋還愣着幹啥呀趕緊去補補…

6、線程安全問題

線程安全問題主要是共享資源競爭的問題,也就是在多個線程情況下,一個或多個線程同時搶佔同一資源導致出現的一些不必要的問題,最典型的例子就是火車四個窗口售票問題了,這裏就不再舉售票例子了,已經爛大街了,這裏就簡單實現一個線程安全問題代碼….

實現Runnable接口方式為例,主要實現過程是:實例化三個Thread,並傳入同一個RunableDemo 實例作為參數,最後開啟三條相同參數的線程,代碼如下:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據
    
    @Override
    public void run() {
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}
public class MainThreadDemo {

    public static void main(String[] args) {
        RunableDemo runn=new RunableDemo();
        
        Thread thread1=new Thread(runn);
        Thread thread2=new Thread(runn);
        Thread thread3=new Thread(runn);
        thread1.start();
        thread2.start();
        thread3.start();
        }
 }

運行結果:

Thread-0==100
Thread-0==99
Thread-1==100
Thread-1==97
Thread-1==96
Thread-1==95
Thread-2==98
...

根據結果可以看出,確實是三條線程(Thread-0、1、2)在執行,安全問題就出在線程會出現相同的結果比如上面的100就出現了兩次,如果循環條件更改一下可能也會出現負數的情況。這種情況該怎麼解決呢?這個時候就需要線程同步了!

7、解決線程安全問題:線程同步

實際上,線程安全問題的解決方法有三種:

1、同步代碼塊
2、同步方法
3、鎖機制

7.1、 synchronized同步代碼塊

第一種方法:同步代碼塊

格式:

synchronized(鎖對象) {
可能會出現線程安全問題的代碼(訪問共享數據的代碼)
}

使用同步代碼塊特別注意:
1、通過代碼塊的鎖對象,可以是任意對象
2、必須保證多個線程使用的鎖對象必須是同一個
3、鎖對象的作用是把同步代碼快鎖住,只允許一個線程在同步代碼塊執行

還是以上麵線程安全問題為例子,使用同步代碼塊舉例:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據

    Object object=new Object(); //事先準備好一個鎖對象

    @Override
    public void run() {
        synchronized (object){  //使用同步代碼塊
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
        }
    }
}

Main方法沒有任何改動,運行一下結果是絕對沒問題的,數據都是正確的沒有出現重複情況這一出,各位可以自己嘗試一下!

同步代碼塊的原理:

使用了一個鎖對象,叫同步鎖,對象鎖,也叫同步監視器,當開啟多個線程的時候,多個線程就開始搶奪CPU的執行權,比如現在t0線程首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖對象,發現有,則獲取該鎖對象,執行同步代碼塊中的代碼。之後當CUP切換線程時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖對象時發現沒有鎖對象,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖對象,t1才可以獲取從而進入同步代碼塊執行。
同步中的線程,沒有執行完畢是不會釋放鎖的,這樣便實現了線程對臨界區的互斥訪問,保證了共享數據安全。
缺點:頻繁的獲取釋放鎖對象,降低程序效率

7.2、同步方法

使用步驟:

1、把訪問了共享數據的代碼抽取出來,放到一個方法中
2、在該方法上添加 synchronized 修飾符

格式:

修飾符 synchronized 返回值類型 方法名稱(參數列表) {
  方法體...
}

代碼示例:

public class RunableDemo implements Runnable{
    public int a = 100;//線程共享數據

    @Override
    public void run() {
        while (true){
            sell(); //調用下面的sell方法
        }
    }
    
    //訪問了共享數據的代碼抽取出來,放到一個方法sell中 
    public synchronized void sell(){
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}

同步方法的也是一樣鎖住同步的代碼,但是鎖對象的是Runable實現類對象,也就是this,誰調用方法,就是誰。

說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖對象就不是this了,靜態同步方法的鎖對象是本類的class屬性,class文件對象(反射)

public class RunableDemo implements Runnable{
    public static int a = 100;//線程共享數據     =====此時共享數據也要加上static

    @Override
    public void run() {
        while (true){
            sell();
        }
    }

    public static synchronized void sell(){  //注意添加了static關鍵字
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
    }
}

使用靜態同步方法時,此時共享數據也要加上static,因為static成員才能訪問static成員,如果對static關鍵字不是他別理解的可以補補了,放心,博主有信心讓你有所收穫,會讓你重新認識到static的魅力:

當然靜態同步方法了解即可!

7.3、Lock鎖

Lock接口位於java.util.concurrent.locks.Lock它是JDK1.5之後出現的,Lock接口中的方法:

void lock(): 獲取鎖

 

void unlock(): 釋放鎖

Lock接口的一個實現類java.util.concurrent.locks.ReentrantLock implements Lock接口

使用方法:
1、在Runable實現類的成員變量創建一個ReentrantLock對象
2、在可能產生線程安全問題的代碼該對象調用lock方法獲取鎖
3、在可能產生線程安全問題的代碼該對象調用unlock方法釋放鎖

代碼示例:

import java.util.concurrent.locks.ReentrantLock;

public class RunableDemo implements Runnable{
    public static int a = 100;//線程共享數據

    //1、在Runable實現類的成員變量創建一個ReentrantLock對象============
    ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        // 2、在可能產生線程安全問題的代碼前該對象調用lock方法獲取鎖=======
        reentrantLock.lock();
        while (a>0){
            System.out.println("線程"+Thread.currentThread().getName()+"執行到"+ a);
            a--;
        }
        // 3、在可能產生線程安全問題的代碼后該對象調用unlock方法獲取鎖======
        reentrantLock.unlock();
    }

}

當然更安全的寫法是,在線程安全問題代碼中try...catchy,最後在finally語句中添加reentrantLock.unlock();,這樣方為上上策!

7.4、三種方法小結

第一種
synchronized 同步代碼塊:可以是任意的對象必須保證多個線程使用的鎖對象是同一個

 

第二種
synchronized 同步方法: 鎖對象是this,誰調用鎖對象就是誰

 

synchronized 靜態同步方法: 鎖對象是其class對象,該對象可以用this.getClass()方法獲取,也可以使用當前類名.class 表示。【了解即可】

 

第三種
Look鎖方法:該方法提供的方法遠遠多於synchronized方式,主要在Runable實現類的成員變量創建一個ReentrantLock對象,並使用該對象調用lock方法獲取鎖以及unlock方法釋放鎖!

8、線程常用方法

8.1、Thread類

  Thread():用於構造一個新的Thread。

  Thread(Runnable target):用於構造一個新的Thread,該線程使用了指定target的run方法。

  Thread(ThreadGroup group,Runnable target):用於在指定的線程組中構造一個新的Thread,該

  線程使用了指定target的run方法。

  currentThread():獲得當前運行線程的對象引用。

  interrupt():將當前線程置為中斷狀態。

  sleep(long millis):使當前運行的線程進入睡眠狀態,睡眠時間至少為指定毫秒數。

  join():等待這個線程結束,即在一個線程中調用other.join(),將等待other線程結束后才繼續本線程。

  yield():當前執行的線程讓出CPU的使用權,從運行狀態進入就緒狀態,讓其他就緒線程執行。

8.2、Object類

  wait():讓當前線程進入等待阻塞狀態,直到其他線程調用了此對象的notify()或notifyAll()方法后,當前線程才被喚醒進入就緒狀態。

  notify():喚醒在此對象監控器(鎖對象)上等待的單個線程。

  notifyAll():喚醒在此對象監控器(鎖對象)上等待的所有線程。

注意:wait()、notify()、notifyAll()都依賴於同步鎖,而同步鎖是對象持有的,且每個對象只有一個,所以這些方法定義在Object類中,而不是Thread類中。

8.3、yield()、sleep()、wait()比較

   wait():讓線程從運行狀態進入等待阻塞狀態,並且會釋放它所持有的同步鎖。

   yield():讓線程從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖。

   sleep():讓線程從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖。

9、線程的狀態

以上只是簡單的一個線程狀態圖,其實線程狀態博大精深,要講清楚還是要一大篇文筆,作為入門文章先了解一下吧,之後的併發編程文章將再講述吧!

如果想要去深入了解一下的話也是可以的:

10、線程池

在java中只要說到池,基本都是一個套路,啥數據庫連接池、jdbc連接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建資源而消耗過多資源。

10.1、線程池概述

線程池其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

合理利用線程池能夠帶來三個好處:

  1. 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
  2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

10.2、 線程池的使用

Java裏面線程池的最頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService

要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。

Executors類中有個創建線程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法如下:

  • public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

使用線程池中線程對象的步驟:

  1. 創建線程池對象。
  2. 創建Runnable接口子類對象。(task)
  3. 提交Runnable接口子類對象。(take task)
  4. 關閉線程池(一般不操作這一步)。

Runnable實現類代碼:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一個游泳教練");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("教練來了: " + Thread.currentThread().getName());
        System.out.println("教我游泳,教會後,教練又回到了游泳池");
    }
}

線程池測試類:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 創建線程池對象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2個線程對象
        // 創建Runnable實例對象
        MyRunnable r = new MyRunnable();

        //自己創建線程對象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 調用MyRunnable中的run()

        // 從線程池中獲取線程對象,然後調用MyRunnable中的run()
        service.submit(r);
        // 再獲取個線程對象,調用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法調用結束后,程序並不終止,是因為線程池控制了線程的關閉。
        // 將使用完的線程又歸還到了線程池中
        // 關閉線程池
        //service.shutdown();
    }
}

以上只是簡單的使用線程池,僅僅是入門階段!道阻且長,路還很長….

到這裏,本文章入門暫時告一段落,以後有時間盡量抽空更新….

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝你~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

用雲開發快速製作客戶業務需求收集小程序丨實戰

一、導語

​ 如何省去企業上門(現場)搜集客戶需求的環節,節約企業人力和時間成本,將客戶的業務定製需求直接上傳至雲數據庫?雲開發為我們提供了這個便利!

二、需求背景

​ 作為一名XX公司IT萌萌新,這段時間對小程序開發一直有非常濃厚的興趣,並且感慨於“雲開發·不止於快”的境界。近期工作中,剛好碰見業務部門的一個需求,目的是節約上門跟客戶收集業務定製資料的時間,以往是每變更一次,就需要上門一次,碰見地域較遠的,費時費力,且往往要求幾天內完成上線,時間非常緊迫。因此,結合一直以來對雲開發的各種優勢的了解,我說服公司領導通過小程序·雲開發來實現。

下面是其中一項業務定製界面的展示:
(1)業務對業務流程有簡單說明;
(2)相關業務介紹;
(3)不同客戶輸入個性化需求;
(4)雲存儲後台實現需求表單的收集。

​ 得力於雲開發提供的API和WeUI庫的便利,本項目在我在極短的時間內就實現了比較理想的效果 。接下來,我就從本項目入手,講講我是如何依靠小程序·雲開發將想法快速實現的,其實我也是剛入門沒多久,只是想分享一下自身在學習小程序開發項目中的一些知識點和體會,代碼可能略為粗糙,邏輯也有待優化,歡迎大家在評論區多多交流。

三、開發過程

1、組件

主要使用了官方WeUI擴展能力的一些組件庫來實現主要功能。

核心的WeUI庫主要有 Msg、Picker、圖片的Upload等(以快為目的,節省自己寫CSS樣式的時間,也方便0基礎的同學上手,這裏又體會到了小程序開發的便捷)。

2、實現代碼

本次雲開發包括雲數據庫雲存儲兩大功能:

(1)雲數據庫

雲數據庫的主要就是搜集客戶提交上來的表單信息,包括客戶的聯繫方式和選擇的業務類型等,並存儲在雲數據庫中,方便業務經理搜集需求。

我們來看簡單的實現過程:

首先是表單,用到了 form 表單組件以及它的 bindsubmit 方法,在 wxml 中放置 form 表單:

<form bindsubmit="formSubmit">
    <view class="form">
      <view class="section">
        <picker bindchange="bindPickerGsd" mode="selector" value="{{indexGsd}}" range="{{arrayGsd}}">
          <view class="picker">歸屬縣市</view>
          <view class="picker-content" >{{arrayGsd[indexGsd]?arrayGsd[indexGsd]:"(必填項) 請下拉選擇歸屬地"}}</view> 
        </picker>
      </view>    
      <!---中間部分詳見代碼--->
    </view>

    <view class="footer">
      <button class="dz-btn" formType="submit" loading="{{formStatus.submitting}}" disabled="{{formStatus.submitting}}" bindtap="openSuccess">提交</button>
    </view>
  </form>

表單中除了普通的文本輸入,增加有下拉列表的實現(畢竟客戶有時候是比較懶的)。

來看一下具體代碼:

bindPickerGsd: function (e) {    
  console.log('歸屬地已選擇,攜帶值為', e.detail.value)
  console.log('歸屬地選擇:', this.data.arrayGsd[e.detail.value])    
  this.setData({
     indexGsd: e.detail.value     
   })   
   this.data.formData.home_county = this.data.arrayGsd[e.detail.value]
},

最後表單上傳到雲數據庫:

  // 表單提交
  formSubmit: function (e) {
    var minlength = '';
    var maxlength = '';
    console.log("表單內容",e)
    var that = this;
    var formData = e.detail.value;
    var result = this.wxValidate.formCheckAll(formData);
    
    console.log("表單提交formData", formData);
    console.log("表單提交result", result)
    wx.showLoading({
      title: '發布中...',
    })
    const db = wx.cloud.database()
    db.collection('groupdata').add({
      data: {
        time: getApp().getNowFormatDate(),
        home_county: this.data.formData.home_county,
        group_name: formData.group_name,
        contact_name: formData.contact_name,
        msisdn: formData.msisdn,
        product_name: this.data.formData.product_name,
        word: formData.word,
      },
      success: res => {
        wx.hideLoading()
        console.log('發布成功', res)

      },
      fail: err => {
        wx.hideLoading()
        wx.showToast({
          icon: 'none',
          title: '網絡不給力....'
        })
        console.error('發布失敗', err)
      }
    })
  },
(2)雲存儲

​ 因為業務的定製需要填單客戶所在單位的授權證明材料,因此需要提單人(使用人)上傳證明文件,因此增加了使用雲存儲的功能。

核心代碼:

    promiseArr.push(new Promise((reslove,reject)=>{
        wx.cloud.uploadFile({
            cloudPath: "groupdata/" + group_name + "/" + app.getNowFormatDate() +suffix,
            filePath:filePath
        }).then(res=>{
            console.log("授權文件上傳成功")          
            })
            reslove()
            }).catch(err=>{
            console.log("授權文件上傳失敗",err)
    })

    因為涉及到不同頁面的數據傳遞,即將表單頁面的group_name作為雲存儲的文件夾用於存儲該客戶在表單中上傳的圖片,因此還需要用到getCurrentPages()來進行頁面間的數據傳遞 

    var pages = getCurrentPages();
    var prePage = pages[pages.length - 2];//pages.length就是該集合長度 -2就是上一個活動的頁面,也即是跳過來的頁面
    var group_name = prePage.data.formData.group_name.value//取上頁data里的group_name數據用於標識授權文件所存儲文件夾的名稱

3、待進一步優化

​ 基於時間關係,本次版本僅對需求進行了簡單實現,作為公司一個可靠的項目,還需要關注”客戶隱私”、“數據安全”,以及更人性化的服務。比如:

(1)提單人確認和認證過程

可靠性:增加驗證碼驗證(防止他人冒名登記),以及公司受理業務有個客戶本人提交憑證。

(2)訂閱消息

受理成功后,可以給客戶進行處理結果的反饋,增強感知。

(3)人工客服

進行在線諮詢等。

四、總結

​ 在本次項目開發中,我深刻體會到了雲開發的“快”,特別是雲數據庫的增刪查改功能非常方便。雲開發提供的種種便利,讓我在有新創意的時候,可以迅速採用小程序雲開發快速實現,省時省力,還能免費使用騰訊雲服務器,推薦大家嘗試!

源碼地址

如果你想要了解更多關於雲開發CloudBase相關的技術故事/技術實戰經驗,請掃碼關注【騰訊云云開發】公眾號~

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Cesium坐標系及坐標轉換詳解

前言

Cesium項目中經常涉及到模型加載、瀏覽以及不同數據之間的坐標轉換,弄明白Cesium中採用的坐標系以及各個坐標系之間的轉換,是我們邁向三維GIS大門的前提,本文詳細的介紹了Cesium中採用的兩大坐標系以及之間轉換的各種方法。

Cesium中的坐標系

Cesium中常用的坐標有兩種WGS84地理坐標系和笛卡爾空間坐標系,我們平時常用的以經緯度來指明一個地點就是用的WGS84坐標,笛卡爾空間坐標系常用來做一些空間位置變換如平移旋轉縮放等等。二者的聯繫如下圖。

其中,WGS84地理坐標系包括 WGS84經緯度坐標系(沒有實際的對象)和 WGS84弧度坐標系(Cartographic);

         笛卡爾空間坐標系包括 笛卡爾空間直角坐標系(Cartesian3)、平面坐標系(Cartesian2),4D笛卡爾坐標系(Cartesian4)。

WGS84坐標系

World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統,坐標原點為地球質心,其地心空間直角坐標系的Z軸指向BIH (國際時間服務機構)1984.O定義的協議地球極(CTP)方向,X軸指向BIH 1984.0的零子午面和CTP赤道的交點,Y軸與Z軸、X軸垂直構成右手坐標系。我們平常手機上的指南針显示的經緯度就是這個坐標系下當前的坐標,進度範圍[-180,180],緯度範圍[-90,90]。

WGS84坐標系

Cesium目前支持兩種坐標系WGS84和WebMercator,但是在Cesium中沒有實際的對象來描述WGS84坐標,都是以弧度的方式來進行運用的也就是Cartographic類:

new Cesium.Cartographic(longitude, latitude, height),這裏的參數也叫longitude、latitude,就是經度和緯度,計算方法:弧度= π/180×經緯度角度。

 笛卡爾空間直角坐標系(Cartesian3)

笛卡爾空間坐標的原點就是橢球的中心,我們在計算機上進行繪圖時,不方便使用經緯度直接進行繪圖,一般會將坐標系轉換為笛卡爾坐標系,使用計算機圖形學中的知識進行繪圖。這裏的Cartesian3,有點類似於三維繫統中的Point3D對象,new Cesium.Cartesian3(x, y, z),裏面三個分量x、y、z。

笛卡爾空間直角坐標系

平面坐標系(Cartesian2)

平面坐標系也就是平面直角坐標系,是一個二維笛卡爾坐標系,與Cartesian3相比少了一個z的分量,new Cesium.Cartesian2(x, y)。Cartesian2經常用來描述屏幕坐標系,比如鼠標在電腦屏幕上的點擊位置,返回的就是Cartesian2,返回了鼠標點擊位置的xy像素點分量。

平面坐標系

坐標轉換

經緯度和弧度的轉換

var radians=Cesium.Math.toRadians(degrees);//經緯度轉弧度
var degrees=Cesium.Math.toDegrees(radians);//弧度轉經緯度

WGS84經緯度坐標和WGS84弧度坐標系(Cartographic)的轉換

//方法一:
var longitude = Cesium.Math.toRadians(longitude1); //其中 longitude1為角度

var latitude= Cesium.Math.toRadians(latitude1); //其中 latitude1為角度

var cartographic = new Cesium.Cartographic(longitude, latitude, height);

//方法二:
var cartographic= Cesium.Cartographic.fromDegrees(longitude, latitude, height);//其中,longitude和latitude為角度

//方法三:
var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude為弧度

WGS84坐標系和笛卡爾空間直角坐標系(Cartesian3)的轉換

通過經緯度或弧度進行轉換
var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);//其中,高度默認值為0,可以不用填寫;longitude和latitude為角度

var positions = Cesium.Cartesian3.fromDegreesArray(coordinates);//其中,coordinates格式為不帶高度的數組。例如:[-115.0, 37.0, -107.0, 33.0]

var positions = Cesium.Cartesian3.fromDegreesArrayHeights(coordinates);//coordinates格式為帶有高度的數組。例如:[-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]

//同理,通過弧度轉換,用法相同,具體有Cesium.Cartesian3.fromRadians,Cesium.Cartesian3.fromRadiansArray,Cesium.Cartesian3.fromRadiansArrayHeights等方法

注意:上述轉換函數中最後均有一個默認參數ellipsoid(默認值為Ellipsoid.WGS84)。

通過過度進行轉換

具體過度原理可以參考上邊的注意事項。

var position = Cesium.Cartographic.fromDegrees(longitude, latitude, height);
var positions = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
var positions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray([position1,position2,position3]);

笛卡爾空間直角坐標系轉換為WGS84

直接轉換
var cartographic= Cesium.Cartographic.fromCartesian(cartesian3);

轉換得到WGS84弧度坐標系后再使用經緯度和弧度的轉換,進行轉換到目標值

間接轉換
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
var cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray([cartesian1,cartesian2,cartesian3]);

平面坐標系(Cartesian2)和笛卡爾空間直角坐標系(Cartesian3)的轉換

平面坐標系轉笛卡爾空間直角坐標系

這裏注意的是當前的點(Cartesian2)必須在三維球上,否則返回的是undefined;通過ScreenSpaceEventHandler回調會取到的坐標都是Cartesian2。

屏幕坐標轉場景坐標-獲取傾斜攝影或模型點擊處的坐標

這裏的場景坐標是包含了地形、傾斜攝影表面、模型的坐標。

通過viewer.scene.pickPosition(movement.position)獲取,根據窗口坐標,從場景的深度緩衝區中拾取相應的位置,返回笛卡爾坐標。

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var position = viewer.scene.pickPosition(movement.position);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:若屏幕坐標處沒有傾斜攝影表面、模型時,獲取的笛卡爾坐標不準,此時要開啟地形深度檢測(viewer.scene.globe.depthTestAgainstTerrain = true; //默認為false)。

屏幕坐標轉地表坐標-獲取加載地形后對應的經緯度和高程

這裡是地球表面的世界坐標,包含地形,不包括模型、傾斜攝影表面。

通過viewer.scene.globe.pick(ray, scene)獲取,其中ray=viewer.camera.getPickRay(movement.position)。

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var ray = viewer.camera.getPickRay(movement.position);
     var position = viewer.scene.globe.pick(ray, viewer.scene);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為該點的地形高程值。

屏幕坐標轉橢球面坐標-獲取鼠標點的對應橢球面位置

這裏的橢球面坐標是參考橢球的WGS84坐標(Ellipsoid.WGS84),不包含地形、模型、傾斜攝影表面。

通過 viewer.scene.camera.pickEllipsoid(movement.position, ellipsoid)獲取,可以獲取當前點擊視線與橢球面相交處的坐標,其中ellipsoid是當前地球使用的橢球對象:viewer.scene.globe.ellipsoid,默認為Ellipsoid.WGS84

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var position = viewer.scene.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為0(此值應該為地表坐標減去地形的高程)。

笛卡爾空間直角坐標系轉平面坐標系

var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

空間位置變換

經緯度轉換到笛卡爾坐標系后就能運用計算機圖形學中的仿射變換知識進行空間位置變換如平移旋轉縮放。

Cesium為我們提供了很有用的變換工具類:Cesium.Cartesian3(相當於Point3D)Cesium.Matrix3(3×3矩陣,用於描述旋轉變換)Cesium.Matrix4(4×4矩陣,用於描述旋轉加平移變換),Cesium.Quaternion(四元數,用於描述圍繞某個向量旋轉一定角度的變換)。

下面舉個例子:

      一個局部坐標為p1(x,y,z)的點,將它的局部坐標原點放置到loc(lng,lat,alt)上,局部坐標的z軸垂直於地表,局部坐標的y軸指向正北,並圍繞這個z軸旋轉d度,求此時p1(x,y,z)變換成全局坐標笛卡爾坐p2(x1,y1,z1)是多少?

var rotate = Cesium.Math.toRadians(d);//轉成弧度
var quat = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, rotate); //quat為圍繞這個z軸旋轉d度的四元數
var rot_mat3 = Cesium.Matrix3.fromQuaternion(quat);//rot_mat3為根據四元數求得的旋轉矩陣
var v = new Cesium.Cartesian3(x, y, z);//p1的局部坐標
var m = Cesium.Matrix4.fromRotationTranslation(rot_mat3, Cesium.Cartesian3.ZERO);//m為旋轉加平移的4x4變換矩陣,這裏平移為(0,0,0),故填個Cesium.Cartesian3.ZERO
m = Cesium.Matrix4.multiplyByTranslation(m, v);//m = m X v
var cart3 = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lng, lat, alt)); //得到局部坐標原點的全局坐標
var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(cart3);//m1為局部坐標的z軸垂直於地表,局部坐標的y軸指向正北的4x4變換矩陣
m = Cesium.Matrix4.multiplyTransformation(m, m1);//m = m X m1
var p2 = Cesium.Matrix4.getTranslation(m);//根據最終變換矩陣m得到p2
console.log('x=' + p2.x + ',y=' + p2.y + ',z=' + p2.z );

總結

通過本文,介紹了各個坐標系間的轉換問題,在具體項目中,可結合實際需求,靈活組合解決具體的實際問題。注意,博文是參照網上相關博客及結合自己的實踐總結得來,希望本文對你有所幫助,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Tsx寫一個通用的button組件

一年又要到年底了,vue3.0都已經出來了,我們也不能一直還停留在過去的js中,是時候學習並且在項目中使用一下Ts了。

  如果說jsx是基於js的話,那麼tsx就是基於typescript的

  廢話也不多說,讓我們開始寫一個Tsx形式的button組件,

  ts真的不僅僅只有我們常常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的

  項目是基於vue-cli 3.0 下開發的,可以自己配置Ts,不會的話那你真的太難了

  

 

     我們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊

  我們再button文件夾下創建 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺少的

       

 

   分析一下button需要的一些東西

  第一個當然是props,還有一個是點擊事件,所以我們第一步就定義一下這兩個類型

type ButtonProps = {
  tag: string,
  size: ButtonSize,
  type: ButtonType,
  text: String
}

type ButtonEvents = {
  onClick?(event: Event) :void
}
type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

  因為button是很簡單的組件,內部也沒有一些特別的狀態需要改變,所以我們用函數式組件的方式去寫(之後的render會用到這個方法)

function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
  const { tag, size, type } = props
  let text
  console.log(slots)
  text = slots.default ? slots.default() : props.text
  function onClick (event: Event) {
    emit(ctx, 'click', event)
  }
  let classes = [size,type]
  return (
    <tag
      onClick = {onClick}
      class = {classes}
    >
      {text}
    </tag>
  )
}

  h 是一個預留參數,這裏並沒有用到 ,CreateElement  這個是vue從2.5之後提供的一個類型,也是為了方便在vue項目上使用ts

  props 就是button組件的傳入的屬性,slots插槽,ctx,代表的是當前的組件,可以理解為當前rendercontext執行環境this

  DefaultSlots是我們自定義的一個插槽類型

export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;

export type ScopedSlots = {
  [key: string]: ScopedSlot | undefined;
}

  插槽的內容我們都是需要從ctx中讀取的,默認插槽的key就是defalut,具名插槽就是具體的name

  button放發內部還有一個具體的點擊事件,還有一個emit方法,從名字我們也可以看的出,他是粗發自定義事件的,我們這裏當然不能使用this.emit去促發,

  所以我們需要單獨這個emit方法,我們知道組件內所以的自定義事件都是保存在listeners里的,我們從ctx中拿取到所以的listeners



  import { RenderContext, VNodeData } from ‘vue’ // 從vue中引入一些類型


function
emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

  這樣我們組件內部的事件觸發就完成了

  我們的button肯定是有一些默認的屬性,所以,我們給button加上默認的屬性

Button.props = {
  text: String,
  tag: {
    type: String,
    default: 'button'
  },
  type: {
    type: String,
    default: 'default'
  },
  size: {
    type: String,
    default: 'normal'
  }
}

  我們定義一個通用的functioncomponent 類型

type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
  (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
  props?: PropsDefs
}

  PropsDefinition<T>  這個是vue內部提供的,對 props的約束定義

  不管怎麼樣我們最終返回的肯定是一個對象,我們把這個類型也定義一下

  ComponentOptions<Vue> 這個也是vue內部提供的

 interface DrmsComponentOptions extends ComponentOptions<Vue> {
  functional?: boolean;
  install?: (Vue: VueConstructor) => void;
}

  最終生成一個組件對象

function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
  return {
    functional: true, // 函數時組件,這個屬性一定要是ture,要不render方法,第二個context永遠為underfine
    props: fn.props,
    model: fn.model,
    render: (h, context): any => fn(h, context.props, unifySlots(context), context)
  }
}

  unifySlots 是讀取插槽的內容

// 處理插槽的內容
export function unifySlots (context: RenderContext) {
  // use data.scopedSlots in lower Vue version
  const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
  const slots = context.slots()

  Object.keys(slots).forEach(key => {
    if (!scopedSlots[key]) {
      scopedSlots[key] = () => slots[key]
    }
  })

  return scopedSlots
}

  當然身為一個組件,我們肯定是要提供全局注入接口,並且能夠按需導入

  所以我們給組件加上名稱和install方法,install 是 vue.use() 方法使用的,這樣我們能全部註冊組件

export function CreateComponent (name:string) {
  return function <Props = DefaultProps, Events = {}, Slots = {}> (
    sfc:DrmsComponentOptions | FunctionComponent) {
    if (typeof sfc === 'function') {
      sfc = transformFunctionComponent(sfc)
    }
    sfc.functional = true
    sfc.name = 'drms-' + name
    sfc.install = install
    return sfc 
  }
}

  index.tsx 中的最後一步,導出這個組件

export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

  還少一個install的具體實現方法,加上install方法,就能全局的按需導入了

function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
  const { name } = this
  Vue.component(name as string, this)
}

 

   最終實現的效果圖,事件的話也是完全ok的,這個我也是測過的

 

   代碼參考的是vant的源碼:

  該代碼已經傳到git:     dev分支應該是代碼全的,master可能有些並沒有合併

 

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

【其他文章推薦】

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

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

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

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

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

GM 首款自動駕駛電動車將透過租車平台展開服務

2016 年年初GM 宣佈向線上租車服務平臺Lyft 投資5 億美元,雙方將在自動駕駛汽車的應用上展開合作,近日GM 高管Pam Fletcher 透露,GM 的首款自動駕駛電動車將透過Lyft 的租車服務平臺推出,能夠為用戶提供更好的乘坐體驗。

目前各大傳統汽車廠商紛紛進入無人駕駛領域,GM 也不例外,據GM 自動駕駛技術部門首席工程師Pam Fletcher 透露,雖然GM 還沒有正式宣佈自動駕駛車的發表日期,但這一切會比外界預期的更早到來,GM 正在和線上租車服務Lyft 展開合作,開發一個租車分享平臺,這不是一個停留在概念階段的專案,GM 的團隊已經準備好把這一服務推廣到市場。

GM 的首款自動駕駛汽車將是電動車,目前電動車是GM 重點推進的產品,2016 年年底該公司將推出可遠程行駛的電動車BoltEV,這是一款為城市通勤設計的電動車,同時也考慮了租車服務的需求,Pam Fletcher 認為將自動駕駛技術應用在電動車上非常有意義,能夠給用戶帶來更好的乘坐體驗,電動車行駛時平穩、安靜,乘客可以在車中休息或者處理工作事務。

2016 年3 月GM 收購自動駕駛技術研發公司Cruise Automation,以提升該公司在這一領域的技術實力,2016 年5 月Cruise 帶來的自動駕駛技術已經在Bolt 電動車上進行測試。目前GM、Lyft 共同研發的租車分享系統與Bolt 電動車的自動駕駛技術測試分屬不同的專案,但未來有可能會結合。

據悉GM 和Lyft 有可能在2016 年年底將自動駕駛電動車帶來公路上測試,Bolt 電動車有可能成為主要的測試車款。Bolt 電動車的許多設計看起來都像是為自動駕駛而設計的,未來自動駕駛與線上租車服務結合也並不讓人感到意外。

(本文授權自《》──〈〉)

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

【其他文章推薦】

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

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

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

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

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

mysql 索引筆記

MyISAM引擎的B+Tree的索引

通過上圖可以直接的看出, 在MyISAM對B+樹的運用中明顯的特點如下:

  • 所有的非恭弘=叶 恭弘子節點中存儲的全部是索引信息
  • 在恭弘=叶 恭弘子節點中存儲的 value值其實是 數據庫中某行數據的index

MyISAM引擎 索引文件的查看:

在 /var/lib/mysql目錄中

.myd 即 my data , 數據庫中表的數據文件

.myi 即 my index , 數據庫中 索引文件

.log 即 mysql的日誌文件

InnoDB引擎 索引文件的查看:

同樣在 /var/lib/mysql 目錄下面

InnoDB引擎的B+Tree的索引

InnoDB的實現方式業內也稱其為聚簇索引, 什麼是聚簇索引呢? 就是相鄰的行的簡直被存儲到一起, 對比上面的兩幅圖片就會發現, 在InnDB中, B+樹的恭弘=叶 恭弘子節點中存儲的是數據行中的一行行記錄, 缺點: 因為索引文件被存放在硬盤上, 所以很占硬盤的空間

一般我們會在每一個表中添加一列 取名 id, 設置它為primary key , 即將他設置成主鍵, 如果使用的存儲引擎也是InnoDB的話, 底層就會建立起主鍵索引, 也是聚簇索引, 並且會自動按照id的大小為我們排好序,(因為它的一個有序的樹)

局部性原理

局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。 更進一步說, 當我們通過程序向操作系統發送指令讓它讀取我們指定的數據時, 操作系統會一次性讀取一頁(centos 每頁4kb大小,InnoDB存儲引擎中每一頁16kb)的數據, 它遵循局部性理論, 猜測當用戶需要使用某個數據時, 用戶很可能會使用這個數據周圍的數據,故而進行一次

InnoDB的頁格式

什麼是頁呢? 簡單說,就是一條條數據被的存儲在磁盤上, 使用數據時需要先將數據從磁盤上讀取到內存中, InnoDB每次讀出數據時同樣會遵循 局部性原理, 而不是一條條讀取, 於是InnoDB將數據劃分成一個一個的頁, 以頁作為和磁盤之間交互的基本單位

通過如下sql, 可以看到,InnoDB中每一頁的大小是16kb

show global status like 'Innodb_page_size';

名稱 簡述
File Header 文件頭部, 存儲頁的一些通用信息
Page Header 頁面頭部, 存儲數據頁專有的信息
Infinum + supremum 最大記錄和最小記錄, 這是兩個虛擬的行記錄
User Records 用戶記錄, 用來實際存儲行記錄中的內容
Free Space 空閑空間, 頁中尚位使用的空間
Page Directory 頁面目錄, 存儲頁中某些記錄的位置
File Tailer 文件尾部 , 用來校驗頁是否完整

InnoDB的行格式 compact

每一頁中存儲的行數據越多. 整體的性能就會越強

compact的行格式如下圖所示

可以看到在行格式中在存儲真正的數據的前面會存儲一些其他信息, 這些信息是為了描述這條記錄而不得不添加的一些信息, 這些額外的信息就是上圖中的前三行

  • 變長字段的長度列表

在mysql中char是固定長度的類型, 同時mysql還支持諸如像 varchar這樣可變長度的類型, 不止varchar , 想 varbinary text blob這樣的變長數據類型, 因為 變長的數據類型的列存儲的數據的長度是不固定的, 所以說我們在存儲真正的數據時, 也得將這些數據到底佔用了多大的長度也給保存起來

  • NULL標誌位

compact行格式會將值可以為NULL的列統一標記在 NULL標誌位中, 如果數據表中所有的字段都被標記上not null , 那麼就沒有NULL值列表

  • 記錄頭信息

記錄頭信息, 顧名思義就是用來描述記錄頭中的信息, 記錄頭信息由固定的5個字節組成, 一共40位, 不同位代表的意思也不同, 如下錶

名稱 單位 bit 簡介
預留位1 1 未使用
預留位2 1 未使用
delete_mark 1 標記改行記錄是否被刪除了
min_rec_mark 1 標記在 B+樹中每層的非恭弘=叶 恭弘子節點中最小的node
n_owned 4 表示當前記錄擁有的記錄數
heap_no 13 表示當前記錄在堆中的位置
record_type 3 表示當前記錄的類型 , 0表示普通記錄, 1表示B+樹中非恭弘=叶 恭弘子節點記錄, 2表示最小記錄 ,3表示最大記錄
next_record 16 表示下一條記錄的相對位置

行溢出

在mysql中每一行, 能存儲的最大的字節數是65535個字節數, 此時我們使用下面的sql執行時就會出現行溢出現象

CREATE TABLE test ( c VARCHAR(65535) ) CHARSET=ascii ROW_FORMAT=Compact;

給varchar申請最大65535 , 再加上compact行格式中還有前面三個非數據列佔用內存,所以一準溢出, 如果不想溢出, 可以適當的將 65535 – 3

頁溢出

前面說了, InnoDB中數據的讀取按照頁為單位, 每一頁的大小是 16kb, 換算成字節就是16384個字節, 但是每行最多存儲 65535個字節啊, 也就是說一行數據可能需要好幾個頁來存儲

怎麼辦呢?

  • compact行格式會在存儲真實數據的列中多存儲一部分數據, 這部分數據中存儲的就是下一頁的地址
  • dynamic行格式 中直接存儲數據所在的地址, 換句話說就是數據都被存儲在了其他頁上
  • compressed行格式會使用壓縮算法對行格式進行壓縮處理

一般我們都是將表中的id列設置為主鍵, 這就會形成主鍵索引, 於是我們需要注意了:

主鍵的佔用的空間越小,整體的檢索效率就會越高

為什麼這麼說呢? 這就可以結合頁的概念來解析, 在B+樹這種數據結果中, 恭弘=叶 恭弘子節點中用來存儲數據, 存儲數據的格式類似Key-value key就是索引值, value就是數據內容, 如果索引佔用的空間太大的話, 單頁16kb能存儲的索引就越小, 這就導致數據被分散在更多的頁上, 致使查詢的效率降低

建立索引的技巧

為某一列建立索引

給text表中的title列創建索引, 索引名字 my_index
alter table text add index my_index (title);

雖然建立索引能提升查詢的效率, 根據前人的經驗看, 這並不是一定的, 建立索引本身會直接消耗內存空間, 同時索, 插入,刪除, 這種寫操作就會打破B+樹的平衡面臨索引的重建, 一般出現如下兩種情況時,是不推薦建立索引的

  1. 表中的數據本身就很少
  2. 我們計算一下索引的選擇性很低

兼顧 – 索引的選擇性與前綴索引

所謂選擇性,其實就是說不重複出現的索引值(基數,Cardinality) 與 表中的記錄數的比值

即: 選擇性= 基數 / 記錄數

選擇性的取值範圍在(0,1]之間, 選擇性越接近1 , 說明建立索引的必要性就越強, 比如對sex列進行建立索引,這裏面非男即女, 如果對它建立索引的話, 其實是沒意義的, 還不如直接進行全表掃描來的快

如何使用sql計算選擇性呢? 嚴格遵循上面的公式

SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
count(基數/記錄數)
DISTINCT(title) / /count(*)

更詳細的例子看下面的連接

索引失效問題

注意事項

  • 索引無法存儲null值

  • 如果條件中有or, 即使條件中存在索引也不會使用索引,如果既想使用or,又想使用索引, 就給所有or條件控制的列加上索引

  • 使用like查詢時, 如果以%開頭,肯定是進行全表掃描

  • 使用like查詢時, 如果%在條件後面

    • 對於主鍵索引, 索引失效
    • 對於普通索引, 索引不失效
  • 如果列的類型是字符串類型, 那麼一定要在條件中將數據用引號引起來,不然也會是索引失效

  • 如果mysql認為全表掃描比用索引塊, 同樣不會使用索引

聯合索引

什麼是聯合索引

聯合索引, 也叫複合索引,說白了就是多個字段一起組合成一個索引

像下面這樣使用 id + title 組合在一起構成一個聯合索引

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8
  • 如果我們像上圖那樣創建了索引,我們只要保證我們的 id+title 兩者結合起來全局唯一就ok
  • 建立聯合索引同樣是需要進行排序的,排序的規則就是按照聯合索引所有列組成的字符串的之間的先後順序進行排序, 如a比b優先

左前原則

使用聯合索引進行查詢時一定要遵循左前綴原則, 什麼是左前綴原則呢? 就是說想讓索引生效的話,一定要添加上第一個索引, 只使用第二個索引進行查詢的話會導致索引失效

比如上面創建的聯合索引, 假如我們的查詢條件是 where id = ‘1’ 或者 where id = ‘1’ and title = ‘唐詩宋詞’ 索引都會不失效

但是如果我們不使用第一個索引id, 像這樣 where title = ‘唐詩’ , 結果就是導致索引失效

聯合索引的分組&排序

還是使用這個例子:

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8

demo1: 當我們像下面這樣寫sql時, 就會先按照id進行排序, 當id相同時,再按照title進行排序

select * form text order by id, title;

demo2: 當我們像下面這樣寫sql時, 就會先將id相同的劃分為一組, 再將title相同的劃分為一組

select id,title form text group by id, title;

demo3: ASC和DESC混用, 其實大家都知道底層使用B+樹, 本身就是有序的, 要是不加限制的話,默認就是ASC, 反而是混着使用就使得索引失效

select * form text order by id ASC, title DESC;

如何定位慢查詢

相關參數

名稱 簡介
slow_query_log 慢查詢的開啟狀態
slow_query_log_file 慢查詢日誌存儲的位置
long_query_time 查詢超過多少秒才記錄下來

常用sql

# 查看mysql是否開啟了慢查詢
show variables like 'slow_query_log';   
# 將全局變量設置為ON
set global slow_query_log ='on';
# 查看慢查詢日誌存儲的位置
show variables like 'slow_query_log_file';
# 查看規定的超過多少秒才被算作慢查詢記錄下來
show variables like 'long_query_time';
show variables like 'long_query%';
# 超過一秒就記錄 , 每次修改這個配置都重新建立一次鏈接
set global long_query_time=1; 

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

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

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

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

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

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

如何教會女友遞歸算法?

一到周末就開始放蕩自我,這不帶着女朋友去萬達電影院看電影(其實是由於整天呆在家敲代碼硬是

被女朋友強行拖拽去看電影,作為一個有理想的程序員,我想各位應該都能體諒我),一到電影院,

女朋友說要買爆米花和可樂,我當時二話沒說,臣本布衣躬耕於南陽,壤中羞澀,所以單買了爆米

花,買完都不帶回頭看老闆的那種,飲料喝多了不好,出門的時候我帶了白開水,還得虧我長得銷

魂,乍一看就能看出是個社會精神小伙,女朋友也沒多說什麼,只是對我微了微笑(我估計是被我的

顏值以及獨到的見解所折服),剛坐下沒多久,女朋友突然問我,咱們現在坐在第幾排啊?電影院里

面太黑了,看不清,沒法數,這個時候,如果是你現在你怎麼辦?別忘了你我是程序員,這個可難不

倒我,遞歸就開始排上用場了。於是我就問前面一排的人他是第幾排,你想只要在他的数字上加一,

就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也問他前面的人。就這樣一排一排往前

問,直到問到第一排的人,說我在第一排,然後再這樣一排一排再把数字傳回來。直到你前面的人告

訴你他在哪一排,於是你就知道答案了。這就是一個非常標準的遞歸求解問題的分解過程,去的過程

叫“遞”,回來的過程叫“歸”。基本上,所有的遞歸問題都可以用遞推公式來表示。我們用遞推公式將

它表示出來就是這樣的

f ( n ) = f (n – 1) + 1 其中,f ( 1 ) = 1

f(n)表示你想知道自己在哪一排,f(n-1)表示前面一排所在的排數,f(1)=1表示第一排的人知道自己在

第一排。有了這個遞推公式,我們就可以很輕鬆地將它改為遞歸代碼,如下:

int f(int n) {
  if (n == 1) return 1;
  return f(n-1) + 1;
}

女朋友不懂遞歸,於是我給她講遞歸需要滿足的三個條件:

1.一個問題的解可以分解為幾個子問題的解

何為子問題?子問題就是數據規模更小的問題。就好比,在電影院,你要知道,“自己在哪一排”的問

題,可以分解為“前一排的人在哪一排”這樣一個子問題。

2.這個問題與分解之後的子問題,除了數據規模不同,求解思路完全一樣

你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一樣的。

3.存在遞歸終止條件

把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限循環,這就需

要有終止條件。就好比,第一排的人不需要再繼續詢問任何人,就知道自己在哪一排,也就是

f(1)=1,這就是遞歸的終止條件。

如何教女友敲遞歸代碼?

剛剛鋪墊了這麼多,現在我們來看,如何來教女友敲遞歸代碼?個人覺得,寫遞歸代碼最關鍵的是寫

出遞推公式,找到終止條件,剩下將遞推公式轉化為代碼就很簡單了。

你先記住這個理論。我舉一個例子,帶你一步一步實現一個遞歸代碼,幫你理解。

假如這裡有n個台階,每次你可以跨1個台階或者2個台階,請問走這n個台階有多少種走法?如果有7個台階,你可以2,2,2,1這樣子上去,也可以1,2,1,1,2這樣子上去,總之走法有很多,那如何用編程求得總共有多少種走法呢?

我們仔細想下,實際上,可以根據第一步的走法把所有走法分為兩類,第一類是第一步走了1個台

階,另一類是第一步走了2個台階。所以n個台階的走法就等於先走1階后,n-1個台階的走法 加上先

走2階后,n-2個台階的走法。用公式表示就是:

f ( n ) = f (n – 1) + f ( n – 2 )

有了遞推公式,遞歸代碼基本上就完成了一半。我們再來看下終止條件。當有一個台階時,我們不需

要再繼續遞歸,就只有一種走法。所以f(1)=1。這個遞歸終止條件足夠嗎?我們可以用n=2,n=3這樣

比較小的數試驗一下。

n=2時,f(2)=f(1)+f(0)。如果遞歸終止條件只有一個f(1)=1,那f(2)就無法求解了。所以除了f(1)=1這一

個遞歸終止條件外,還要有f(0)=1,表示走0個台階有一種走法,不過這樣子看起來就不符合正常的

邏輯思維了。所以,我們可以把f(2)=2作為一種終止條件,表示走2個台階,有兩種走法,一步走完

或者分兩步來走。

所以,遞歸終止條件就是f(1)=1,f(2)=2。這個時候,你可以再拿n=3,n=4來驗證一下,這個終止條

件是否足夠並且正確。

我們把遞歸終止條件和剛剛得到的遞推公式放到一起就是這樣的:

f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)

有了這個公式,我們轉化成遞歸代碼就簡單多了。最終的遞歸代碼是這樣的:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

我總結一下,寫遞歸代碼的關鍵就是找到如何將大問題分解為小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼。

如果以後再遇到類似問題,A可以分解為若干子問題B、C、D情況,你可以假設子問題B、C、D已經

解決,在此基礎上思考如何解決問題A。而且,你只需要思考問題A與子問題B、C、D兩層之間的關

系即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。屏蔽掉遞

歸細節,這樣子理解起來就簡單多了。

因此,編寫遞歸代碼的關鍵是,只要遇到遞歸,我們就把它抽象成一個遞推公式,不用想一層層的調

用關係,不要試圖用人腦去分解遞歸的每個步驟

如何教女友玩轉漢羅塔

好了,講完了遞歸算法,再回到電影院,不說別的,我還真那麼做了,我真問了前面一排的人他是第

幾排如果不清楚並讓他跟我一樣問他的上一排,顯然,沒循環到第三人,我差點被認為是神經病,差

點沒被幾個社會精神小伙打si,座位事情暫時告一段落,話說這電影屬實夠無聊,於是我不知是趁熱

打鐵,還是心血來潮,非要給女朋友玩一個漢羅塔遊戲,我這暴脾氣,剛實踐遞歸算法被懟,是時候

挽回形象了,不秀一把遞歸算法我就不得勁。就是這個遊戲,至於遊戲規則,我覺得你體驗一兩把絕

對比我說的更加記憶深刻,,別看4399覺得有點弱zhi,再怎麼說也承

載着童年

果然,女朋友是個哈皮,剛過第三關就撲街了,這個時候,頭冒五丈光芒的我身披金甲挺身而出(貌

似有一點點小誇張,劇情需要嘛)一聲不吭地敲了幾行靚麗的代碼

public class TestHanoi {

    public static void main(String[] args) {
        hanoi(5,'A','B','C');  //可以理解為5個圈或者第5關
    }
    
    /**
     * @param n     共有N個圈
     * @param A    開始的柱子
     * @param B 中間的柱子
     * @param C 目標的柱子
     * 無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
     */
    public static void hanoi(int n,char A,char B,char C) {
        //只有一個圈。
        if(n==1) {
            System.out.println("第1個盤子從"+A+"移到"+C);
        //無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
        }else {
            //移動上面所有的圈到中間位置
            hanoi(n-1,A,C,B);
            //移動下面的圈
            System.out.println("第"+n+"個圈從"+A+"移到"+C);
            //把上面的所有圈從中間位置移到目標位置
            hanoi(n-1,B,A,C);
        }
    }

}

只要main方法一致行,對着結果移動即可,就跟開了掛一樣的,其實漢羅塔問題核心關鍵是無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。

到這裏,教女友敲遞歸算法代碼,你學會了嗎?

哦豁,明天還是一個晴天~老天賜給宜春一個女朋友吧~畢竟我們程序員長得又帥敲代碼又好看,是吧哥幾個~~

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

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

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

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

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

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