Alink漫談(五) : 迭代計算和Superstep_台中搬家公司

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

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

Alink漫談(五) : 迭代計算和Superstep

目錄

  • Alink漫談(五) : 迭代計算和Superstep
    • 0x00 摘要
    • 0x01 緣由
    • 0x02 背景概念
      • 2.1 四層執行圖
      • 2.2 Task和SubTask
      • 2.3 如何劃分 Task 的依據
      • 2.4 JobGraph
      • 2.5 BSP模型和Superstep
        • BSP模型
        • BSP模型的實現
        • Flink-Gelly
    • 0x03 Flink的迭代算法(superstep-based)
      • 3.1 Bulk Iterate
      • 3.2 迭代機制
    • 0x04 Alink如何使用迭代
    • 0x05 深入Flink源碼和runtime來驗證
      • 5.1 向Flink提交Job
      • 5.2 生成JobGraph
      • 5.3 迭代對應的Task
        • 5.3.1 IterationHeadTask
        • 5.3.2 IterationIntermediateTask
        • 5.3.3 IterationTailTask
          • 如何和Head建立聯繫
          • 如何把用戶返回的數值傳給Head
        • 5.3.4 IterationSynchronizationSinkTask
      • 5.4 superstep
    • 0x06 結合KMeans代碼看superset
      • 6.1 K-means算法概要
      • 6.2 KMeansPreallocateCentroid
      • 6.3 KMeansAssignCluster 和 KMeansUpdateCentroids
      • 6.4 KMeansOutputModel
    • 0x07 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。迭代算法在很多數據分析領域會用到,比如機器學習或者圖計算。本文將通過Superstep入手看看Alink是如何利用Flink迭代API來實現具體算法。

因為Alink的公開資料太少,所以以下均為自行揣測,肯定會有疏漏錯誤,希望大家指出,我會隨時更新。

0x01 緣由

為什麼提到 Superstep 這個概念,是因為在擼KMeans代碼的時候,發現幾個很奇怪的地方,比如以下三個步驟中,都用到了context.getStepNo(),而且會根據其數值的不同進行不同業務操作:

public class KMeansPreallocateCentroid extends ComputeFunction {
    public void calc(ComContext context) {
        LOG.info("liuhao  KMeansPreallocateCentroid ");
        if (context.getStepNo() == 1) {
          /** 具體業務邏輯代碼
           * Allocate memory for pre-round centers and current centers.
           */        
        }
    }
}  

public class KMeansAssignCluster extends ComputeFunction {
    public void calc(ComContext context) {
        ......
        if (context.getStepNo() % 2 == 0) {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID1);
        } else {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID2);
        }
      /** 具體業務邏輯代碼
       * Find the closest cluster for every point and calculate the sums of the points belonging to the same cluster.
       */
    }
}

public class KMeansUpdateCentroids extends ComputeFunction {
    public void calc(ComContext context) {
        if (context.getStepNo() % 2 == 0) {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID2);
        } else {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID1);
        }
      /** 具體業務邏輯代碼
       * Update the centroids based on the sum of points and point number belonging to the same cluster.
       */
    }

查看ComContext的源碼,發現stepNo的來源居然是runtimeContext.getSuperstepNumber()

public class ComContext {
   private final int taskId;
   private final int numTask;
   private final int stepNo; // 對,就是這裏
   private final int sessionId;
	public ComContext(int sessionId, IterationRuntimeContext runtimeContext) {
		this.sessionId = sessionId;
		this.numTask = runtimeContext.getNumberOfParallelSubtasks();
		this.taskId = runtimeContext.getIndexOfThisSubtask();
		this.stepNo = runtimeContext.getSuperstepNumber(); // 這裏進行了變量初始化
	}  
	/**
	 * Get current iteration step number, the same as {@link IterationRuntimeContext#getSuperstepNumber()}.
	 * @return iteration step number.
	 */
	public int getStepNo() {
		return stepNo; // 這裡是使用
	}  
}

看到這裡有的兄弟可能會虎軀一震,這不是BSP模型的概念嘛。我就是想寫個KMeans算法,怎麼除了MPI模型,還要考慮BSP模型。下面就讓我們一步一步挖掘究竟Alink都做了什麼工作。

0x02 背景概念

2.1 四層執行圖

在 Flink 中的執行圖可以分為四層:StreamGraph -> JobGraph -> ExecutionGraph -> 物理執行圖

  • StreamGraph:Stream API 編寫的代碼生成的最初的圖。用來表示程序的拓撲結構。
  • JobGraph:StreamGraph 經過優化後生成了 JobGraph, JobGraph是提交給 JobManager 的數據結構。主要的優化為,將多個符合條件的節點 chain 在一起作為一個節點,這樣可以減少數據在節點之間流動所需要的序列化/反序列化/傳輸消耗。JobGraph是唯一被Flink的數據流引擎所識別的表述作業的數據結構,也正是這一共同的抽象體現了流處理和批處理在運行時的統一。
  • ExecutionGraph:JobManager 根據 JobGraph 生成 ExecutionGraph。ExecutionGraph 是 JobGraph 的并行化版本,是調度層最核心的數據結構。
  • 物理執行圖:JobManager 根據 ExecutionGraph 對 Job 進行調度后,在各個TaskManager 上部署 Task 后形成的“圖”,並不是一個具體的數據結構。

2.2 Task和SubTask

因為某種原因,Flink內部對這兩個概念的使用本身就有些混亂:在Task Manager里這個subtask的概念由一個叫Task的類來實現。Task Manager里談論的Task對象實際上對應的是ExecutionGraph里的一個subtask。

所以這兩個概念需要理清楚。

  • Task(任務) :Task對應JobGraph的一個節點,是一個算子Operator。Task 是一個階段多個功能相同 subTask 的集合,類似於 Spark 中的 TaskSet。
  • subTask(子任務) :subTask 是 Flink 中任務最小執行單元,是一個 Java 類的實例,這個 Java 類中有屬性和方法,完成具體的計算邏輯。在ExecutionGraph里Task被分解為多個并行執行的subtask 。每個subtask作為一個excution分配到Task Manager里執行。
  • Operator Chains(算子鏈) :沒有 shuffle 的多個算子合併在一個 subTask 中,就形成了 Operator Chains,類似於 Spark 中的 Pipeline。Operator subTask 的數量指的就是算子的并行度。同一程序的不同算子也可能具有不同的并行度(因為可以通過 setParallelism() 方法來修改并行度)。

Flink 中的程序本質上是并行的。在執行期間,每一個算子 Operator (Transformation)都有一個或多個算子subTask(Operator SubTask),每個算子的 subTask 之間都是彼此獨立,並在不同的線程中執行,並且可能在不同的機器或容器上執行。

Task( SubTask) 是一個Runnable 對象, Task Manager接受到TDD 後會用它實例化成一個Task對象, 並啟動一個線程執行Task的Run方法。

TaskDeploymentDescriptor(TDD) : 是Task Manager在submitTask是提交給TM的數據結構。 他包含了關於Task的所有描述信息。比如:

  • TaskInfo : 包含該Task 執行的java 類,該類是某個 AbstractInvokable的實現類 , 當然也是某個operator的實現類 (比如DataSourceTask, DataSinkTask, BatchTask,StreamTask 等)。
  • IG描述 :通常包含一個或兩個InputGateDeploymentDescriptor(IGD)。
  • 目標RP的描述: ParitionId, PartitionType, RS個數等等。

2.3 如何劃分 Task 的依據

在以下情況下會重新劃分task

  • 并行度發生變化時
  • keyBy() /window()/apply() 等發生 Rebalance 重新分配;
  • 調用 startNewChain() 方法,開啟一個新的算子鏈;
  • 調用 diableChaining()方法,即:告訴當前算子操作不使用 算子鏈 操作。

比如有如下操作

DataStream<String> text = env.socketTextStream(hostname, port);

DataStream counts = text
    .filter(new FilterClass())
    .map(new LineSplitter())
    .keyBy(0)
    .timeWindow(Time.seconds(10))
    .sum(2)

那麼StreamGraph的轉換流是:

 Source --> Filter --> Map --> Timestamps/Watermarks --> Window(SumAggregator) --> Sink

其task是四個:

  • Source –> Filter –> Map
  • keyBy
  • timeWindow
  • Sink

其中每個task又會被分成分若干subtask。在執行時,一個Task會被并行化成若干個subTask實例進行執行,一個subTask對應一個執行線程。

2.4 JobGraph

以上說了這麼多,就是要說jobGraph和subtask,因為本文中我們在分析源碼和調試時候,主要是從jobGraph這裏開始入手來看subtask

JobGraph是在StreamGraph的基礎之上,對StreamNode進行了關聯合併的操作,比如對於source -> flatMap -> reduce -> sink 這樣一個數據處理鏈,當source和flatMap滿足鏈接的條件時,可以可以將兩個操作符的操作放到一個線程并行執行,這樣可以減少網絡中的數據傳輸,由於在source和flatMap之間的傳輸的數據也不用序列化和反序列化,所以也提高了程序的執行效率。

相比流圖(StreamGraph)以及批處理優化計劃(OptimizedPlan),JobGraph發生了一些變化,已經不完全是“靜態”的數據結構了,因為它加入了中間結果集(IntermediateDataSet)這一“動態”概念。

作業頂點(JobVertex)、中間數據集(IntermediateDataSet)、作業邊(JobEdge)是組成JobGraph的基本元素。這三個對象彼此之間互為依賴:

  • 一個JobVertex關聯着若干個JobEdge作為輸入端以及若干個IntermediateDataSet作為其生產的結果集;每個JobVertex都有諸如并行度和執行代碼等屬性。
  • 一個IntermediateDataSet關聯着一個JobVertex作為生產者以及若干個JobEdge作為消費者;
  • 一個JobEdge關聯着一個IntermediateDataSet可認為是源以及一個JobVertex可認為是目標消費者;

那麼JobGraph是怎麼組織並存儲這些元素的呢?其實JobGraph只以Map的形式存儲了所有的JobVertex,鍵是JobVertexID:

private final Map<JobVertexID, JobVertex> taskVertices = new LinkedHashMap<JobVertexID, JobVertex>();

至於其它的元素,通過JobVertex都可以根據關係找尋到。需要注意的是,用於迭代的反饋邊(feedback edge)當前並不體現在JobGraph中,而是被內嵌在特殊的JobVertex中通過反饋信道(feedback channel)在它們之間建立關係。

2.5 BSP模型和Superstep

BSP模型

BSP模型是并行計算模型的一種。并行計算模型通常指從并行算法的設計和分析出發,將各種并行計算機(至少某一類并行計算機)的基本特徵抽象出來,形成一個抽象的計算模型。

BSP模型是一種異步MIMD-DM模型(DM: distributed memory,SM: shared memory),BSP模型支持消息傳遞系統,塊內異步并行,塊間顯式同步,該模型基於一個master協調,所有的worker同步(lock-step)執行, 數據從輸入的隊列中讀取。

BSP計算模型不僅是一種體繫結構模型,也是設計并行程序的一種方法。BSP程序設計準則是整體同步(bulk synchrony),其獨特之處在於超步(superstep)概念的引入。一個BSP程序同時具有水平和垂直兩個方面的結構。從垂直上看,一個BSP程序由一系列串行的超步(superstep)組成。

BSP模型的實現

BSP模型的實現大概舉例如下:

  • Pregel :Google的大規模圖計算框架,首次提出了將BSP模型應用於圖計算,具體請看Pregel——大規模圖處理系統,不過至今未開源。
  • Apache Giraph :ASF社區的Incubator項目,由Yahoo!貢獻,是BSP的java實現,專註於迭代圖計算(如pagerank,最短連接等),每一個job就是一個沒有reducer過程的hadoop job。
  • Apache Hama :也是ASF社區的Incubator項目,與Giraph不同的是它是一個純粹的BSP模型的java實現,並且不單單是用於圖計算,意在提供一個通用的BSP模型的應用框架。

Flink-Gelly

Flink-Gelly利用Flink的高效迭代算子來支持海量數據的迭代式圖處理。目前,Flink Gelly提供了“Vertex-Centric”,“Scatter-Gather”以及“Gather-Sum-Apply”等計算模型的實現。

“Vertex-Centric”迭代模型也就是我們經常聽到的“Pregel”,是一種從Vertex角度出發的圖計算方式。其中,同步地迭代計算的步驟稱之為“superstep”。在每個“superstep”中,每個頂點都執行一個用戶自定義的函數,且頂點之間通過消息進行通信,當一個頂點知道圖中其他任意頂點的唯一ID時,該頂點就可以向其發送一條消息。

但是實際上,KMeans不是圖處理,Alink也沒有基於Flink-Gelly來構建。也許只是借鑒了其概念。所以我們還需要再探尋。

0x03 Flink的迭代算法(superstep-based)

迭代算法在很多數據分析領域會用到,比如機器學習或者圖計算。為了從大數據中抽取有用信息,這個時候往往會需要在處理的過程中用到迭代計算。

所謂迭代運算,就是給定一個初值,用所給的算法公式計算初值得到一个中間結果,然後將中間結果作為輸入參數進行反覆計算,在滿足一定條件的時候得到計算結果。

大數據處理框架很多,比如spark,mr。實際上這些實現迭代計算都是很困難的。

Flink直接支持迭代計算。Flink實現迭代的思路也是很簡單,就是實現一個step函數,然後將其嵌入到迭代算子中去。有兩種迭代操作算子: Iterate和Delta Iterate。兩個操作算子都是在未收到終止迭代信號之前一直調用step函數。

3.1 Bulk Iterate

這種迭代方式稱為全量迭代,它會將整個數據輸入,經過一定的迭代次數,最終得到你想要的結果。

迭代操作算子包括了簡單的迭代形式:每次迭代,step函數會消費全量數據(本次輸入和上次迭代的結果),然後計算得到下輪迭代的輸出(例如,map,reduce,join等)

迭代過程主要分為以下幾步:

  • Iteration Input(迭代輸入):是初始輸入值或者上一次迭代計算的結果。
  • Step Function(step函數):每次迭代都會執行step函數。它迭代計算DataSet,由一系列的operator組成,比如map,flatMap,join等,取決於具體的業務邏輯。
  • Next Partial Solution(中間結果):每一次迭代計算的結果,被發送到下一次迭代計算中。
  • Iteration Result(迭代結果):最後一次迭代輸出的結果,被輸出到datasink或者發送到下游處理。

它迭代的結束條件是:

  • 達到最大迭代次數
  • 自定義收斂聚合函數

編程的時候,需要調用iterate(int),該函數返回的是一個IterativeDataSet,當然我們可以對它進行一些操作,比如map等。Iterate函數唯一的參數是代表最大迭代次數。

迭代是一個環。我們需要進行閉環操作,那麼這時候就要用到closeWith(Dataset)操作了,參數就是需要循環迭代的dataset。也可以可選的指定一個終止標準,操作closeWith(DataSet, DataSet),可以通過判斷第二個dataset是否為空,來終止迭代。如果不指定終止迭代條件,迭代就會在迭代了最大迭代次數后終止。

3.2 迭代機制

DataSet API引進了獨特的同步迭代機制(superstep-based),僅限於用在有界的流。

我們將迭代操作算子的每個步驟函數的執行稱為單個迭代。在并行設置中,在迭代狀態的不同分區上并行計算step函數的多個實例。在許多設置中,對所有并行實例上的step函數的一次評估形成了所謂的superstep,這也是同步的粒度。因此,迭代的所有并行任務都需要在初始化下一個superstep之前完成superstep。終止準則也將被評估為superstep同步屏障。

下面是Apache原文

We referred to each execution of the step function of an iteration operator as a single iteration. In parallel setups, multiple instances of the step function are evaluated in parallel on different partitions of the iteration state. In many settings, one evaluation of the step function on all parallel instances forms a so called superstep, which is also the granularity of synchronization. Therefore, all parallel tasks of an iteration need to complete the superstep, before a next superstep will be initialized. Termination criteria will also be evaluated at superstep barriers.

下面是apache原圖

概括如下:

每次迭代都是一個superstep
    每次迭代中有若干subtask在不同的partition上分別執行step
      	 每個step有一個HeadTask,若干IntermediateTask,一個TailTask
    每個superstep有一個SynchronizationSinkTask 同步,因為迭代的所有并行任務需要在下一個迭代前完成

由此我們可以知道,superstep這是Flink DataSet API的概念,但是你從這裡能夠看到BSP模型的影子,比如:

  • 在傳統的BSP模型中,一個superstep被分為3步: 本地的計算, 消息的傳遞, 同步的barrier.
  • Barrier Synchronization又叫障礙同步或柵欄同步。每一次同步也是一個超步的完成和下一個超步的開始;
  • Superstep超步 是一次計算迭代,從起始每往前步進一層對應一個超步。
  • 程序該什麼時候結束是程序自己控制

0x04 Alink如何使用迭代

KMeansTrainBatchOp.iterateICQ函數中,生成了一個IterativeComQueue,而IterativeComQueue之中就用到了superstep-based迭代。

return new IterativeComQueue()
   .initWithPartitionedData(TRAIN_DATA, data)
   .initWithBroadcastData(INIT_CENTROID, initCentroid)
   .initWithBroadcastData(KMEANS_STATISTICS, statistics)
   .add(new KMeansPreallocateCentroid())
   .add(new KMeansAssignCluster(distance))
   .add(new AllReduce(CENTROID_ALL_REDUCE))
   .add(new KMeansUpdateCentroids(distance))
   .setCompareCriterionOfNode0(new KMeansIterTermination(distance, tol)) // 終止條件
   .closeWith(new KMeansOutputModel(distanceType, vectorColName, latitudeColName, longitudeColName)) 
   .setMaxIter(maxIter) // 迭代最大次數
   .exec();

而BaseComQueue.exec函數中則有:

public DataSet<Row> exec() {
   IterativeDataSet<byte[]> loop // Flink 迭代API
      = loopStartDataSet(executionEnvironment)
      .iterate(maxIter);
     // 後續操作能看出來,之前添加在queue上的比如KMeansPreallocateCentroid,都是在loop之上運行的。
  		if (null == compareCriterion) {
        loopEnd = loop.closeWith...
     	} else {     
        // compare Criterion.
        DataSet<Boolean> criterion = input ... compareCriterion
        loopEnd = loop.closeWith( ... criterion ... )
      }   
}

再仔細研究代碼,我們可以看出:

superstep包括:

.add(new KMeansPreallocateCentroid())
.add(new KMeansAssignCluster(distance))
.add(new AllReduce(CENTROID_ALL_REDUCE))
.add(new KMeansUpdateCentroids(distance))

終止標準就是

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

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

利用KMeansIterTermination構建了一個RichMapPartitionFunction作為終止標準。最後結束時候調用 KMeansOutputModel完成業務操作。

最大循環就是

.setMaxIter(maxIter)

於是我們可以得出結論,superstep-based Bulk Iterate 迭代算子是用來實現整體KMeans算法,KMeans算法就是一個superstep進行迭代。但是在superstep內容如果需要通訊或者柵欄同步,則採用了MPI的allReduce。

0x05 深入Flink源碼和runtime來驗證

我們需要深入到Flink內部去挖掘驗證,如果大家有興趣,可以參見下面調用棧,自己添加斷點來研究。

execute:56, LocalExecutor (org.apache.flink.client.deployment.executors)
executeAsync:944, ExecutionEnvironment (org.apache.flink.api.java)
execute:860, ExecutionEnvironment (org.apache.flink.api.java)
execute:844, ExecutionEnvironment (org.apache.flink.api.java)
collect:413, DataSet (org.apache.flink.api.java)
sinkFrom:44, PrintBatchOp (com.alibaba.alink.operator.batch.utils)
sinkFrom:20, PrintBatchOp (com.alibaba.alink.operator.batch.utils)
linkFrom:31, BaseSinkBatchOp (com.alibaba.alink.operator.batch.sink)
linkFrom:17, BaseSinkBatchOp (com.alibaba.alink.operator.batch.sink)
link:89, BatchOperator (com.alibaba.alink.operator.batch)
linkTo:239, BatchOperator (com.alibaba.alink.operator.batch)
print:337, BatchOperator (com.alibaba.alink.operator.batch)
main:35, KMeansExample (com.alibaba.alink)

5.1 向Flink提交Job

Alink和Flink構建聯繫,是在print調用中完成的。因為是本地調試,Flink會啟動一個miniCluster,然後會做如下操作。

  • 首先生成執行計劃Plan。Plan以數據流形式來表示批處理程序,但它只是批處理程序最初的表示,然後計劃會被優化以生成更高效的方案OptimizedPlan。
  • 然後,計劃被編譯生成JobGraph。這個圖是要交給flink去生成task的圖。
  • 生成一系列配置。
  • 將JobGraph和配置交給flink集群去運行。如果不是本地運行的話,還會把jar文件通過網絡發給其他節點。
  • 以本地模式運行的話,可以看到啟動過程,如啟動性能度量、web模塊、JobManager、ResourceManager、taskManager等等。

當我們看到了submitJob調用,就知道KMeans代碼已經和Flink構建了聯繫

@Internal
public class LocalExecutor implements PipelineExecutor {

   public static final String NAME = "local";

   @Override
   public CompletableFuture<JobClient> execute(Pipeline pipeline, Configuration configuration) throws Exception {

      // we only support attached execution with the local executor.
      checkState(configuration.getBoolean(DeploymentOptions.ATTACHED));

      final JobGraph jobGraph = getJobGraph(pipeline, configuration);
      final MiniCluster miniCluster = startMiniCluster(jobGraph, configuration);
      final MiniClusterClient clusterClient = new MiniClusterClient(configuration, miniCluster);

      CompletableFuture<JobID> jobIdFuture = clusterClient.submitJob(jobGraph);

      jobIdFuture
            .thenCompose(clusterClient::requestJobResult)
            .thenAccept((jobResult) -> clusterClient.shutDownCluster());

      return jobIdFuture.thenApply(jobID ->
            new ClusterClientJobClientAdapter<>(() -> clusterClient, jobID));
   }

5.2 生成JobGraph

生成jobGraph的具體流程是:

  • IterativeDataSet.closeWith會生成一個BulkIterationResultSet。
  • PrintBatchOp.sinkFrom中會調用到ExecutionEnvironment.executeAsync
  • 調用createProgramPlan構建一個Plan
  • OperatorTranslation.translate函數發現if (dataSet instanceof BulkIterationResultSet),則調用translateBulkIteration(bulkIterationResultSet);
  • 這時候生成了執行計劃Plan
  • ExecutionEnvironment.executeAsync調用LocalExecutor.execute
  • 然後調用FlinkPipelineTranslationUtil.getJobGraph來生成jobGraph
  • GraphCreatingVisitor.preVisit中會判斷 if (c instanceof BulkIterationBase),以生成BulkIterationNode
  • PlanTranslator.translateToJobGraph會調用到JobGraphGenerator.compileJobGraph,最終調用到createBulkIterationHead就生成了迭代處理的Head。
  • 最後將jobGraph提交給Cluster ,jobGraph 變形為 ExceutionGraph在JM和TM上執行。

5.3 迭代對應的Task

前面代碼中,getJobGraph函數作用是生成了job graph。

然後 JobManager 根據 JobGraph 生成 ExecutionGraph。ExecutionGraph 是 JobGraph 的并行化版本,是調度層最核心的數據結構。

最後 JobManager 根據 ExecutionGraph 對 Job 進行調度后,在各個TaskManager 上部署 Task。

所以我們需要看看最終運行時候,迭代API對應着哪些Task。

針對IterativeDataSet,即superstep-based Bulk Iterate,Flink生成了如下的task。

  • IterationHeadTask
  • IterationIntermediateTask
  • IterationTailTask
  • IterationSynchronizationSinkTask

5.3.1 IterationHeadTask

IterationHeadTask主要作用是協調一次迭代。

它會讀取初始輸入,和迭代Tail建立一個BlockingBackChannel。在成功處理輸入之後,它會發送EndOfSuperstep事件給自己的輸出。它在每次superstep之後會聯繫 synchronization task,等到自己收到一個用來同步的AllWorkersDoneEvent。AllWorkersDoneEvent表示所有其他的heads已經完成了自己的迭代。

下一次迭代時候,上一次迭代中tail的輸出就經由backchannel傳輸,形成了head的輸入。何時進入到下一個迭代,是由HeadTask完成的。一旦迭代完成,head將發送TerminationEvent給所有和它關聯的task,告訴他們shutdown。

				barrier.waitForOtherWorkers();

				if (barrier.terminationSignaled()) {
					requestTermination();
					nextStepKickoff.signalTermination();
				} else {
					incrementIterationCounter();
					String[] globalAggregateNames = barrier.getAggregatorNames();
					Value[] globalAggregates = barrier.getAggregates();
					aggregatorRegistry.updateGlobalAggregatesAndReset(globalAggregateNames, globalAggregates);
          // 在這裏發起下一次Superstep。
					nextStepKickoff.triggerNextSuperstep();
				}
			}

IterationHeadTask是在JobGraphGenerator.createBulkIterationHead中構建的。其例子如下:

"PartialSolution (Bulk Iteration) (org.apache.flink.runtime.iterative.task.IterationHeadTask)"

5.3.2 IterationIntermediateTask

IterationIntermediateTask是superstep中間段的task,其將傳輸EndOfSuperstepEvent和TerminationEvent給所有和它關聯的tasks。此外,IterationIntermediateTask能更新the workset或者the solution set的迭代狀態。

如果迭代狀態被更新,本task的輸出將傳送回IterationHeadTask,在這種情況下,本task將作為head再次被安排。

IterationIntermediateTask的例子如下:

 "MapPartition (computation@KMeansUpdateCentroids) (org.apache.flink.runtime.iterative.task.IterationIntermediateTask)"
   
 "Combine (SUM(0), at kMeansPlusPlusInit(KMeansInitCentroids.java:135) (org.apache.flink.runtime.iterative.task.IterationIntermediateTask)"
   
 "MapPartition (AllReduceSend) (org.apache.flink.runtime.iterative.task.IterationIntermediateTask)"
   
"Filter (Filter at kMeansPlusPlusInit(KMeansInitCentroids.java:130)) (org.apache.flink.runtime.iterative.task.IterationIntermediateTask)"
   

5.3.3 IterationTailTask

IterationTailTask是迭代的最末尾。如果迭代狀態被更新,本task的輸出將通過BlockingBackChannel傳送回IterationHeadTask,反饋給迭代頭就意味着一個迭代完整邏輯的完成,那麼就可以關閉這個迭代閉合環了。這種情況下,本task將在head所在的實例上重新被調度。

這裡有幾個關鍵點需要注意:

如何和Head建立聯繫

Flink有一個BlockingQueueBroker類,這是一個阻塞式的隊列代理,它的作用是對迭代併發進行控制。Broker是單例的,迭代頭任務和尾任務會生成同樣的broker ID,所以頭尾在同一個JVM中會基於相同的dataChannel進行通信。dataChannel由迭代頭創建。

IterationHeadTask中會生成BlockingBackChannel,這是一個容量為1的阻塞隊列。

// 生成channel
BlockingBackChannel backChannel = new BlockingBackChannel(new SerializedUpdateBuffer(segments, segmentSize, this.getIOManager())); 

// 然後block在這裏,等待Tail
superstepResult = backChannel.getReadEndAfterSuperstepEnded();

IterationTailTask則是如下:

// 在基類得到channel,因為是單例,所以會得到同一個
worksetBackChannel = BlockingBackChannelBroker.instance().getAndRemove(brokerKey());

// notify iteration head if responsible for workset update 在這裏通知Head
worksetBackChannel.notifyOfEndOfSuperstep();

而兩者都是利用如下辦法來建立聯繫,在同一個subtask中會使用同一個brokerKey,這樣首尾就聯繫起來了。

public String brokerKey() {
    if (this.brokerKey == null) {
        int iterationId = this.config.getIterationId();
        this.brokerKey = this.getEnvironment().getJobID().toString() + '#' + iterationId + '#' + this.getEnvironment().getTaskInfo().getIndexOfThisSubtask();
    }

    return this.brokerKey;
}
如何把用戶返回的數值傳給Head

這是通過output.collect來完成的。

首先,在Tail初始化時候,會生成一個outputCollector,這個outputCollector會被設置為本task的輸出outputCollector。這樣就保證了用戶函數的輸出都會轉流到outputCollector。

而outputCollector的輸出就是worksetBackChannel的輸出,這裏設置為同一個instance。這樣用戶輸出就輸出到backChannel中。

	@Override
	protected void initialize() throws Exception {
		super.initialize();
    
		// set the last output collector of this task to reflect the iteration tail state update:
		// a) workset update,
		// b) solution set update, or
		// c) merged workset and solution set update

		Collector<OT> outputCollector = null;
		if (isWorksetUpdate) {
      // 生成一個outputCollector
			outputCollector = createWorksetUpdateOutputCollector();

			// we need the WorksetUpdateOutputCollector separately to count the collected elements
			if (isWorksetIteration) {
				worksetUpdateOutputCollector = (WorksetUpdateOutputCollector<OT>) outputCollector;
			}
		}
    
    ......
    // 把outputCollector設置為本task的輸出
		setLastOutputCollector(outputCollector);
	}

outputCollector的輸出就是worksetBackChannel的輸出buffer,這裏設置為同一個instance。

	protected Collector<OT> createWorksetUpdateOutputCollector(Collector<OT> delegate) {
		DataOutputView outputView = worksetBackChannel.getWriteEnd();
		TypeSerializer<OT> serializer = getOutputSerializer();
		return new WorksetUpdateOutputCollector<OT>(outputView, serializer, delegate);
	}

運行時候如下:

	@Override
	public void run() throws Exception {

		SuperstepKickoffLatch nextSuperStepLatch = SuperstepKickoffLatchBroker.instance().get(brokerKey());

		while (this.running && !terminationRequested()) {

      // 用戶在這裏輸出,最後會輸出到output.collect,也就是worksetBackChannel的輸出buffer。
			super.run();

      // 這時候以及輸出到channel完畢,只是通知head進行讀取。
			if (isWorksetUpdate) {
				// notify iteration head if responsible for workset update
				worksetBackChannel.notifyOfEndOfSuperstep();
			} else if (isSolutionSetUpdate) {
				// notify iteration head if responsible for solution set update
				solutionSetUpdateBarrier.notifySolutionSetUpdate();
			}

      ...
	}

IterationTailTask例子如下:

"Pipe (org.apache.flink.runtime.iterative.task.IterationTailTask)"

5.3.4 IterationSynchronizationSinkTask

IterationSynchronizationSinkTask作用是同步所有的iteration heads,IterationSynchronizationSinkTask被是實現成一個 output task。其只是用來協調,不處理任何數據。

在每一次superstep,IterationSynchronizationSinkTask只是等待直到它從每一個head都收到一個WorkerDoneEvent。這表示下一次superstep可以開始了。

這裏需要注意的是 SynchronizationSinkTask 如何等待各個并行度的headTask。比如Flink的并行度是5,那麼SynchronizationSinkTask怎麼做到等待這5個headTask。

在IterationSynchronizationSinkTask中,註冊了SyncEventHandler來等待head的WorkerDoneEvent。

this.eventHandler = new SyncEventHandler(numEventsTillEndOfSuperstep, this.aggregators, this.getEnvironment().getUserClassLoader());
this.headEventReader.registerTaskEventListener(this.eventHandler, WorkerDoneEvent.class);

在SyncEventHandler中,我們可以看到,在構建時候,numberOfEventsUntilEndOfSuperstep就被設置為并行度,每次收到一個WorkerDoneEvent,workerDoneEventCounter就遞增,當等於numberOfEventsUntilEndOfSuperstep,即并行度時候,就說明本次superstep中,所有headtask都成功了。

    private void onWorkerDoneEvent(WorkerDoneEvent workerDoneEvent) {
        if (this.endOfSuperstep) {
            throw new RuntimeException("Encountered WorderDoneEvent when still in End-of-Superstep status.");
        } else {
          // 每次遞增
            ++this.workerDoneEventCounter;
            String[] aggNames = workerDoneEvent.getAggregatorNames();
            Value[] aggregates = workerDoneEvent.getAggregates(this.userCodeClassLoader);
            if (aggNames.length != aggregates.length) {
                throw new RuntimeException("Inconsistent WorkerDoneEvent received!");
            } else {
                for(int i = 0; i < aggNames.length; ++i) {
                    Aggregator<Value> aggregator = (Aggregator)this.aggregators.get(aggNames[i]);
                    aggregator.aggregate(aggregates[i]);
                }

              // numberOfEventsUntilEndOfSuperstep就是并行度,等於并行度時候就說明所有head都成功了。
                if (this.workerDoneEventCounter % this.numberOfEventsUntilEndOfSuperstep == 0) {
                    this.endOfSuperstep = true;
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

IterationSynchronizationSinkTask的例子如下:

"Sync (BulkIteration (Bulk Iteration)) (org.apache.flink.runtime.iterative.task.IterationSynchronizationSinkTask)"

5.4 superstep

綜上所述,我們最終得到superstep如下:

***** 文字描述如下 *****
  
每次迭代都是一個superstep
  每次迭代中有若干subtask在不同的partition上分別執行step
     每個step有一個HeadTask,若干IntermediateTask,一個TailTask
  每個superstep有一個SynchronizationSinkTask
  
***** 偽代碼大致如下 *****
  
for maxIter :
  begin superstep
      for maxSubTask :
         begin step
           IterationHeadTask
           IterationIntermediateTask
           IterationIntermediateTask
           ...
           IterationIntermediateTask
           IterationIntermediateTask
           IterationTailTask
         end step
    IterationSynchronizationSinkTask
  end superstep

0x06 結合KMeans代碼看superset

6.1 K-means算法概要

K-means算法的過程,為了盡量不用數學符號,所以描述的不是很嚴謹,大概就是這個意思,“物以類聚、人以群分”:

  1. 首先輸入k的值,即我們希望將數據集經過聚類得到k個分組。
  2. 從數據集中隨機選擇k個數據點作為初始大哥(質心,Centroid)
  3. 對集合中每一個小弟,計算與每一個大哥的距離(距離的含義後面會講),離哪個大哥距離近,就跟定哪個大哥。
  4. 這時每一個大哥手下都聚集了一票小弟,這時候召開人民代表大會,每一群選出新的大哥(其實是通過算法選出新的質心)。
  5. 如果新大哥和老大哥之間的距離小於某一個設置的閾值(表示重新計算的質心的位置變化不大,趨於穩定,或者說收斂),可以認為我們進行的聚類已經達到期望的結果,算法終止。
  6. 如果新大哥和老大哥距離變化很大,需要迭代3~5步驟。

6.2 KMeansPreallocateCentroid

KMeansPreallocateCentroid也是superstep一員,但是只有context.getStepNo() == 1的時候,才會進入實際業務邏輯,預分配Centroid。當superstep為大於1的時候,本task會執行,但不會進入具體業務代碼。

public class KMeansPreallocateCentroid extends ComputeFunction {
    private static final Logger LOG = LoggerFactory.getLogger(KMeansPreallocateCentroid.class);

    @Override
    public void calc(ComContext context) {
        // 每次superstep都會進到這裏
        LOG.info("  KMeansPreallocateCentroid 我每次都會進的呀   ");
        if (context.getStepNo() == 1) {
          // 實際預分配業務只進入一次
        }
    }
}

6.3 KMeansAssignCluster 和 KMeansUpdateCentroids

KMeansAssignCluster 作用是為每個點(point)計算最近的聚類中心,為每個聚類中心的點坐標的計數和求和。

KMeansUpdateCentroids 作用是基於計算出來的點計數和坐標,計算新的聚類中心。

Alink在整個計算過程中維護一個特殊節點來記住待求中心點當前的結果。

這就是為啥迭代時候需要區分奇數次和偶數次的原因了。奇數次就表示老大哥,偶數次就表示新大哥。每次superstep只會計算一批大哥,留下另外一批大哥做距離比對。

另外要注意的一點是:普通的迭代計算,是通過Tail給Head回傳用戶數據,但是KMeans這裏的實現並沒有採用這個辦法,而是把計算出來的中心點都存在共享變量中,在各個intermediate之間互相交互。

public class KMeansAssignCluster extends ComputeFunction {
    public void calc(ComContext context) {
        ......
        if (context.getStepNo() % 2 == 0) {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID1);
        } else {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID2);
        }
      /** 具體業務邏輯代碼
       * Find the closest cluster for every point and calculate the sums of the points belonging to the same cluster.
       */
    }
}

public class KMeansUpdateCentroids extends ComputeFunction {
    public void calc(ComContext context) {
        if (context.getStepNo() % 2 == 0) {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID2);
        } else {
            stepNumCentroids = context.getObj(KMeansTrainBatchOp.CENTROID1);
        }
      /** 具體業務邏輯代碼
       * Update the centroids based on the sum of points and point number belonging to the same cluster.
       */
    }

6.4 KMeansOutputModel

這裏要特殊說明,因為KMeansOutputModel是最終輸出模型,而KMeans算法的實現是:所有subtask都擁有所有中心點,就是說所有subtask都會有相同的模型,就沒有必要全部輸出,所以這裏限定了第一個subtask才能輸出,其他的都不輸出。

	@Override
	public List <Row> calc(ComContext context) {
    // 只有第一個subtask才輸出模型數據。
		if (context.getTaskId() != 0) {
			return null;
		}

    ....
      
		modelData.params = new KMeansTrainModelData.ParamSummary();
		modelData.params.k = k;
		modelData.params.vectorColName = vectorColName;
		modelData.params.distanceType = distanceType;
		modelData.params.vectorSize = vectorSize;
		modelData.params.latitudeColName = latitudeColName;
		modelData.params.longtitudeColName = longtitudeColName;

		RowCollector collector = new RowCollector();
		new KMeansModelDataConverter().save(modelData, collector);
		return collector.getRows();
	}

0x07 參考

幾種并行計算模型的區別(BSP LogP PRAM)

https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/batch/iterations.html
聚類、K-Means、例子、細節

Flink-Gelly:Iterative Graph Processing

從BSP模型到Apache Hama

Flink DataSet迭代運算

幾種并行計算模型的區別(BSP LogP PRAM)

Flink架構,源碼及debug

Flink 之 Dataflow、Task、subTask、Operator Chains、Slot 介紹

Flink 任務和調度

Flink運行時之生成作業圖

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

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

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

Elasticsearch系列—生產集群部署(上)_網頁設計公司

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

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

概要

本篇開始介紹Elasticsearch生產集群的搭建及相關參數的配置。

ES集群的硬件特性

我們從開始編程就接觸過各種各樣的組件,而每種功能的組件,對硬件要求的特性都不太相同,有的需要很強的CPU計算能力,有的對內存需求量大,有的對網卡要求高等待,下面我們討論一下ES集群對幾種硬件的特性需求。

CPU

ES集群對CPU的要求相對低一些,畢竟純計算的比重要小一些,選用主流的CPU,2核到8核的都可以。

如果有兩種CPU可以挑選,一種是主頻高但核數少的CPU,另一種是主頻一般核數多的CPU,肯定選后一種,因為多核的CPU可以提供更多的併發處理能力,遠比單核高性能帶來的效益要高。

內存

ES集群對內存的要求很高,部署ES集群時,要把大部分資源投入到內存當中。內存分配主要有兩部分,JVM heap內存(堆內存)和OS Cache內存。

JVM heap內存用得不多,主要是OS Cache,我們知道,ES建立的倒排索引,正排索引,過濾器緩存,都是優先放在內存當中的,OS Cache的大小直接決定搜索的性能,如果OS Cache不夠,ES搜索等操作只有被迫讀硬盤,延時就會從毫秒級升到秒級。

OS Cache具體在多大才算夠,取決於數據量,如果是百萬級別的數據,16GB左右應該可以接受,如果是億級,一般單節點都是64GB內存。生產環境最低要求內存應不低於8GB。

硬盤

硬盤成本本身比較便宜,能用SSD就用SSD,訪問速度肯定比机械硬盤快,預估好數據量后就盡可能多規劃一些容量。

另外盡量使用本地存儲,網絡存儲還依賴於網絡傳輸,這個容易造成一些延遲。

網絡

對ES集群這種分佈式系統來說,快速並且可靠的網絡還是比較重要的,shard的分配和rebalance都需要佔用大量的帶寬,集群最好部署在同一個局域網內,異地容災等跨數據中心的部署方案,要考慮到網絡故障帶來的影響。

JVM選擇

使用ES官網推薦的JDK版本,服務端和客戶端盡量使用同一個版本的JDK。

涉及到ES服務端的JVM調優設置,保持原樣不要輕易改動,畢竟ES已經花了大量人力物力驗證過的,隨意調整jvm參數可能適得其反。

容量規劃

規劃集群里,要規劃好投入幾台服務器,數據量上限是多少,業務模型數據讀寫的比例是多少,歷史數據的遷移方案等,一般來說,百萬到10億內的數據量,使用ES集群還是能夠支撐下來的,ES節點數建議不要超過100個。

舉個例子:數據量10億以內,部署5台服務器,8核64GB內存,是能夠支撐的。

生產案例模擬

Linux操作系統搭建

我們使用Linux虛擬機來演示一個生產ES集群的搭建。我們創建4台虛擬機,每台2核CPU,4GB內存,操作系統為CentOS 7 64bit。

虛擬機我用的是VMware workstation,有用virtual box也行,CentOS 7、JDK的安裝不贅述。記得把CentOS的防火牆關了。

修改每台機器的hostname信息,命令
vi /etc/hostname,修改文件,保存即可,建議修改成elasticsearch01,elasticsearch02,elasticsearch03,elasticsearch04。

假定我們4台虛擬機的域名和IP是這樣分配的:

192.168.17.138 elasticsearch01
192.168.17.137 elasticsearch02
192.168.17.132 elasticsearch03
192.168.17.139 elasticsearch04

把這段配置放在 /etc/hosts文件末尾,4台機器做相同的配置。

這4台機器之間,可以配置免密登錄,如在elasticsearch01機器上,我們執行以下操作:

  1. 生成公鑰文件,命令:
ssh-keygen -t rsa

一直輸入回車,不要設置密碼默認會將公鑰放在/root/.ssh目錄下生成id_rsa.pub和id_rsa兩個文件

  1. 拷貝公鑰文件
cp id_rsa.pub authorized_keys
  1. 將公鑰文件拷貝到另外三台機器
ssh-copy-id -i elasticsearch02
ssh-copy-id -i elasticsearch03
ssh-copy-id -i elasticsearch03

拷貝完成后,可以在目標機器上/root/.ssh/目錄下看到多了一個authorized_keys文件。

  1. 嘗試免密登錄,在elasticsearch01機器上輸入ssh elasticsearch02,如果不需要輸入密碼就能登錄到elasticsearch02,說明配置成功,其他機器類似。

這4台機器也可以相互做ssh免密設置。

這裏補充一點免密登錄的方向性問題,上面的案例是在elasticsearch01機器生成的公鑰,並且發送給了elasticsearch02等三台機器,那麼我從elasticsearch01跳到elasticsearch02是不需要密碼的,反過來從elasticsearch02登錄到elasticsearch01,還是需要密碼的。

最後補充幾個常用檢查命令:

  • 檢查NetManager的狀態:systemctl status NetworkManager.service
  • 檢查NetManager管理的網絡接口:nmcli dev status
  • 檢查NetManager管理的網絡連接:nmcli connection show

Elasticsearch服務端

這裏選用的JDK版本為1.8.0_211,Elasticsearch版本為6.3.1,自行安裝不贅述。

ES解壓后的目錄結構:

# 用 "tree -L 1" 命令得到的樹狀結構
.
├── bin
├── config
├── lib
├── LICENSE.txt
├── logs
├── modules
├── NOTICE.txt
├── plugins
└── README.textile
  • bin:存放es的一些可執行腳本,比如用於啟動進程的elasticsearch命令,以及用於安裝插件的elasticsearch-plugin插件
  • config:用於存放es的配置文件,比如elasticsearch.yml
  • logs:用於存放es的日誌文件
  • plugins:用於存放es的插件
  • data:用於存放es的數據文件的默認目錄,就是每個索引的shard的數據文件,一般會另外指定一個目錄。

Elasticsearch參數設置

在config目錄下的文件,包含了ES的基本配置信息:

.
├── elasticsearch.yml
├── jvm.options
├── log4j2.properties
├── role_mapping.yml
├── roles.yml
├── users
└── users_roles

默認參數

Elasticsearch的配置項比較豐富並且默認配置已經非常優秀了,基本上我們需要改動的是跟服務器環境相關的配置,如IP地址,集群名稱,數據存儲位置,日誌存儲位置等外圍參數,涉及到內部機制及JVM參數的,一般不干預,不恰當的JVM參數調整反而會導致集群出現性能故障,如果沒有充足的理由或數據驗證結果,不要輕易嘗試修改。

集群和節點名稱

在elasticsearch.yml文件里這項配置表示集群名稱,配置項默認是註釋掉的,集群名稱默認為elasticsearch。

#cluster.name: my-application

這個配置項強烈建議打開,用項目約定的命名規範進行重命名,並且將研發環境、測試環境、STG准生產環境、生產環境分別命名,如elasticsearch_music_app_dev表示研發環境,elasticsearch_music_app_sit表示測試環境,elasticsearch_music_app_pro表示生產環境等。避免開發測試環境連錯環境,無意中加入集群導致數據問題。

cluster.name: elasticsearch_music_app_pro

節點名稱的配置項

#node.name: node-1

默認也是註釋掉的,ES啟動時會分配一個隨機的名稱,建議還是自行分配一個名稱,這樣容易記住是哪台機器,如

node.name: es_node_001_data

文件路徑

涉及到文件路徑的幾個參數,主要有數據、日誌、插件等,默認這幾個地址都是在Elasticsearch安裝的根目錄下,但Elasticsearch升級時,有些目錄可能會有影響,安全起見,可以單獨設置目錄。

#
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
#path.data: /path/to/data
#
# Path to log files:
#
#path.logs: /path/to/logs
#

例如我們可以在/var目錄下創建相應的文件夾,並且賦予相應的讀寫權限,如:

path.data: /var/es/data
path.logs: /var/es/logs

日誌文件配置

log4j2.properties文件,ES日誌框架選用的是log4j2,也就是log4j的進化版本,對Java技術棧熟悉的童鞋,看到這個配置文件會非常熟悉,默認的日誌輸入配置、格式均能滿足日常的故障定位和分析,也不需要什麼改動。

默認是一天生成一個日期文件,如果ES承載的數據量特別大,可以調整日誌文件產生頻率和每個日誌文件的大小,以及ES最多存儲日誌的大小、數量。

Elasticsearch集群發現機制

配置參數

Zen Discovery是Elasticsearch集群發現機制的默認實現,底層通信依賴transport組件,我們完成Elasticsearch集群的配置主要有下面幾個參數:

  • cluster.name 指定集群的名稱。
  • node.name 節點名稱。
  • network.host 節點綁定的IP。
  • node.master 可選值為true/false,決定該節點類型為master eligible或data node。
  • discovery.zen.ping.unicast.hosts gossip路由服務的IP地址,即集群發現協議通信的公共節點,可以寫多個,有節點啟動時會向裏面的IP發送消息,獲取集群其他節點的信息,最後加入集群。

Elasticsearch集群是點對點(P2P)的分佈式系統架構,數據索引、搜索操作是node之間直接通信的,沒有中心式的master節點,但Elasticsearch集群內的節點也分成master node和data node兩種角色。

正常情況下,Elasticsearch集群只有一個master節點,它負責維護整個集群的狀態信息,集群的元數據信息,有新的node加入或集群內node宕機下線時,重新分配shard,並同步node的狀態信息給所有的node節點,這樣所有的node節點都有一份完整的cluster state信息。

集群發現的一般步驟如下:

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

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

  1. 節點配置network.host綁定內網地址,配置各自的node.name信息,cluster.name設置為相同的值。
  2. discovery.zen.ping.unicast.hosts配置了幾個gossip路由的node。
  3. 所有node都可以發送ping消息到路由node,再從路由node獲取cluster state回來。
  4. 所有node執行master選舉。
  5. 所有node都會跟master進行通信,然後加入master的集群。

master選舉

node.master設置為true的,將成為master eligible node,也叫master候選節點,只有master eligible node才能被選舉成master node。如果是個小集群,那麼所有節點都可以是master eligible node,10個節點以上的集群,可以考慮拆分master node和data node,一般建議master eligible node給3個即可。

master選舉過程是自動完成的,有幾個參數可以影響選舉的過程:

  • discovery.zen.ping_timeout: 選舉超時時間,默認3秒,網絡狀況不好時可以增加超時時間。

  • discovery.zen.join_timeout: 有新的node加入集群時,會發送一個join request到master node,同樣因為網絡原因可以調大,如果一次超時,默認最多重試20次。

  • discovery.zen.master_election.ignore_non_master_pings:如果master node意外宕機了,集群進行重新選舉,如果此值為true,那麼只有master eligible node才有資格被選為master。

  • discovery.zen.minimum_master_nodes: 新選舉master時,要求必須有多少個 master eligible node去連接那個新選舉的master。而且還用於設置一個集群中必須擁有的master eligible node。如果這些要求沒有被滿足,那麼master node就會被停止,然後會重新選舉一個新的master。這個參數必須設置為我們的master eligible node的quorum數量。一般避免說只有兩個master eligible node,因為2的quorum還是2。如果在那個情況下,任何一個master候選節點宕機了,集群就無法正常運作了。

集群故障探查

有兩種集群故障探查機制

  1. master主動對集群中所有的其他node發起ping命令,判斷它們是否是存活着的。
  2. 每個node向master node發送ping請求,判斷master node是否存活,否則就會發起一個選舉過程。

有下面三個參數用來配置集群故障的探查過程:

  • ping_interval:ping一次node的間隔時間,默認是1s
  • ping_timeout:每次ping的timeout等待時長,默認是30s
  • ping_retries:對node的ping請求失敗了,重試次數,默認3次。

集群狀態更新

master node是集群中唯一可以對cluster state進行更新的node。更新的步驟如下:

  1. master node收到更新事件,如shard移動,可能會有多條事件,但master node一次只處理一個集群狀態的更新事件。
  2. master node將事件更新到本地,併發布publish message到集群所有的node上。
  3. node接收publish message后,對這個message返回ack響應,但是不會立即更新。
  4. 如果master沒有在指定的時間內(discovery.zen.commit_timeout配置項,默認是30s),從至少N個節點(discovery.zen.minimum_master_nodes配置項)獲取ack響應,那麼這次cluster state change事件就會被reject,最終不會被提交。
  5. 如果在指定時間內,指定數量的node都返回了ack消息,那麼cluster state就會被commit,然後master node把 commit message發送給所有的node。所有的node接收到那個commit message之後,接着才會將之前接收到的集群狀態應用到自己本地的狀態副本中去。
  6. master會等待所有node的commit message 的ack消息,在一個等待超時時長內,如果接收到了響應,表示狀態更新成功,master node繼續處理內存queue中保存的下一個更新事件。

discovery.zen.publish_timeout默認是30s,這個超時等待時長是從plublish cluster state開始計算的。

我們可以參照此圖:

master node宕機問題

Elasticsearch集群中,master node扮演着非常重要的角色,如果master node宕機了,那豈不是群龍無首了?雖然有master選舉,但這個也是要時間的,沒有master node那段空檔期集群該怎麼辦?

說了一半,基本上是完了,但我們也可以設置,群龍無首時哪些操作可以做,哪些操作不能做。

discovery.zen.no_master_block配置項可以控制在群龍無首時的策略:

  • all: 一旦master宕機,那麼所有的操作都會被拒絕。
  • write:默認的選項,所有寫操作都會被拒絕,但是讀操作是被允許的。

split-brain(腦分裂問題)

在Elasticsearch集群中,master node非常重要,並且只有一個,相當於整個集群的大腦,控制將整個集群狀態的更新,如果Elasticsearch集群節點之間出現區域性的網絡中斷,比如10個節點的Elasticsearch集群,4台node部署在機房A區,6台node部署在機房B區,如果A區與B區的交換機故障,導致兩個區隔離開來了,那麼沒有master node的那個區,會觸發master選舉,如果選舉了新的master,那麼整個集群就會出現兩個master node,這種現象叫做腦分裂。

這樣現象很嚴重,會破壞集群的數據,該如何避免呢?

回到我們前面提到的discovery.zen.minimum_master_nodes參數,這個值的正確設置,可以避免上述的腦分裂問題。

discovery.zen.minimum_master_nodes參數表示至少需要多少個master eligible node,才可以成功地選舉出master,否則不進行選舉。

足夠的master eligible node計算公式:

quorum = master_eligible_nodes / 2 + 1

如上圖我們10個node的集群,如果全部是master eligible node,那麼quorum = 10/2 + 1 = 6。

如果我們有3個master eligible node,7個data node,那麼quorum = 3/2 + 1 = 2。

如果集群只有2個節點,並且全是master eligible node,那麼quorum = 2/2 + 1 = 2,問題就來了,如果隨便一個node宕機,在只剩下一個node情況下,無法滿足quorum的值,master永遠選舉不成功,集群就徹底無法寫入了,所以只能設置成1,後果是只要這兩個node之間網絡斷了,就會發生腦分裂的現象。

所以一個Elasticsearch集群至少得有3個node,全部為master eligible node的話,quorum = 3/2 + 1 = 2。如果我們設置minimum_master_nodes=2,分析一下會不會出現腦分裂的問題。

場景一:A區一個node,為master,B區兩個node,為master eligible node

A區因為只剩下一個node,無法滿足quorum的條件,此時master取消當前的master角色,且無法選舉成功。

B區兩個master eligible node,滿足quorum條件,成功選舉出master。

此時集群還是只有一個master,待網絡故障恢復后,集群數據正常。

場景二:A區一個node,為master eligible node,B區2個node,其中一個是master

A區只有一個master eligible node,不滿足quorum的條件,無法進行選舉。

B區原本的master存在,不需要進行選舉,並且滿quorum的條件,master角色可以保留。

此時集群還是一個master,正常。

綜上所述:3個節點的集群,全部為master eligible node,配置discovery.zen.minimum_master_nodes: 2,就可以避免腦裂問題的產生。

minimum_master_nodes動態修改

因為集群是可以動態增加和下線節點的,quorum的值也會跟着改變。minimum_master_nodes參數值需要通過api隨時修改的,特別是在節點上線和下線的時候,都需要作出對應的修改。而且一旦修改過後,這個配置就會持久化保存下來。

修改api請求如下:

PUT /_cluster/settings
{
    "persistent" : {
        "discovery.zen.minimum_master_nodes" : 2
    }
}

響應報文:

{
  "acknowledged": true,
  "persistent": {
    "discovery": {
      "zen": {
        "minimum_master_nodes": "2"
      }
    }
  },
  "transient": {}
}

也可以通過命令查詢當前的配置:

GET /_cluster/settings

響應結果如下:

{
  "persistent": {
    "discovery": {
      "zen": {
        "minimum_master_nodes": "1"
      }
    }
  },
  "transient": {}
}
留一個問題

上圖10個節點的集群,假設全是master eligible node,按照上述的網絡故障,會不會出現腦分裂現象 ?配置項minimum_master_nodes最低要配置成多少,才不會出現腦分裂的問題?

小結

本篇主要介紹了Elasticsearch集群的部署和參數設置等知識,大部分都不需要人工干預,默認值已經是最優選,集群發現機制和master選舉機制了解一下就OK。

專註Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公眾號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術

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

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

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

我眼中的檳榔_潭子電動車

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

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

初識檳榔已是多年前的事情了,在很小的時候,大人們嚼檳榔,我們就已經耳濡目染了。後來我們長大了參加工作了。第一次吃檳榔還是朋友給的,在一次偶然的飯桌上,酒足飯飽后,朋友遞過來一顆小小的檳榔果,它穿着一層厚厚的芝麻大衣,包裹着細小的果肉,放入口中細細咀嚼,沁人心脾的清爽的感覺,刺激着味蕾,清香的果味混合著芝麻的香醇,回味悠久。

       

從那之後,我跟檳榔的接觸就越來越多了,我也開始了解了一些檳榔的文化,原來檳榔在很久很久以前的時候就已經為人們所知,併為人們所用了。

       

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

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

相傳在很久以前,關於“檳榔”,有個神話傳說。古時候炎帝有個女兒叫賓,她的郎君長的英俊瀟洒,為人善良,平時逞強除惡,而兩人的感情非常的好,心意相通,有一次她的郎君在同妖魔相鬥時不幸被殺死,賓為了紀念她的郎君就將他葬於崑崙石下,化成為一片樹林,賓每天都悉心照顧着這片樹林,後來樹上結出綠油油的果實。

       於是賓將果實裝在茶包里,帶在身旁,以示紀念。後來賓把自己製作的茶包送給人們品嘗,據說人們吃了這種果實后就再不怕妖魔作惡了。於是人們就給這個綠油油的果實以賓加郎命名,於是就取名為檳榔。所以在古代的時候檳榔就已經為人們所食用了,而且還富有很多的價值。

       隨着後來檳榔的廣泛食用,檳榔的功效越來越多的被人們所知道。比如檳榔有很強的藥用價值,它能殺蟲消積,用於腸道寄生蟲,蛔蟲病、蟯蟲病。,而且檳榔是被歷代醫學學者作為治病的葯果,又被稱為“洗瘴丹”,咀食檳榔不僅可以消食下氣、祛痰導滯,而且可以促進腸胃吸收,增強腸道收縮能力,有潤腸通便的效果。

       除此之外,檳榔還被過去住高山地帶的人們常用來禦寒,和消除緊張勞動后的疲勞。所以檳榔還有提神醒腦的作用,當然檳榔的功效還不止是這些,隨便我對檳榔越來越多的認識,越來越覺得這顆小小的檳榔果實渾身上下都是寶。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

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

Apple TV 確認降臨 Chromecast with Google TV 電視棒_網頁設計

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

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

2020 雖然是很O的一年,但同時也是各廠更願意正視居家娛樂與通訊交流的一年。至少除了各種通訊服務與視訊功能以外,今年我們更開始看到 Apple 服務開始在各種平台上廣為散佈。而在蘋果開放讓 Apple Music 正式登陸 Google Nest 智慧喇叭後(雖然台灣還沒看到選項 QQ),現在則是輪到了當今最流行的電視串流棒之一 Chromecast。繼續閱讀 Apple TV 確認降臨 Chromecast with Google TV 電視棒報導內文。

▲圖片來源:Google

Apple TV 確認降臨 Chromecast with Google TV 電視棒

可能是現階段可以用最低成本(最新版 Chromecast 僅美金 49.99 元,也就約台幣 1,500),就能讓電視升級成能觀看 Apple TV 的智慧電視的途徑 — 雖然,還要等到 2021 年初才會更新(反正台灣還不能直接買囉)。

Google 今天在官方部落格宣佈 Apple TV app 將正式支援 Google 電視棒產品的消息。而首個支援的產品則是最新直接提供遙控器的 Chromecast with Google TV — 想知道它好不好用的,可以參考我們的 Chromecast with Google TV 開箱體驗(傳送門)。

雖然確定會不會支援更多之前的 Google TV 串流裝置(希望可以)。不過在 Apple TV app 正式登上 Chromecast 後,訂閱蘋果影視串流服務的使用者,將可以輕鬆以遙控器或是語音的方式瀏覽觀看 Apple TV+ 的各式影片內容。

現階段 Apple TV 登上各平台的速度真的遠超過以往,近期已經有不少電視產品直接內建(最新的應該是 Sony BRAVIA 系列宣布提供此支援);主流家用遊戲主機 PlayStation 更是不僅在最新的 PS5 上架 Apple TV app,甚至也往下支援 PS4 舊款主機。不得不說,這對於愛用 TV+ 的朋友們而言真的是很棒的發展趨勢啊。

本篇圖片 / 引用來源

延伸閱讀:

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

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

Chromecast with Google TV 快速開箱動手玩,讓失智電視也聰明起來

Google Home / Nest 智慧喇叭開始支援 Apple Music

您也許會喜歡:

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

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

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

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

Robot Framework(15)- 擴展關鍵字_潭子電動車

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

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

如果你還想從頭學起Robot Framework,可以看看這個系列的文章哦!

https://www.cnblogs.com/poloyy/category/1770899.html

 

前言

  • 什麼是擴展關鍵字?就是你自己寫的 Python 文件,裡面包含了函數或者類
  • 然後 RF 導入這個 Python 模塊,就可以調用函數或者類方法,它們就是擴展關鍵字

 

Python 模塊作為測試庫

模塊文件名作為測試庫的名字

比如:Python 模塊名叫 MyLibrary,文件名是 MyLibrary.py,那麼測試庫的名字就叫做 MyLibrary

 

Python 模塊和 Robot 文件同目錄下的栗子

這是目錄結構哈

python 模塊的代碼

def returnlist():
    return [i for i in range(10)]


def return_dict():
    return {"a": "hahhahahaahah"}


# 以下劃線開頭的函數不能作為RF關鍵字
def _returnlist2():
    return [1, 2]

robot 代碼

進入test目錄下,運行以下命令

 robot -P . test.robot 

執行結果

知識點

  • _前綴的方法不會作為關鍵字,在Python裏面, _ 開頭的方法是私有方法,RF 不會識別到它
  • Python 方法作為關鍵字也是大小寫不敏感
  • RF 中會把關鍵字的 _ 和單個空格忽略掉,所以 returndict、return dict、return_dict 都是調用同一個關鍵字

 

Python 類作為測試庫的栗子

項目目錄

所有 Python 測試代碼都在 tlib2.py 裏面哦

最終運行是在【15_擴展關鍵字】目錄下運行的,命令如下

robot -P . testrf

 

栗子一:類初始化不需要傳參

python 代碼

class SubLibrary:
    def __init__(self):
        pass

    def returnint(self):
        return 2020

    def _returnint2(self):
        return 4

robot 代碼

測試結果

知識點

  • 在類裏面, _ 前綴的方法不會當做關鍵字
  • 同樣,類中聲明的方法當做關鍵字的話,大小寫不敏感

 

栗子二:類初始化需要傳參

python 代碼

from robot.api import logger
class SubLibrary2: def __init__(self, host, port, table='test'): self.host = host self.port = port self.table = table def printaddr2(self): logger.console('host:%s,port:%s,table:%s' % (self.host, self.port, self.table))

robot 代碼

測試結果

知識點

如果類的 __init__ 初始化方法需要傳參,則在導入庫後面跟對應的參數列表

拓展 Python 知識點:先有類對象,還是先執行類初始化方法?

 __new__ 方法產生對象

 __init__ 對象的初始化方法

先 new 一個對象,再 init 一個對象

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

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

 

栗子三:類名和模塊名相同

python 代碼

from robot.api import logger

class tlib2:
    def __init__(self, host, port):
        self.host = host
        self.port = port

    def printaddr(self):
        logger.console('host:%s,port:%s' % (self.host, self.port))

robot 代碼

測試結果

知識點

如果類名和模塊名相同,可以不用導入類名

 

栗子四:使用路徑法導入 Python 模塊

Python 代碼用的還是栗子三的

robot 代碼

測試結果

知識點

如果用路徑法,需要注意導入 Python 模塊需要有文件後綴哦,且用 / 來表示目錄下

重點:使用路徑法,只能導入和模塊名相同的類名!

 

Python 擴展庫的搜索規則

統一的規則

  • 先根據 robot 文件自身當前目錄下查找庫文件
  • 如果沒有找到則再根據 –pythonpath 和 -P 提供的搜索路徑進行搜索
  • 最後找 Python 安裝的路徑

 

Python 庫引入了其他模塊

背景

當 robot 文件導入的 Python 測試庫引入了其他模塊時,應該怎麼寫導入路徑?

正確寫法

確保導入的模塊路徑和RF導入的模塊起始路徑統一

看栗子

 testother.robot  導入 test.py 模塊, test.py  模塊引入了 login.py 模塊的方法

目錄結構

login.py 代碼

from robot.api import logger


def login_test():
    logger.console('test login')

test.py 代碼

from pylib.login import login_test
# from login import login_test 報錯

def test():
    login_test()

robot 的代碼

在 othertest 目錄下運行下面命令

robot -P . testother.robot

測試結果

結論

  • 可以看到 robot 文件引入的路徑是 pylib 開頭, test 模塊引入 login 模塊的路徑也是 pylib 開頭
  • 如果路徑是 login 開頭導入,那麼運行robot文件將會報錯(如下圖,包含了解析錯誤)

 

Python 庫中的 class 存在繼承

背景

當 robot 文件導入 Python 測試庫的類繼承了另一個類,應該怎麼寫導入路徑?

正確寫法

  • 確保導入的模塊路徑和RF導入的模塊起始路徑統一
  • 使用的時候 RF 文件只需導入子類即可

看栗子

 test.robot 引入了 other.py  模塊下的 Child 類,而 Child 類繼承了 Base.py 模塊下的 Father 類

目錄結構

base.py 的代碼

from robot.libraries.BuiltIn import logger


class Father:
    def __init__(self):
        logger.console('init Father')

    def money(self):
        return '$10000'

other.py 的代碼

from robot.api import logger
from pylib.Base import Father


class Child(Father):
    def __init__(self):
        Father.__init__(self)
        logger.console('init Child')

    def use_money(self):
        return self.money()

    def make_money(self):
        return '$9999'

robot 的代碼

在 testClass 目錄下運行下面命令

robot -P . test.robot

測試結果

 

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

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

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

原型和原型鏈的深入探索_台中搬家公司

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

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

前言

原型和原型鏈這方面的底層原理知識,一直以來都是面試市場上的一塊的肥肉,也是每一位前端開發人員不得不掌握的內功心法。一直以來,我都想要搞懂弄明白的這部分知識,所以,就借這次重學前端將這方面的成果和自己的一些拙見整理一下,分享給大家。現在就從編程思想開始講起吧。

本文篇幅較長,如果只想了解原型和原型鏈的核心知識,建議可以直接從第三部分看起。

一.編程思想

提起編程思想,這個概念在我的腦海中一直是一個非常模糊的概念,我所知道的是作為一名開發人員,每個人都應當具備這種能力,並且要不斷地去探索到底怎麼才能提高編程思想。那什麼是編程思想呢?

我覺得這個問題沒有固定的答案,如果非要給一個定義,那大概就是用計算機來解決人們實際問題的思維方式,即編程思想。目前我所了解的編程思想有面向過程編程,結構化編程以及面向對象編程。

1.面向過程編程

面向過程:POP(Process-oriented programming)就是分析出解決問題所需要的的步驟,然後用函數把這些步驟一步一步實現,使用的時候再一個一個的一次調用就可以了。這裏舉個栗子:將一隻大象裝進冰箱,就可以看做是面向過程的做法。

面向過程:就是按照我們分析好了的步驟,按照這個步驟解決問題。

2.結構化編程

結構化編程(Structured programming):在程序設計的早期,程序採用流程圖和自上而下的方式進行設計。這種設計方法的主要思路是將一個大問題分解為多個小問題進行解決,再針對每個小問題編寫方法。總體上來說,是先構建一個主過程來啟動程序流程,隨後根據程序走向來調用相關的其他過程,這種程序設計思想就是結構化編程。

舉個經典的栗子:需要編寫不同的工資計算方法,社保計算方法以及個人所得稅計算方法。而如果從另一個角度看這個程序,則可以從判斷判斷該程序中的對象入手。該程序中的對象,最明顯的就是“員工”!(小玲至今沒弄懂工資咋算的,所以就隨意畫了一個工資計算的流程圖,大家將就看看吧)

3.面向對象編程

面向對象是把事物分解成一個個對象,然後由對象之間分工合作。

舉個栗子:將大象裝進冰箱,面向對象的做法。(突然有點可憐這隻大象了,老是被裝進冰箱)

先找出對象,並寫出這些對象的功能

(1)大象對象

  • 進去

(2)冰箱對象

  • 打開

  • 關閉

(3)使用大象和冰箱的功能

面向對象是以對象功能來劃分問題,而不是步驟。

4.三大編程思想的對比

面向過程:

優點:性能比面向對象高,適合跟硬件聯繫很緊密的東西,例如單片機就採用的面向過程編程。

缺點:沒有面向對象易維護

面向對象:

優點:易維護,易復用,易擴展,由於面向對象有封裝,繼承,多態性的特性,可以設計出低耦合的系統,使系統更加靈活,更加易於維護。

缺點:性能比面向過程低。

結構化編程:

優點:程序易於閱讀、理解和維護,能將一個複雜的程序分解成若干個子結構,便於控制、降低程序的複雜性;提高了編程工作的效率,降低了軟件開發成本

缺點:

  • 用戶要求難以在系統分析階段準確定義,致使系統在交付使用時產生許多回問題。

  • 用系統開發每個階段的成果來進行控制,不能適應事物變化的要求。

  • 系統的開發周期長。

某大佬總結的:用面向過程的方式寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。(覺得有點意思,拿來用用)

5.深入面向對象

面向對象更貼近我們的實際生活,可以使用面向對象的思想來描述世界事物,但是事物分為具體的事物和抽象的事物。

面向對象編程:

程序中先用對象結構保存現實中一個事物的屬性和功能,然後再按需使用事物的屬性和功能,這種編程方法,就叫面向對象編程

使用面向對象的原因:

便於大量數據的管理和維護

面向對象的思維特點:

(1)抽取(抽象)對象共用的屬性和行為組織(封裝)成一個類(模板)

(2)對類進行實例化,獲取類的對象

面向對象編程我們考慮的是有哪些對象;按照面向對象的思維特點,是不斷的創建對象,使用對象,指揮對象做事情。

面向對象的三大特點:

封裝、繼承和多態

想要對編程思想有進一步了解的話,可以看看一位前輩寫的一位十年軟件工程師告訴你什麼是編程思想,相信你會有新的收穫哦。

二.對象

1.什麼是對象?

廣義來講,都說萬物皆對象。對象是一個具體的事物,看的見摸得着的實物,例如:一本書,一支筆,一個人可以是”對象”,一個數據庫、一張網頁等也可以是“對象”。

在JavaScript中,對象是一組無序的相關屬性和方法的集合,所有的實物都是對象,例如字符串,數值,數組,函數等。

許多人都以為“JavaScript 中萬物都是對象”,這是錯誤的。對象是 6 個(或者是 7 個,取 決於你的觀點)基礎類型之一。對象有包括 function 在內的子類型,不同子類型具有不同 的行為,比如內部標籤 [object Array] 表示這是對象的子類型數組。 對象就是鍵 / 值對的集合。可以通過 .propName 或者 [“propName”] 語法來獲取屬性值。

——選自《你不知道的JavaScript(上卷)》

2.組成

對象是由屬性和方法組成的:

  • 屬性:事物的特徵,在對象中用屬性來表示(常用名詞)

  • 方法:事物的行為,在對象中用方法來表示(常用動詞)

3.何時使用對象?

今後只要使用面向對象的編程方式,都要先創建所需的所有對象,作為備用。

4.創建對象的幾種方式

看到前輩們寫的文章中一共提到了有以下五種方式:

  • 工廠模式(對象字面量):用{}創建一個對象

  • 構造函數模式:用new創建

  • 原型對象模式:利用prototype創建

  • 組合使用構造函數模式和原型模式 :構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性(可以看成是重寫原型對象)。

  • 動態原型模式

可以看看javascript中創建對象的幾種方式 或者 JavaScript創建對象的幾種方式,講解的非常詳細。在這裏我就只簡單介紹其中三種

4.1 工廠模式(對象字面量)

今天上班第一天,先把小玲自己的信息記錄一下~

      
1   var person = {
2             uname:"xiaoling",
3             age:18,
4             intr:function(){
5                  console.log('我是小玲')}
6    };
7    console.log(person);
8    person.intr() //調用方法 

 

現在我們來看一下,假如今天老闆開會說,公司要再來三個新同事,讓你記錄一下她們的信息。

然後你按照這種方式一會就完成了

    
 1         var person1 = {
 2             uname:"小張",
 3             age:20,
 4             intr:function(){
 5                  console.log('我是小張1111')}
 6         };
 7         var person2 = {
 8             uname:"小劉",
 9             age:23,
10             intr:function(){
11                  console.log('我是小劉')}
12         };
13         var person3 = {
14             uname:"小蘇",
15             age:24,
16             intr:function(){
17                  console.log('我是小蘇')}
18         };

 

如果按照這種字面量的方式去創建,不會感覺太傻瓜式了嗎?假如老闆說要增加一個特長 Specialty 的信息,你是否對要這些創建好的每一個對象都要進行添加修改呢?另外,請注意 person1 這個對象,因為小玲粗心了一下,不小心記錄錯誤了。導致兩個地方的屬性 name 和 intr 方法中的打印的名字內容是不一致的。那麼,這種問題能否在以後開發工作中避免呢?

對於第二個問題,我們可以用this.屬性名來代替方法中引用的對象屬性即

1 console.log('我是'+this.uname)
2 //或者
3 console.log(`我是${this.uname}`)

 

關於this,每個函數都會自動攜帶(可以想象成天生就有),可以直接使用,指向正在調用函數的對象(誰調用,this就指向誰),所以person.intr(),intr中的this就指向person。

對於第一個問題,我們可以用下面講的構造函數來解決~

4.2 構造函數模式

構造函數

構造函數式是一種特殊的函數,主要用來初始化對象,及為對象成員變量賦初始值,它總是與new一起使用。我們可以把對象中一些公用的屬性和方法抽取出來,然後封裝到這個函數裏面。

new在執行時做的四件事

①在內存中創建一個新的空對象

②讓this指向這個新的對象

③執行構造函數裏面的代碼,給這個新對象添加屬性和方法

④返回這個新對象(所以構造函數裏面不需要return)

    
 1     //2.構造函數模式
 2         //創建構造函數 Person    uname/age/intr 是實例成員
 3       function Person(name,age){
 4           this.uname = name;
 5           this.age = age;
 6  7           this.intr = function(){
 8               console.log(`我是${this.uname}`);
 9           }
10       }
11         //創建實例
12        var p1 = new Person('xiaoling',18),
13            p2 = new Person('小張',20),
14            p3 = new Person('小劉',23);
15 16           console.log(p1); //Person {name: "xiaoling", age: 18, intr: ƒ}
17           console.log(p2); //Person {name: "小張", age: 20, intr: ƒ}
18           console.log(p3); //Person {name: "小劉", age: 23, intr: ƒ}
19         //調用方法
20           p1.intr(); //我是xiaoling
21           p2.intr(); //我是小張
22           p3.intr(); //我是小劉
23            console.log(Person.intr); //undefined
24 25            Person.sex = '女';   //sex是靜態成員
26            console.log(Person.sex); //
27            console.log(p1.sex);  //undefined

 

這裏提幾點:

  • 構造函數中的屬性和方法我們稱為成員,成員可以添加

  • 實例成員就是構造函數內部通過this添加的成員 ,如name,age,intr就是實例成員;實例成員只能通過實例化的對象來訪問,如p1.uname。不能通過構造函數來訪問實例成員,如Person.uname (這樣是不允許的)

  • 靜態成員是在構造函數本身添加的成員,如sex就是靜態成員。靜態成員只能通過構造函數來訪問,不能通過實例對象來訪問(不可以用p1.sex訪問)

這樣,就能愉快地解決4.1的第一個問題啦!

構造函數存在的問題

現在我們來看一下p1,p2,p3在內存中是怎樣的?

從上圖中我們可以看到,創建多個不同的對象時,會同時在內存中開闢多個空間創建多個相同的函數,這些方法都是一樣的。所以就存在浪費內存的問題。我們希望所有的對象使用同一個函數,這樣會比較節省內存,那麼這個問題改如何解決呢?

利用原型對象的方式創建對象可以解決。

4.3 原型對象模式 -prototype

這節內容比較多,就單獨在第三部分講了~

另:其他創建對象的方式請自行查閱資料了解(可以看看javascript中創建對象的幾種方式 或者 JavaScript創建對象的幾種方式),這裏我就不再講了~

三.原型

先來簡單介紹幾個概念

1.構造函數

前面已經提過了,這裏就再簡單概括一下吧。

構造函數式是一種特殊的函數,return會自動返回一個對象。使用時要搭配new使用,並且new會做4件非常重要的事。

2.原型對象

現在想想什麼是原型呢?

一個對象,我們也稱prototype為原型對象,他具有共享方法的作用。

其實在創建每個構造函數時,都會自動附贈一個空對象,名為原型對象(prototype),屬性名為prototype,這個屬性指向函數的原型對象,同時這個屬性是一個對象類型的值。通過 構造函數.prototype 屬性,可獲得這個構造函數對應的一個原型對象。

構造函數通過原型對象分配的函數是所有對象共享的。JavaScript規定,每一個構造函數都有一個prototype屬性,他指向另一個對象。請再次注意這個prototype就是一個對象,這個對象的所有屬性和方法,都會被構造函數所擁有。

借用一張圖來表示構造函數和實例原型之間的關係:

3.對象原型proto

對象都會有一個屬性proto指向構造函數的prototype原型對象,之所以我們的對象可以使用構造函數prototype原型對象的屬性和方法,就是因為對象對象有proto原型的存在,那麼對象的proto是怎麼存在的呢?

我們來看一下這段話:

JavaScript 中的對象有一個特殊的 [[Prototype]] 內置屬性,其實就是對於其他對象的引用。幾乎所有的對象在創建時[[Prototype]] 屬性都會被賦予一個非空的值。 

注意:很快我們就可以看到,對象的 [[Prototype]] 鏈接可以為空,雖然很少見。

                                                    ——選自《你不知道的JavaScript(上卷)》 第五章

這段話的意思其實就是在告訴我們:每個對象都有“proro”屬性,這個屬性會通過指針的方式指向其他對象,這個“其他對象”就是我們說的原型對象。當然除了頂級Object.prototype.proto為null外,幾乎所有的”proto“都會指向一個非空對象。

當我們用構造函數創建對象時,new的第二步自動為新對象添加”_ proto “屬性,將” _ proto_ _”屬性指向當前構造函數的原型對象。

比如: 如果var p1=new Person(“xiaoling”,18)

則new會自動: p1._ proto _=Person.prototype。

然後會有以下 結果:

① 凡是這個構造函數創建出的新對象,都是原型對象的孩子(子對象)

②放在原型對象中的屬性值或方法,所有子對象無需重複創建,就可直接使用。

看到這裏,是否有點懵圈呢?別急,我們來畫個圖理一理

如上圖所示:構造函數的prorotype屬性 和proto對象原型指向的是同一個對象。

來證明一下吧在控制台打印一下這段代碼:

1 console.log(Person.prototype === p1.__proto__);//true
2 console.log(Person.prototype === p2.__proto__);//true
3 console.log(Person.prototype === p3.__proto__);//true

 

我們發現結果都是true,那麼說明proto對象原型和原型對象prototype是等價的。或者說他們就是同一個對象,即:

構造函數.prototype === 對應實例對象.proto

4.原型對象模式

前面介紹了很多概念,現在來介紹一下原型對象prorotype集體是怎麼實現的,其實很簡單。

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

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

構造函數.prototype.方法 = function(){ … }

具體看代碼:

 1       //原型對象方式
 2          function Person(name,age){
 3           this.uname = name;
 4           this.age = age;
 5       }
 6       Person.prototype.intr = function(){
 7               console.log(`我是${this.uname}`);
 8           }
 9         //創建實例
10        var p1 = new Person('xiaoling',18),
11            p2 = new Person('小張',20),
12            p3 = new Person('小劉',23);
13            console.log(p1); //Person {name: "xiaoling", age: 18}
14           console.log(p2); //Person {name: "小張", age: 20}
15           console.log(p3); //Person {name: "小劉", age: 23}

 

我們來看控制台打印:

我們會發現,打印結果並沒有出現intr方法,那我們直接來調用一下試試吧

//調用intr方法
p1.intr()
p2.intr()
p3.intr()

 

控制台結果:

我們發現,居然可以直接調用,這是怎麼回事呢?我們現在把p1,p2,p3在控制台打印的結果展開

我們可以看到每個實例對象都有一個對象原型proto,將它展開就能看到intr方法,難道實例對象調用成功的intr()是proto上的嗎?。我們知道實例對象的proto就是構造函數的prototype,然而我們的intr方法就是定義在Person.prototype上的,所以,實例對象p1,p2,p3能夠成功調用intr方法。

我們來看一下,他們在內存中是怎麼樣的(簡單畫了一下)?

我們可以看到,p1,p2,p3的__proto__通過指針指向原型對象。我們知道原型對象是一個對象,所以intr在原型對象中存儲的只是一個地址,這個地址會通過指針指向intr方法存儲的位置。所以p1,p2,p3調用的intr方法,其實就是原型對象上的intr,他們共享同一個方法,這也正是前面提到的原型對象的作用:共享方法。

這樣4.2提到的浪費內存的問題就完美的解決啦。雖然創建了三個實例對象,但是他們用的都是同一個方法(只佔用了一份內存),就算咱們再創建3000,30000個對象,他們用的還是同一個方法,佔用的內存依然只有一份,內存資源得到了大大的改善和節省,perfect!

那我們再來思考一個問題:p1,p2,p3,自己沒有uname,age,intr這些方法和屬性,為什麼可以使用呢?這就涉及到我們後面會講的繼承以及JavaScript的查找機制規則了.我們後面會說。

5.三者關係

在上文中,我們總是提到構造函數,原型(原型對象),實例,那麼這三者之間到底有什麼樣的關係呢?

目前我們已經知道的關係是:

  • 構造函數.prototype 指向的是原型對象

  • 實例對象的proto指向的也是原型對象

我們再來看一下這三個實例對象的打印結果,上圖

有看到什麼額外的東西嗎?請再仔細看一下!

我們可以看到每個實例對象展開,他裏面都有一個proto,這個不是重點,重點是,每個proto屬性展開,他不僅僅有我們自己定義的方法intr,還有一個淺粉色的constructor屬性,重點是我們可以看到這個這個constructor的值正好是我們的構造函數Person.

咱們來驗證一下,在控制台輸出以下代碼:

1 console.log(Person.prototype.constructor === Person); //true
2 console.log(p1.__proto__.constructor === Person);//true
3 console.log(p2.__proto__.constructor === Person);//true console.log(p3.__proto__.constructor === Person);//true

 

我們得到的結果是4個true,說明我們上文的猜想是正確的,也就是說構造函數也有一個屬性constructor,這個constructor屬性指向的是構造函數。既然構造函數能夠出生的時候就有prototype屬性–創建每個構造函數時,都會自動附贈一個空對象,名為原型對象(prototype),那我們是不是也可以把原型對象上憑空多出來的constructor當成是它天生就有的呢(哎!有些人註定了,出生就是不平凡的,自帶光環)

好了,我們現在再來總結一下:

  • 創建構造函數時,構造函數會附贈一個prototype屬性,這個prototype屬性以指針的方式指向一個對象,這個對象我們稱為原型對象。

  • 原型對象存在的同時,也會自動附贈一個constructor屬性,這個屬性以指針的方式指向他的構造函數。

  • 用構造函數實例化對象時,會通過new這個動作做4件事。

    ①在內存中創建一個新的空對象

    ②讓this指向這個新的對象(自動設置新對象的_ proto _指向構造函數的原型對象——繼承)

    ③執行構造函數裏面的代碼,給這個新對象添加屬性和方法

    ④返回這個新對象(所以構造函數裏面不需要return)

  • 實例對象創建時會自帶一個內置屬性proto,這個proto性會通過指針的方式指向原型對象(我們可以稱為父類)

    再來畫一個圖看看他們之間的關係

再來看一張更詳細的圖

看完這張圖,你是不是又懵圈了呢?沒關係,請跟我一樣拿起筆自己也在紙上畫一畫吧,如果你能畫明白,說明你已經動了一大半了。如果還沒有明白,我們一起再來捋一捋:

  1. 構造函數Person生成的時候會自動存在一個prototype屬性,即Person.prototype,我們稱為原型對象。

  2. 原型對象是一個對象,它存在的同時會自動生成一個constrctor屬性,這個屬性會自動指向他的構造函數,即Person。

  3. 用new生成實例對象時,這個實力對象同時會攜帶一個proto屬性,這個屬性會指向構造函數的原型對象,通過這個proto,實例對象繼承了原型對象上的所有方法,就是可以用原型對象上的方法。

其實不僅僅是實例對象,任何一個對象數據類型都存在這樣的關係。

再來看一張圖,或許你會更加明白

(本圖來自《JavaScript 高級程序設計》的圖 6-1)

好了,構造函數,原型對象,實例對象三者之間的關係就講完了,如果還沒有弄明白,建議多看幾遍,看的同時自己動手在紙上畫一畫哦。

四.JavaScript的成員查找機制

先直接講一下規則:

①當訪問一個對象的屬性(包括方法)時,首先查找這個對象有沒有該屬性。

②如果沒有就查找他的原型(也就是proto指向的prototype原型對象)。

③如果還沒有就查找原型對象的原型(Object的原型對象)。

④以此類推一直到Object為止(null).

proto對象原型的意義就在於為對象成員查找機制提供了一個方向,或者說一條路線。

這一部分看不明白沒關係,先大概知道這個規則就行。

五.原型鏈

在JavaScript中萬物都是對象,對象和對象之間也有關係,並不是孤立存在的。對象之間的繼承關係,在JavaScript中是通過prototype對象指向父類對象,直到指向Object對象為止,這樣就形成了一個原型指向的鏈條,專業術語稱之為原型鏈

舉例說明:person → Person → Object ,普通人繼承人類,人類繼承對象類

當我們訪問對象的一個屬性或方法時,它會先在對象自身中尋找,如果有則直接使用,如果沒有則會去原型對象中尋找,如果找到則直接使用。如果沒有則去原型的原型中尋找,直到找到Object對象的原型,Object對象的原型沒有原型,如果在Object原型中依然沒有找到,則返回null。這也是實例對象p1,p2,p3能夠使用intr()方法的最重要的

上文說到“如果在Object原型中依然沒有找到,則返回null”,這個一起來驗證一下。我們先來看一下在瀏覽器執行的這些代碼:

我們知道任何一個原型對象都是一個對象,而每個對象都有proto屬性,所以Object的原型對象也是一個對象(可以看Object.prototype的打印結果就是一個對象),那麼他的proto就是上圖我們打印的結果null.所以我們得到了驗證結果

即:Object.prototype.proto = null

所以在JavaScript中,Object是一等公民!

現在我們把原型鏈畫出來!

現在再看着這張圖,重新讀一遍第四和第五部分,你就會明白原型鏈了。

六.構造器

1.定義

本文沒有構造器的定義(沒有找到對它的準確定義),真的要說它是什麼只能說:構造函數。

構造函數跟普通函數非常相似,我們已經說過構造函數時一種特殊的函數(第四部分4.2中),我可以通過new關鍵字來使用它們。主要有兩種類型的構造函數,native構造函數(Array,Object等)它們可以在執行環境中自動生成,還有自定義的構造函數,你可以定義自己的方法和屬性,比如我們自定義的Person構造函數。

2.native構造函數

JavaScript中有內置(build-in)構造器/對象共計12個(ES5中新加了JSON):

Object、Function、String、Number、Boolean、Array、RegExp、Data、Error、Math、JSON、Global

3.自定義構造函數

就是我們可以根據需要按照構造函數的方式定義自己的方法和屬性就行。

4.一個重要結論

所有構造器/函數的proto都指向Function.prototype(Function.prototype是一個空函數)

怎麼驗證這句話呢?上代碼:

 1         console.log(Boolean.__proto__ === Function.prototype); //true
 2         console.log(Number.__proto__ === Function.prototype); // true
 3         console.log(String.__proto__ === Function.prototype);  // true
 4         console.log( Object.__proto__ === Function.prototype); // true
 5         console.log(Function.__proto__ === Function.prototype);  // true
 6         console.log(Array.__proto__ === Function.prototype); // true
 7         console.log(RegExp.__proto__ === Function.prototype); // true
 8         console.log(Error.__proto__ === Function.prototype);  // true
 9         console.log(Date.__proto__ === Function.prototype);// true
10 11 12         console.log(Function.prototype);

 

結果:

再來個自定義構造器:

 1          //自定義構造器
 2          //原型對象方式
 3          function Person(name,age){
 4           this.uname = name;
 5           this.age = age;
 6         }
 7         Person.prototype.intr = function(){
 8               console.log(`我是${this.uname}`);
 9           }
10         
11         console.log(Person.__proto__ === Function.prototype);  //true

 

這說明什麼呢?

①JavaScript中的內置構造器/對象共計12個(ES5中新加了JSON).上面列舉了可訪問的9個構造器。剩下如Global不能直接訪問,Arguments僅在函數調用時由JS引擎創建,Math,JSON是以對象形式存在的,無需new。它們的proto是Object.prototype。如下

Math.__proto__ === Object.prototype // true
JSON.__proto__ === Object.prototype // true

②所有的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身。所有構造器都繼承了Function.prototype的屬性及方法。如length、call、apply、bind(ES5)。

③Function.prototype也是唯一一個typeof XXX.prototype為 “function”的prototype。其它的構造器的prototype都是一個對象。

    
 1 console.log(typeof Function.prototype) // function
 2 console.log(typeof Object.prototype)   // object
 3 console.log(typeof Number.prototype)   // object
 4 console.log(typeof Boolean.prototype)  // object
 5 console.log(typeof String.prototype)   // object
 6 console.log(typeof Array.prototype)    // object
 7 console.log(typeof RegExp.prototype)   // object
 8 console.log(typeof Error.prototype)    // object
 9 console.log(typeof Date.prototype)     // object
10 console.log(typeof Object.prototype)   // object

 

④除了Function.prototype,所有構造函數的prototype都是一個對象

5.一等公民

前面原型鏈部分我們知道Objec是一等公民,那其實Function也是。

我們已經知道了所有構造器(含內置及自定義)的proto都是Function.prototype,那Function.prototype的proto是誰呢?

console.log(Function.prototype.__proto__ === Object.prototype) // true

這說明所有的構造器也都是一個普通JS對象,可以給構造器添加/刪除屬性等。同時它也繼承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

最後再提一次:Object.prototype的proto是誰?

前面我們已經驗證過是null,到頂了

Object.prototype.__proto__ === null // true

講到這裏,是不是又有點懵圈了呢?上圖幫助你消化吧

看圖,一起理一理:

  • 每個構造函數(不管是內置構造函數還是自定義的構造函數),他的proto都指向Function.Prototype,包括Function他自己(Math和JSON不算在裏面,因為他們的proto指向的是Object.prototype)。

  • 每個構造函數都有一個內置屬性即prototype,他們指向自己的原型對象,除了Function的原型對象是一個空函數外,所有構造函數的prototype都是一個對象。

  • 每個原型對象都有一個內置屬性constructor,屬性,constructor屬性指回原型對象的屬性

  • Object是頂級對象,Object.prototype.proto為null

  • 每個實例對象都有一個proto屬性,通過這個proto屬性,這個實例對象可以按照JavaScript的成員查找規則(本文第四部分),去使用原型鏈上的屬性和方法,也就是我們說的繼承父類的屬性和方法的本質。這也是我們隨便創建一個數組或者對象,能夠使用toString()/valueOf() pop()/filter()/sort()等方法的原因。

現在再看這張圖明白了嘛,回國頭再去看一看第四部分:JavaScript的成員查找規則 是不是也頓悟了很多。

七.一張手繪原型鏈

請忽略我醜陋的字跡,能看懂這張圖並且自己可以畫出來,說明你今天的收穫非常大哦~

 

到這裏我們就講完原型和原型鏈相關的內容了,可能還有些地方沒講到,大家就自行下去再研究了。

另外建議紅寶書和你不知道系列多看幾遍(「書讀百遍其義自見」是很有道理的)。

再推薦幾位前輩寫的不錯的文章,值得多讀幾遍:

[
​最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」]  https://www.jianshu.com/p/dee9f8b14771  [
​javascript中構造器(函數)的__proto__與prototype初探]  https://www.cnblogs.com/webjoker/p/5319377.html 

 

後記

非常感謝大家能夠認真看完這篇文章。其實在提筆寫這篇文章之前,小玲的內心是非常緊張和忐忑的,因為害怕自己研究的不夠深入和全面,但是一想到可以和大家分享我的收穫,還是非常激動和興奮的。可能有些地方講的還不夠清楚,大家可以自己多思考一下,你自己思考想出來的東西,比別人灌輸給你的更能讓你記憶深刻。如果,若有某個地方存在問題或者不明白的,歡迎大家积極提出寶貴的建議和見解!

 

參考文章:

https://www.cnblogs.com/TRY0929/p/11870385.html

https://www.jianshu.com/p/dee9f8b14771

https://www.cnblogs.com/snandy/archive/2012/09/01/2664134.html

https://www.cnblogs.com/webjoker/p/5319377.html

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

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

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

虛擬機安裝中標麒麟桌面版7.0系統 + 升級Firefox瀏覽器_網頁設計公司

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

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

背景

由於公司業務(政府項目)需要走國產化路線,需要把原來已有的產品在國產的系統進行測試。目前選擇的是中標麒麟系統,這是一款國產系統,界面 UI 和 window 類似,系統內核使用的是 Linux 的,說白了就是 window + linux 的結合體。

在虛擬機中安裝中標麒麟系統

準備工作

  • 虛擬機軟件:VMware12

  • 系統鏡像:中標麒麟系統7.0

  • Firefox瀏覽器:Linux版本Firefox52

  • 軟件下載導向:VMware12 提取碼:5ijj、 中標麒麟桌面版7.0 提取碼:e6fp 、Linux版本Firefox52

虛擬機安裝

這裏虛擬機安裝沒什麼好講的了,拿到安裝包,一直下一步就可以了,上面已經提供了虛擬機的下載鏈接。

安裝教程可以參考:https://blog.csdn.net/lx940112/article/details/80159509

中標麒麟系統安裝

先從上面提供的鏈接下載對應的鏡像文件,然後在本機把虛擬機軟件安裝好。

  • 打開虛擬機軟件,點擊【創建新的虛擬機】
  • 進入新建嚮導,選擇對應的選項,一直下一步即可,如下圖:

  • 進入虛擬機后安裝麒麟系統,如下圖:

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

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

  • 軟件安裝完成之後,退出然後重啟系統,進入用戶時間的設置,如下圖:

到此,在虛擬機中安裝中標麒麟系統就完成了。

系統自帶Firefox-45升級到Firefox-52

Linux桌面版系統自帶的瀏覽器一般都是Firefox 因為業務的需求,要用Firefox52以上的版本,目前系統自帶的版本不滿足需求,需要升級。

  • 1、下載好最新版本火狐瀏覽器安裝包,上面有下載鏈接,上傳到麒麟系統,路徑自己選。

  • 2.在目錄解壓 Firefox-52.0.tar.bz2。

tar -xjvf  Firefox-52.0.tar.bz2

解壓後會生成一個 firefox 的文件夾,裏面有最新版本的 firefox 的二進制可執行文件,以及各種擴展模塊,插件等等。

  • 3.刪除系統默認自帶的舊版 firefox ,在 /usr/lib64 目錄下。
rm -rf /usr/lib64/firefox
  • 4.將你下載的新版解壓后的 firefox 文件夾複製到 /usr/lib64 目錄下。
mv /usr/firefox /usr/lib64
  • 5.刪除或備份移除原始 /usr/bin 目錄下的 Firefox 文件(這裏進行備份)

    mv /usr/bin/firefox /usr/bin/firefox.bak
    
  • 6.將安裝的新Firefox快捷方式放到 /usr/bin

ln -s /usr/firefox/firefox /usr/bin
  • 7.點擊原來的Firefox圖標打開瀏覽器

升級前:

升級后:

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

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

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

HAKOmini 零負重電視盒:不只是輕巧,更能播放高畫質 4K HDR Netflix影音!_潭子電動車

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

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

這次為大家介紹的是超輕巧的 HAKOmini 零負重電視盒 開箱,相信這年頭很多人家裡早就沒有看第四台了吧?很多人可能是申辦電信業者的機上盒充當電視使用,機上盒雖然頻道也不少、價格也比有線電視便宜,但如果不常看的話,其實也是浪費。雖然現代人很多都用手機、平板透過各種串留影音軟體追劇或看影片,但怎樣手機、平板的螢幕都很小,長時間觀看其實很累且傷眼,如果不小心還會雜到臉上,如果在家想躺在沙發或床上無腦追劇、看電影難道沒有更好的選擇嗎?阿達個人是蠻推薦HAKOmini 零負重電視盒,功能多且方便,最重要的是輕巧又超便宜!

HAKOmini 零負重電視盒開箱

我們也拍攝了 HAKOmini 零負重電視盒 的開箱介紹,主要的重點都在影片中(請點我)更多科技新知酷品開箱請訂閱電腦王阿達頻道並開啟小鈴鐺就不會錯過最新資訊:

HAKOmini 零負重電視盒這邊買(請點我)

(*小提醒:募資非網購,下訂前請詳閱相關訊息)

全部的配件如下,有本體、說明書、遙控器、轉接線與旅充等等:

隨產品給了一條接電視用的Micro hdmi 轉HDMI轉接線:

還有一條 USB介面的轉接線,很多人以為 HAKO mini 這麼小巧,恐怕沒辦法外接隨身碟讀取我們自己儲存的照片影片吧?不,廠商特製的轉接線保留了一個 USB B 的介面,可以外接隨身碟等裝置,讀取內部的照片、影片與音樂:

HAKOmini 真的很小一個,長跟寬僅有6.6公分、厚度也只有1.4公分重量僅有37公克,比女生隨身攜帶的粉餅盒還要小:

如果大家沒概念的話,這樣就比較清楚,就是這麼小巧:

甚至面積也比信用卡還小,重量也很輕巧,是可以隨身攜帶完全無感的體積與重量:

側面前緣這邊配置了HDMI連接埠、重置鍵與 LED 狀態燈號:

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

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

另一頭則只有 Micro USB 介面的連接埠。另外 HAKO mini 也支援 2.4/5GHz Wi-Fi 連線與藍牙4.0:

多功能遙控器設計很不錯,除了基本的方向鍵語音量鍵控制以外,還有語音助理功能,下方還有 YouTube、Netflix、KAKO精靈與AMAZON Prime Video的快捷鍵:

 

HAKOmini 零負重電視盒實測

接下來我們將 HAKO mini 接到電視上,真的很簡單,只要將 HDMI 線接好,另外一頭的電源線接到現在電視上都有的任何一個 USB 連接埠即可,HAKO mini 不需要變壓器,只要一般 5V供電的USB接頭都可以使用,而且體積小巧直接隱身在電視後面,完全無感:

接下來我們看一下 HAKO mini 的介面,內建標準的Android TV 9.0系統,預計明年更新到 10.0系統,可下載正版的 Netflix 欣賞最高 4K HDR 畫質的 YouTube 或 Netflix影音,只要透過遙控器快捷鍵就能快速開啟應用,很多電視盒因為沒有拿到正版 Android TV 授權,所以 Netflix 必須透過第三方市集下載或安裝APK才能看,這種電視盒雖然可看,但畫質最高只有1080P,而且還有可能因為官方改版而不能使用,比較麻煩一些。HAKO mini 也內建了 Google Play 市集,可下載愛奇藝、KKBOX影音、Friday、動漫瘋、HBO GO…各種影音串流平台觀看,現在購買再加送 LiTV 90 天全餐服務體驗,可收看超過400多個傳統第四台頻道看到飽!

另外 HAKO mini 也支援 ChromeCast 功能,可以跟智慧型手機連動投影,iOS 裝置只要安裝極速投屏這類應用就可以使用 AirPlay功能。

除了以上功能以外 HAKO mini 還支援 Google語音助理,想看什麼影片,動口說就搞定,還可以詢問天氣與各種資訊,而且獨家的 HAKO 精靈除了可以找影片以外,還可以連動 iHouse 智慧家電,或直接切換使用情境。

HAKOmini 零負重電視盒這邊買(請點我)

(*小提醒:募資非網購,下訂前請詳閱相關訊息)

結語

看完以上的簡單介紹之後,相信大家對 HAKO mini 零負重電視盒應該也有相當清楚的瞭解了,個人覺得 HAKO mini 體積很小巧,安裝在電視或是一般的螢幕後方也完全不佔體積,也沒有亂七八糟的線路,就算要帶出門使用也很輕鬆方便,而且體積雖然小巧,但具備完整的 Android TV 功能與硬體功能,擴充能力強大,還可播放 4K HDR畫質的 YouTube、Netflix 影音、5GHz的連線能力播放高畫質影音也不卡頓,更別說它方便的 Google 語音助理與 HAKO精靈和智慧家電連動能力,最重要的是價格相當便宜,目前嘖嘖預購價不到1500元,相當划算!推薦給在外租屋的小資族與經常外出旅行想在旅館看電視的朋友使用喔!

您也許會喜歡:

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

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

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

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

PAT 1033 To Fill or Not to Fill (25分) 貪心思想_台中搬家公司

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

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

題目

With highways available, driving a car from Hangzhou to any other city is easy. But since the tank capacity of a car is limited, we have to find gas stations on the way from time to time. Different gas station may give different price. You are asked to carefully design the cheapest route to go.

Input Specification:
Each input file contains one test case. For each case, the first line contains 4 positive numbers: C​max​​ (≤ 100), the maximum capacity of the tank; D (≤30000), the distance between Hangzhou and the destination city; D​avg​​ (≤20), the average distance per unit gas that the car can run; and N (≤ 500), the total number of gas stations. Then N lines follow, each contains a pair of non-negative numbers: P​i​​ , the unit gas price, and D​i​​ (≤D), the distance between this station and Hangzhou, for i=1,⋯,N. All the numbers in a line are separated by a space.

Output Specification:
For each test case, print the cheapest price in a line, accurate up to 2 decimal places. It is assumed that the tank is empty at the beginning. If it is impossible to reach the destination, print The maximum travel distance = X where X is the maximum possible distance the car can run, accurate up to 2 decimal places.

Sample Input 1:
50 1300 12 8
6.00 1250
7.00 600
7.00 150
7.10 0
7.20 200
7.50 400
7.30 1000
6.85 300
Sample Output 1:
749.17
Sample Input 2:
50 1300 12 2
7.10 0
7.00 600
Sample Output 2:
The maximum travel distance = 1200.00

題目解讀

題目大意:汽車從杭州出發可以通過高速公路去任何城市,但是油箱的容量是有限的,路上有很多加油站,每個加油站的價格不同,為汽車設計一個從杭州到終點的最便宜的加油策略。

輸入:第一行:Cmax表示油箱最大容量,D表示杭州到目的地的距離,Davg表示平均每單位的汽油可以讓汽車行駛的距離,N表示途中加油站的數量;接下來 N 行:給出給個加油站的單位油價Pi和杭州(起點)到這個站點的距離Di

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

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

輸出:求汽車從杭州到終點的最少花費(精確到兩位小數)。如果不能夠到達,就輸出汽車能夠行駛的最大距離(精確到兩位小數)。

思路分析

核心思想:貪心算法(每次尋找局部最優,在最便宜的加油站加最多的油)。

前期準備:

  • 最終輸出無論是距離還是價格都要求精確到兩位小數,雖然從給出的輸入數據來看,距離、郵箱容量等好像都是整數,但為了操作方便,避免運算過程精度丟失,我們全都用double保存。(再說了,它給出的數據說不定就是坑你的呢?)
  • 設置結構體數組保存每個加油站的單價和到杭州的距離。
  • 按照到杭州的距離對結構體數組排序,因為輸入是無序的。
  • 排序完判斷第1個結構體到杭州的距離是否為0,也就是說最近的加油站是不是在起點。因為題目說了假定剛開始郵箱沒有油,那麼如果起點處沒有加油站,就比欸想開車了,直接輸出並返回吧。

貪心核心:怎麼實現每次都做出局部最優的選擇?

對於任意一個站點:如果我們在這個站點加滿油,那麼最多就可以跑cmax*davg的距離,我們對這個距離段中遇到的加油站情況進行分析:

  • 按順序遍歷【當前位置,當前位置+cmax*davg】中的所有加油站,如果某個加油站的收費低於當前站點,那麼我就在當前站點加油,跑到那個站點去,加多少呢?就加能恰好讓我到達那個加油站的油。這樣我去那個站點加油就能更便宜。
  • 如果當前站點後面有不止一個站點更便宜,怎麼選擇?比如我現在在A,價格是10,後面是 B 價格9, 後面是C 價格8, 我先找到的是B,那我就退出本次循環,剛好加油跑到B去,在B處重新繼續分析。為啥不直接加油去C,如果從當前位置直接加油去C,那麼BC之間的花費單價是當前加油站的價格也就是10,但我如果先去了B,那麼從B到C的油價就是B處的價格9,顯然更便宜。這樣才滿足局部最優。
  • 如果當前位置後面沒有更便宜的加油站呢?
    • 如果我在當前位置最多能達到的最遠距離超過了終點,那麼我直接加油跑到終點,因為後面的站點只會更貴。
    • 如果我不能直接到終點,那麼我肯定是需要加油的,那我就找盡可能地找比較便宜的那個加油站,在當前加油站加滿油之後過去。既然沒有比當前更低價格的了,就讓油箱加到最大值,這樣能保證利益最大化,保證最大的距離使用的是便宜的油。
  • 如果當前位置不能直接到達終點,並且後面沒有加油站了呢?那麼肯定不能到達中終點了,只能到達當前位置+cmax*davg,也就是說在當前位置加滿,能跑多遠是多遠。

總結:

  • 當前位置能到達的範圍中如果存在更便宜的加油站,就加合適的油剛好到達那個加油站。
  • 如果不存在更便宜的,但是當前位置能直接到達終點,那就加合適的油到達終點。
  • 不存在更便宜的,並且不能直接到終點,找到可達的加油站的相對而言最便宜那個,在當前位置加滿油,然後去那個站點。
  • 當前位置不能到終點,並且後面沒有加油站了,輸出能到達的最大距離。

代碼

#include <iostream>
#include <algorithm>
using namespace std;

struct Station {
    // 每單位汽油價格
    double price;
    // 從起點到這裏的距離
    double dis;
}stat[500];

// 對加油站 離起點 從近到遠 排序
bool cmp(Station a, Station b) {
    return a.dis < b.dis;
}

int main() {
    // cmax油箱容量 d 距離 davg 每單位油能跑多遠 n個加油站
    double cmax, d, davg;
    int n;
    cin >> cmax >> d >> davg >> n;
    // 每個加油站的 價格 位置
    for (int i = 0; i < n; ++i) 
        cin >> stat[i].price >> stat[i].dis;
    // 根據離起點的距離排序
    sort(stat, stat + n, cmp);
    // 第一個加油站不在起點,無法起步
    if(stat[0].dis != 0) {
        printf("The maximum travel distance = 0.00");
        return 0;
    } 
    // nowpos當前在哪個加油站,
    int nowpos = 0;
    // nowgas 當前剩餘多少油,total_price 總花費
    double nowgas = 0.00, total_price = 0.00;
    // 油箱加滿一次油,可以跑多遠
    double each_max_run_dis = cmax * davg;
    // 是否到達終點
    bool arrived = false;
    while (!arrived) {
        // 遍歷 【從當前加油站到最遠能跑到的位置】 之間的 全部加油站
        bool exist_stat  = false; // 當前位置後面是否存在可達加油站
        bool exist_cheaper_stat = false; // 是否存在比當前位置更便宜的加油站
        // 不存在比當前便宜的,就找到其中價格最低的
        int min_pos = -1; double min_price = 9999.99;
        for (int i = nowpos + 1; i < n; ++i) {
            // 當前位置到不了這個加油站,退出循環
            if (stat[i].dis - stat[nowpos].dis > each_max_run_dis) {
                // 最多還能走 nowgas * davg
                break;
            }
            exist_stat = true; // 存在可達加油站
            // 如果有比當前加油站價格更低的加油站
            // 算一下恰好跑到那個加油站需要多少油,
            // 加油跑到那個加油站
            if (stat[i].price < stat[nowpos].price) {
                // 設置標誌
                exist_cheaper_stat = true;
                double needgas = (stat[i].dis - stat[nowpos].dis) / davg - nowgas;
                // 加這麼多油,剛好跑到那個加油站,算一下花費
                total_price += stat[nowpos].price * needgas;
                // 到達那個位置后剩餘油量歸0
                nowgas = 0;
                // 改變位置
                nowpos = i;
                // 不再遍歷後面的加油站
                // 比如我現在 在A,價格是10,後面是 B 價格9, 後面是C 價格8
                // 我先找到的是B,那我就剛好加油跑到B去,在B處重新考慮
                // 為啥不直接加油去C,如果從當前位置直接加油去C,那麼BC之間的花費單價是當前加油站的價格也就是10
                // 但我如果先去了B,那麼從B到C的油價就是B處的價格9,顯然更便宜
                // 這樣才滿足局部最優
                break;
            }
            // 如果說我能從當前位置跑1000米,但是在此之間的加油站的價格沒有一個比我現在的價格低
            // 那我就盡量找最便宜的那個,然後在當前位置加滿油,跑到相對而言最便宜的那個加油站去加油
            // 這樣才滿足局部最優(在最便宜的位置加更多的油跑最多的距離)
            // 這個if不會和上面的if同時執行(上面執行完就break了),所以不用加else
            if (stat[i].price < min_price) {
                min_pos = i;
                min_price = stat[i].price;
            }
        }
        // 不存在比當前便宜的,但是當前位置最遠能達到終點
        if (!exist_cheaper_stat && (d - stat[nowpos].dis <= each_max_run_dis)) {
            double needgas = (d - stat[nowpos].dis) / davg - nowgas;
            // 加這麼多油,剛好跑到終點,算一下花費
            total_price += stat[nowpos].price * needgas;
            // 到達終點
            arrived = true;
            break;
        }
        // 不存在比當前便宜的,但是找到了其他加油站中相對最便宜那個
        if (!exist_cheaper_stat && exist_stat) {
            // 後面有加油站,但是都比當前位置的加油站貴
            // 那我就盡量找最便宜的那個,然後在當前位置加滿油,跑到相對而言最便宜的那個加油站去加油
            // 這樣才滿足局部最優(在最便宜的位置加更多的油跑最多的距離)
            double needgas = cmax - nowgas;
            // 在當前位置加滿油,算一下花費
            total_price += stat[nowpos].price * needgas;
            // 到達那個位置后的剩餘油量
            nowgas = cmax - (stat[min_pos].dis - stat[nowpos].dis) / davg;
            // 改變位置
            nowpos = min_pos;
        // 當前位置無法抵達下一個加油站
        } else if (!exist_stat){
            // 最多還能走 cmax * davg
            printf("The maximum travel distance = %.2f", stat[nowpos].dis + each_max_run_dis);
            return 0;
        }
    }
    // while正常結束,說明到達終點
    printf("%.2f", total_price);
    return 0;
}

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

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

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

離散數學 II(最全面的知識點匯總)_網頁設計公司

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

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

離散數學 II(知識點匯總)

目錄

  • 離散數學 II(知識點匯總)
    • 代數系統
      • 代數系統定義
        • 例子
      • 二元運算定義
    • 運算及其性質
      • 二元運算的性質
        • 封閉性
        • 可交換性
        • 可結合性
        • 可分配性
        • 吸收律
        • 等冪性
        • 消去律
      • 特殊的元素性質
        • 幺元
        • 零元
        • 逆元
          • 證明逆元且唯一定理
      • 二元運算表中性質的體現
    • 半群
      • 廣群
        • 成立條件
      • 半群
        • 定義
        • 特性
        • 子半群
      • 獨異點
        • 成立條件
        • 特性
      • 證明是半群或獨異點
    • 群和子群
        • 定義
        • 階數、有限群、無限群
          • 1階、2階、3階、4階群
        • 特性
          • 冪特性
          • 運算表特性
        • 運算
        • 子群
          • 定義
          • 判定條件
          • 性質
          • 平凡子群
          • 中心
          • 共軛子群
    • 阿貝爾群和循環群
      • 阿貝爾群 / 交換群
        • 定義
        • 判定
      • 循環群
        • 定義
        • 特性
        • 元素的階
          • 定義
          • 性質
        • 子群性質
    • 置換群和伯恩賽德定理
      • 置換
        • 成立條件
        • 運算
      • 置換群
        • 定義
        • 對稱群
        • 交錯群
        • 輪換
          • 定義
          • 記法
          • 對換
            • 定義
            • 性質
        • 誘導的二元關係
          • 定義
          • 性質
        • 三元素集的置換群
          • 對稱群
          • 交錯群
      • 伯恩賽德定理
    • 陪集和拉格朗日定理
      • 陪集
        • 定義
        • 性質
      • 特殊關係
        • 劃分
        • 等價關係
        • 等價類
        • 商集 A/R
      • 子群的指數
      • 拉格朗日定理
        • 推論
    • 正規子群和商群
      • 正規子群 / 不變子群
        • 定義
        • 判別
        • 單群
        • 性質
      • 商群
        • 運算
        • 定義
        • 性質
        • 推論
    • 同態與同構
      • 同態映射 / 同態 ~
        • 定義
        • 同態象
        • 自然同態
        • 分類
          • 同構
            • 凱萊定理
        • 自同態 / 自同構
      • 同態映射性質
      • 同態核
        • 定義
        • 性質
      • 同態基本定理
      • 第一同構定理 / 商群同構定理
    • 環與域
      • 定義
        • 零元
        • 單位元
        • 負元
        • 逆元
      • 例子
      • 性質
      • 特殊環
        • 交換環
        • 含幺環
        • 無零因子環
          • 零因子
      • 整環
        • 定義
      • 子環
        • 定義
        • 判定定理
        • 定義
        • 例子
      • 域與整環的關係
      • 環的同態定義
        • 分類
        • 同態像及其特性
          • 綜合例題

代數系統

代數系統定義

一個非空集合A,連同若干個定義在該集合上的運算f1,f2,…,fk,所組成的系統就稱為一個代數系統,記作<A, f1,f2,…,fk >。

例子

例:<N,+>,<Z,+,·>,<R,+,·>都是代數系統,其中+和·分別表示普通加法和乘法。
例:<Mn(R),+,·>是代數系統,其中+和·分別表示n階(n≥2)實矩陣的加法和乘法。
例:<ρ(S),∪,∩,~ >也是代數系統,其中含有兩個二元運算∪和∩以及一個一元運算 ~。

二元運算定義

S為非空集合,從S×S->S的映射: f: S×S->S稱為集合S上的一個二元運算。

運算及其性質

二元運算的性質

封閉性

  • Premise:\(*\)是定義在集合A上的二元運算, \(\forall\ x,y\in A\)
  • Condition:\(\ x*y\in A\)
  • Summary:\(*\)在A上是封閉的

可交換性

  • Premise:\(*\)是定義在集合A上的二元運算, \(\forall\ x,y\in A\)
  • Condition:\(x*y=y*x\)
  • Summary:\(*\)在A上是可交換的

可結合性

  • Premise:\(*\)是定義在集合A上的二元運算, \(\forall\ x,y,z\in A\)
  • Condition:\((x*y)*z=x*(y*z)\)
  • Summary:\(*\)在A上是可結合的

可分配性

  • Premise:\(*,\triangle\)是定義在集合A上的二元運算, \(\forall\ x,y,z\in A\)
  • Condition:\(x*(y\triangle z)=(x*y)\triangle (x*z)\)\((y\triangle z)*x=(y*x)\triangle (z*x)\)
  • Summary:在A上,\(*\)對於$\triangle $是可分配的

吸收律

  • Premise:\(*,\triangle\)是定義在集合A上的二元運算, \(\forall\ x,y\in A\)
  • Condition:\(x*(x\triangle y)=x\)\(x\triangle (x*y)=x\)
  • Summary:\(*\)和$\triangle $在A上滿足吸收律

等冪性

  • Premise:設\(*\)是定義在集合A上的二元運算, \(\forall\ x\in A\)
  • Condition:\(x*x=x\)
  • Summary:\(*\)在A上是等冪的

消去律

  • Premise:設\(*\)是定義在集合A上的二元運算, \(\forall\ x,y,z \in A\)
  • Condition:(左消去律)\(x*y=x*z\Rightarrow y=z\)、(右消去律)\(y*x=z*x\Rightarrow y=z\)
  • Summary:\(*\)在A上是滿足消去律的

特殊的元素性質

\(*\)是定義在集合A上的二元運算

幺元

  • 左幺元:對於\(e_l\in A,\ \forall\ x\in A,\ e_l*x=x\)
  • 右幺元:對於\(e_r\in A,\ \forall\ x\in A,\ x*e_r=x\)
  • 幺元:對於\(e\in A\)\(e\)既是左幺元又是右幺元

零元

  • 左零元:對於\(\theta_l\in A,\ \forall\ x\in A,\ \theta_l*x=\theta_l\)
  • 右零元:對於\(\theta_r\in A,\ \forall\ x\in A,\ x*\theta_r=\theta_r\)
  • 零元:對於\(\theta\in A\)\(e\)既是左零元又是右零元

逆元

設在代數系統\(<A,*>\)中,\(*\)為二元運算,e為A中關於\(*\)的幺元,\(a,b\in A\)

  • 左逆元\(b*a=e\),則b為a的左逆元
  • 右逆元\(a*b=e\),則b為a的右逆元
  • 逆元:b​既是a的左逆元又是右逆元,則b為a的逆元,記為a^-1^
    • 此時有a與b互為逆元
證明逆元且唯一定理
  • Premise:\(\forall\ a\in A\),e為A的逆元,\(*\)為A的二元運算
  • Condition:a都有左逆元,\(*\)可結合
  • Summary:a的左逆元為a的逆元且唯一

二元運算表中性質的體現

\(*\)是定義在集合A上的二元運算

  • 封閉性\(\Leftrightarrow\)運算表中所有元素\(\in A\)
  • 可交換性\(\Leftrightarrow\)運算表中所有元素沿對角線對稱
  • 等冪性\(\Leftrightarrow\)運算表中主對角線元素等於本身
  • 零元\(\Leftrightarrow\)該元素運算行列元素與其本身相同
  • 幺元\(\Leftrightarrow\)該元素運算行列元素與其對應的行列元素一致
  • 逆元\(\Leftrightarrow\)兩元素行列相交處都是幺元

半群

廣群

成立條件

  • \(*\)運算封閉

半群

定義

  • \(*\)運算封閉
  • \(*\)運算可結合

特性

  • A元素有限,則必有等冪元

證:

∵ <S, *>是半群,∴對於\(\forall\)b \(\in\)S,由運算*封閉可知:
b^2^=b*b\(\in\)S,b^2^ *b=b*b^2^=b^3^\(\in\)S ,b^4^,b^5^… \(\in\)S
∵ S有限,∴必定\(\exists\)i,j,j>i,有b^i^=b^j^(第一輪)
∴ b^i^ =b^j^ =b^j-i^ * b^i^
令p=j-i ,則有 b^i^ =b^p^ * b^i^
∴ 對任意q≥i, 有b^q^= b^p^ *b^q^ (第二輪)
又∵p≥1 ∴$\exists $k,有kp≥i,則有b^kp^=b^p^ *b^kp^ (第三輪)
由b^kp^=b^p^ *b^kp^得: b^kp^=b^p^ *b^kp^=b^p^ *(b^p^ *b^kp^)=…=b^kp^ *b^kp^
∴令a=b^kp^ \(\in\)S 則a*a=a,∴b^kp^是等冪元。

子半群

  • \(B\subseteq A\)
  • \(*\)在B上運算封閉

獨異點

成立條件

  • 為半群
  • 含幺元

特性

  • 運算表任意兩行兩列都不相同

證:

設獨異點中幺元為e,對於任意 a,bS且a≠b,總有
(1)∵a*e=a ≠ b=b*e
由a,b任意性, 有<S, *>運算表中任兩行不同;
(2)∵e*a = a ≠ b = e*b
由a,b任意性,有<S, *>運算表中任兩列不同。

  • 若a,b均有逆元,則
    • \((a^{-1})^{-1}=a\)
    • \(a*b\)有逆元,且\((a*b)^{-1}=b^{-1}*a^{-1}\)

證:

a) ∵a^-1^是a的逆元

​ ∴a^-1^既是a的左逆元又是a的右逆元

​ 即:a^-1^ *a=a *a^-1^=e

​ ∴a既是a^-1^的右逆元又是a^-1^的左逆元,

​ ∴ a是a^-1^的逆元 即(a^-1^)^-1^=a

b) 要證(a *b)^-1^=b^-1^ *a^-1^,即證b^-1^ *a^-1^為a*b的逆元。

∵(a*b) *(b^-1^ *a^-1^)=a* (b*b^-1^) *a^-1^=a*e*a^-1^=e

∴b^-1^ *a^-1^是a*b的右逆元,

又∵(b^-1^ *a^-1^)*(a *b)=b^-1^ *(a^-1^ *a)*b=e

∴b^-1^ *a^-1^是a*b的左逆元,

∴(a*b)^-1^=b^-1^ *a^-1^

證明是半群或獨異點

按定義證明

群和子群

定義

  • 運算封閉
  • 可結合
  • 存在幺元e
  • 對於每一個元素\(x\in G\),存在逆元$x^{-1}

階數、有限群、無限群

如果\(<G,*>\)為群且元素有限,則稱為有限群,元素個數稱為群的階數,否則稱為無限群

1階、2階、3階、4階群

1~4階都有循環群,可以用mod運算推

4階還有克萊因四元群,如下

* e a b c
e e a b c
a a e c b
b b c e a
c c b a e

特性

  • 階大於1的群中不可能有零元

證:

(1)當群的階為1時,它的唯一元素視作幺元e;

(2)設|G|>1且群<G, *>中有零元q,那麼群中

​ ∀x∈G,*都有q*x=x*q=q ≠ e

所以零元q不存在逆元,這與<G, *>是群矛盾。

  • $\forall\ a,b\in G,\ \exists\ \(唯一的\)x,\ a*x=b$

證:

(1)存在性
設群<G, *>的單位元為e,令x=a^-1^ *b, 則
a*x=a*(a^-1^ *b)=(a*a^-1^) *b=e*b=b
所以x=a^-1^ *b是方程a*x=b的解。
(2)唯一性
若還有x′∈G, 使得a*x′=b, 則
x′=e*x′
=(a^-1^ *a)*x′=a^-1^ *(a*x′)=a^-1^ *b=x
故x=a^-1^ *b是方程a*x=b的唯一解。

  • 滿足消去律

證:

a*b=a*c

$\Rightarrow $ a^-1^ *(a*b)=a^-1^ *(a*c)

$\Rightarrow $ (a^-1^ *a) *b=(a^-1^ *a)*c

$\Rightarrow $ e*b=e*c

$\Rightarrow $ b=c

冪特性
  • 除了幺元外,不存在其他等冪元
  • 關於逆元,群中任一元素逆元唯一,且有:
    • \((a^{-1})^{-1}=a\)
    • \((a*b)^{-1}=b^{-1}*a^{-1}\)
    • \((a^{n})^{-1}=(a^{-1})^n=a^{-n}\)

證:

已學定理5-2.4:設代數系統<A, *> , A中存在幺元e,且$\forall $x∈A,都存在左逆元,若*是可結合的運算,那麼<A, *> 中任何一個元素的左逆元必定也是該元素的右逆元,且每個元素的逆元唯一。

證明:

∵群滿足結合律,且群中每個元素都有逆元,

∴每個元素都有左逆元,

∴每個元素的逆元唯一。

運算表特性
  • 每一行與每一列都是G元素的一個置換,沒有相同元素
  • 運算表中任意兩行或者兩列都不相同

運算

AB={ab|a∈A,b∈B}
A^-1^={a^-1^|a∈A}
gA={ga|a∈A}

子群

記為H\(\leq\)G,真子群記為H<G

定義
  • 為一個群的非空子集
  • 也為群
判定條件
  1. 非空\(S\subseteq G\),且S也是群
  2. 非空\(S\subseteq G\),G為有限群,S中運算封閉
  3. 非空\(S\subseteq G\),有\(a*b^{-1}\in S\)
性質

若<H, *>和<K, *>為<G, *>子群,則

  • <H\(\cap\)K, *>也是子群
  • <H\(\cup\)K, *>是子群 當且僅當 H\(\subseteq\)K或K\(\subseteq\)H
  • HK是子群 當且僅當 HK=KH
平凡子群

\(S=\{e\}\quad OR\quad S=G\)

中心

對於\(C=\{y|y*a=a*y,y\in G\}\),則<C, *>為子群,稱為G的中心

共軛子群

若H為G子群,則xHx^-1^={x*h*x^-1^|h ∈H}也是G的子群,稱xHx^-1^是H的共軛子群

阿貝爾群和循環群

阿貝爾群 / 交換群

定義

  • 是群
  • \(*\)可交換

判定

  • 是群,且\(\forall\ a,b\in G,\ (a*b)*(a*b)=(a*a)*(b*b)\)

證:

充分性 即證a*b=b*a。
∵ (a*b)*(a*b)=(a*a)*(b*b) 且<G,*>是群,*可結合
∴ a*(b*a)*b=a*(a*b)*b
∴ a^-1^ *(a*(a*b)*b)*b^-1^=a^-1^ *(a*(b*a)*b)*b^-1^
即有:a*b=b*a, ∴ <G,*>是阿貝爾群。
必要性 ∵ <G,*>是阿貝爾群,
∴對∀a,b∈G,有:a*b=b*a
∴ (a*b)*(a*b)=a*(b*a)*b=a*(a*b)*b=(a*a)*(b*b)

循環群

定義

\(\exists\ a\in G,\ \forall\ b\in G\),b都能表示成a的冪,a稱為生成元

特性

  • 是阿貝爾群
  • 如果是有限群,階數為n,則
    • 幺元為a^n^
    • \(\psi(n)\)個生成元,(歐拉函數,表示小於n且與n互質的正整數個數)
    • G的其他生成元即\(a^k\),k與n互質
  • 若階數無限,則只有兩個生成元e和e^-1^

元素的階

定義

最小正整數k使某一元素\(a^k=e\),則k為a的階(周期)

性質
  • a^k^=e \(\iff\) r | k

    (k是r的整數倍,即存在整數m,使得k=rm )

證:

充分性:r | k \(\Rightarrow\) a^k^=e

設 r | k,則存在整數m,使得k=rm,

​ a^k^= a^rm^=(a^r^)^m^=e^m^=e

必要性:a^k^=e \(\Rightarrow\) r | k

若a^k^=e,由帶余除法,一定存在整數p,q,使得

k=pr+q(0≤q<r),於是a^k^=a^pr+q^=a^pr^ *a^q^=(a^r^)^p^ *a^q^ =(e)^p^ *a^q^ =e*a^q^ =a^q^ =e (a^k^=e)

∵ r是a的階,即使得a^r^=e的最小正整數

∴只有q=0才可能有a^q^ =e, ∴ k=pr 即r | k。

  • O(a)= O(a^-1^)(元素與其逆元的階相同)

證:

O(a)= O(a^-1^)(元素與其逆元的階相同)

證:∀a∈G,a的階為r, a^-1^的階為r’,

則 (a^-1^)^r’^=e ,a^r^=e

∵ (a^r^)^-1^ *a^r^=e 且a^r^=e,
∴ (a^r^)^-1^=e( (a^r^)^-1^與e做運算=e,則(a^r^)^-1^必=e)
由紅色部分可得(a^r^)^-1^=(a^-1^)^r’^=e-----①
∵ <G,*>是群,即(a^n^)^-1^=(a^-1^)^n^成立,則
(a^r^)^-1^=(a^-1^)^r^ 成立-----②
由①②可得,(a^-1^)^r^ =(a^-1^)^r’^=e
∵ 已知r’是a^-1^的階,即r’是使得(a^-1^)^k^ =e的最小正整數,
∴ r=mr’(m為正整數),即r’|r。 (定理中的(1)剛證明過)
同理可證r|r’。
(a^-1^)^r’^= (a^r’^)^-1^=e
∵ (a^r’^)^-1^ * a^r’^=e
∴ a^r’^=e
∵ 已知r是a的階,即r是使得(a)^r^ =e的最小正整數,
∴ r’=mr (m為正整數),即r|r’ .由r’|r與 r|r’即可證得r=r’。

  • r ≤ |G|(元素的階一定小於等於群的階)

證:

一個元素a, a的階是r,且r>|G|,則由a可生成一個集合S={a,a^2^,a^3^,…,a^r-1^,a^r^},因為運算*封閉,所以S⊆G, 則S的元素個數小於|G|.
然後證明a,a^2^,a^3^,…,a^r-1^,a^r^各不相同。
若不然,假設S中存在兩個元素相同:
a^i^=a^j^,其中1≤i<j≤r,就有e=a^j-i^ (1≤ j-i<r,a^i^=a^j^右側同*a-i),而已知r是使得a^r^=e的最小整數。
a,a^2^,a^3^,…,a^r-1^,a^r^都各不相同,即集合S的元素個數大於|G|,與S⊆G矛盾。綜上,r≤|G|

子群性質

  • 循環群的子群也是循環群
  • 循環群是無限階的,則其子群除了{e}也是無限階的
  • 循環群是n階的,對於每個n的因子,有且只有一個循環子群

置換群和伯恩賽德定理

置換

成立條件

  • 對於非空集合S,\(S\rightarrow S\)的雙射稱為S的置換

運算

先運用\(\pi_2\),再運用\(\pi_1\)

  • 左複合 $\circ \(:\)\pi_1\circ\pi_2$
  • 右複合 $\diamond \(:\)\pi_2\diamond\pi_1$

置換群

定義

  • 具有n個元素的集合S中所有的置換組成的群\(<S_n,\circ>\),其中元素個數有 n! 個
  • 任意\(<S_n,\circ>\)的子群都是S上的置換群

對稱群

\(S_n\)稱為S的對稱群

交錯群

\(S_n\)中所有偶置換組成的群,記為\(A_n\)\(|A_n|=n!/2\)

輪換

定義

設s是S={1,2,…,n}上的n元置換,且:

\[s(i_1)=i_2, s(i_2)=i_3, …, s(i_k-1)=i_k, s(i_k)=i_1 \]

\(\forall\ x\in S,\ x\ne i_j (j=1,2,…,k)\),有 s(x)=x(即s 不改變其餘元素),稱s是S上的一個k輪換, 當k=2, s也稱為對換

記法

\((i_1,i_2,…,i_k)\)

對換
定義

k=2時

性質
  • 任意輪換可以寫成對換的乘積。即

    (a1 a2…ar)=(a1 ar)(a1 ar-1)…(a1 a3)(a1 a2)

誘導的二元關係

定義

\(<G,\circ>\)為S的一個置換群,則其誘導的二元關係有

\[R=\{<a,b>|\pi(a)=b,\ \pi\in G\} \]

性質
  • 是一個等價關係(條件:自反性、對稱性、傳遞性)

三元素集的置換群

對稱群

S~3~={ (1), (1 2), (1 3), (2 3), (1 2 3), (1 3 2) }

交錯群

A~3~={ (1), (1 2 3), (1 3 2) }

伯恩賽德定理

\(\pi\)是劃分S的置換群的一個置換,\(\phi(\pi)\)指置換中不變元個數

\[等價類數目=\frac{1}{|G|}\sum_{\pi\in G}\phi(\pi) \]

陪集和拉格朗日定理

陪集

定義

設H是G的子群,\(a\in G\),則

  • aH={a*h|h∈H} H關於a的左陪集
  • Ha={h*a|h∈H} H關於a的右陪集

a稱為陪集的代表元素

性質

元素\(\Rightarrow\)陪集

  • 陪集元素個數相等,\(\forall a\in G\),|aH|=|H|

  • a∈H$\iff $aH=H,Ha=H

  • a∈aH

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

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

  • b∈aH $\iff $ bH=aH

陪集與陪集

  • aH和bH關係只有兩種
    • aH∩bH=\(\varnothing\)(Ha∩Hb=\(\varnothing\)
    • aH=bH(Ha=Hb)

陪集\(\Rightarrow\)元素,a/b屬於同一陪集

  • aRb \(\iff\) a^-1^ *b∈H \(\iff\) b∈aH \(\iff\) aH=bH

所有左陪集的集合∑剛好是G的一個劃分

特殊關係

劃分

  • 每個元素非空。不存在空塊
  • 所有元素並集為G
  • 任兩個元素交集為空

等價關係

關係R滿足自反、對稱、傳遞

  • 若<x,y>\(\in\)R,稱x等價於y,記作x~y

等價類

有等價關係的元素組成的一個集合,記為[a]~R~

  • a稱為[a]~R~的代表元素

商集 A/R

以R的所有等價類作為元素的集合稱為A關於R的商集

子群的指數

G對H的陪集的集合的基數,即陪集的數目,記為[G:H ]

拉格朗日定理

H為G的子群,則:

  • R={<a,b>|a∈G,b∈G且a^-1^ *b∈H}是G上的一個等價關係。對於a∈G,若記[a]~R~={x|x∈G且<a,x>∈R},則[a]~R~=aH
  • 如果G是有限群,|G|=n,|H|=m,則m|n。

推論

  • 素數階群的子群一定是平凡群。(素數階的群不存在非平凡子群)
  • 設<G,*>是n階群,則對任意a∈G,有a^n^=e
  • 有限群中,元素的階能整除群的階
  • 素數階群一定是循環群,且每個非幺元均為生成元

正規子群和商群

正規子群 / 不變子群

定義

H\(\leq\)G,\(\forall g\in G\),gH=Hg,記為H\(\unlhd\)G

判別

\(\forall a\in G\)

  • aH=Ha,(即H\(\unlhd\)G)
  • \(\forall h\in H\),aha^-1^\(\in\)H
  • aHa^-1^\(\subseteq\)H
  • aHa^-1^=H

如果G是交換群,則G的任何子群都是正規子群

[G:H]=2 , 則H是G的正規子群

單群

G除了平凡子群外無其他正規子群

性質

  • 正規子群與子群的乘積是子群
  • 正規子群與正規子群的乘積是正規子群
  • 傳遞性

商群

運算

在G/H上定義陪集乘法運算∙,對於任意aH,bH∈G/H, 有

\[aH·bH=(ab)H \]

定義

設G為群,H為正規子群,則G/H關於運算∙構成一個群,稱為G的商群

性質

  • 商群G/H的單位元是eH(=H)
  • 在G/H中aH的逆元是a^-1^H

推論

  • 若G是交換群,G/H也是交換群
  • 商群的階是G階數的因子

同態與同構

同態映射 / 同態 ~

定義

<A,\(\star\)>與<B,*>滿足\(f(a_1\star a_2)=f(a_1)*f(a_2)\)

稱 f 為同態映射 / 同態,<A,\(\star\)>同態於<B,*>

記為 A~B

同態象

<f(A), *>為<A,\(\star\)>的一個同態象

自然同態

群G到商群G/H的同態,為 a\(\rightarrow\)aH

分類

  • f:A\(\rightarrow\)B 為滿射,f 稱為滿同態
  • f:A\(\rightarrow\)B 為入射,f 稱為單一同態
  • f:A\(\rightarrow\)B 為雙射,f 稱為同構映射
同構

f 為同構映射時,稱<A,\(\star\)>與<B,*>同構,記為A\(\cong\)B

  • 同構關係是等價關係
凱萊定理

任何一個有限群同構於一個置換群。

置換群即運算表中所有行 OR 所有列

自同態 / 自同構

自身到自身的映射

同態映射性質

在 f 作用下

  • <A, $\star $>的所有性質在同態象上保留
  • 若同構,則<B, *>擁有<A, $\star $>的所有性質

同態核

定義

A中元素映射 f 後為幺元。記為 Ker(f),稱為 f 的同態核

Ker(f) = {x|x∈G且f(x)=e’}

性質

  • 同態核N為A的正規子群
  • f 為單同態 \(\iff\) Ker(f)={e}
  • 若Ker(f)=N ,則 f(a)=f(b) \(\iff\) aN=bN

同態基本定理

  • 若 f 為A到B的滿同態,Ker(f)=N,則A/N\(\cong\)B
  • 若h為A自然同態,存在A/N到B的同構g,有f=gh

第一同構定理 / 商群同構定理

  • 若 f 為A到B的滿同態,Ker(f)=N,H\(\unlhd\)A 且 N\(\subseteq\)H
    • 則 A/H \(\cong\) B/f(H)
  • 若 H\(\unlhd\)A 且 K\(\unlhd\)A 且 K\(\subseteq\)H
    • 則 A/H \(\cong\) (A/K) / (H/K)

環與域

定義

對於<A, +, ·>有兩種二元運算的代數系統

  • <A, +>是阿貝爾群

  • <A, ·>是半群

  • 運算 · 對於 + 是可分配的,即\(\forall a,b,c\in A\)

    a·(b+c)=(a·b)+(a·c)

    (b+c)·a=(b·a)+(c·a)

為了區別環中的兩個運算,通常稱+運算為環中的加法,·運算為環中的乘法。

零元

加法單位元,記為0(\(\theta\))

單位元

乘法單位元,記為1

負元

加法逆元,記為-x

逆元

乘法逆元,記為x^-1^

例子

  • <R,+,·> 實數環
  • <Q,+,·> 有理數環
  • <I,+,·> 整數環
  • <M~n~(I),+, ·> n階整數矩陣環
  • <N~k~ , +~k~ , ×~k~> 模k整數環
  • <Z[i], +, ·>(Z[i]=a+bi,a,b\(\in\)Z,i^2^=-1) 高斯整數環 (複數)
  • <R[x] ,+, ·> R[x]為實數多項式

性質

與理解的加法乘法相同,消去律不一定

  • \(\theta\)=\(\theta\)·a=\(\theta\)
  • a·(–b)=(–a)·b = –(a·b)
  • (–a)·(–b)=a·b
  • a·(b–c)=(a·b)–(a·c)
  • (b–c)·a=(b·a)– (c·a)

特殊環

交換環

<A, · >可交換

含幺環

<A, · >含幺元

無零因子環

等價於乘法消去律)

\(\forall a,b\in A, a\neq\theta, b\neq \theta\),則必有\(a·b\neq\theta\)

零因子

\(a,b\in A, a\neq\theta, b\neq \theta\),有\(a·b=\theta\),則a或b為零因子

整環

定義

(基於乘法運算的性質)

交換、無零因子 OR 含幺、無零因子

即同時滿足交換環、含幺環和無零因子環的條件

子環

定義

環的子集,也是環

判定定理

\(\forall a,b\in S,a-b\in S,a·b\in S\)

定義

滿足如下:

  • <A, +>是阿貝爾群
  • <A – {\(\theta\)}, ·>是阿貝爾群
  • 運算 · 對運算+是可分配的

例子

  • 實數域
  • 有理數域
  • 〈Z~n~,+~n~, · ~n~ 〉是域的充要條件是n是素數

域與整環的關係

  • 域一定是整環
  • 有限整環一定是域

環的同態定義

V~1~=<A,*,∘>和V~2~=<B,⊛,◎>是兩環,其中*、∘、⊛和◎都是二元運算。f 是從AB的一個映射,使得對\(\forall\)a, b\(\in\)A有:

f(a*b)=f(a)⊛f(b)

f(ab)=f(a)◎f(b)

則稱f是環V1到環V2的同態映射

分類

如果f單射、滿射和雙射,分別稱f單同態、滿同態和同構

同態像及其特性

<f(A),⊛,◎>是<A,*,∘>的同態像

  • 任何環的同態像是環
綜合例題

設<R,+, · >是環,其乘法單位元記為1,加法單位元記為0,對於任意a,b\(\in\)R,定義

a⊕b=a+b+1,a⊙b=a·b+a+b。求證: <R, ⊕, ⊙ >也是含幺環,並與<R,+, · >同構。

證明:

首先證明<R, ⊕, ⊙ >是環。

(1) <R, ⊕ >是阿貝爾群。

(2) <R, ⊙ >是含幺半群。

(3) ⊙對⊕可分配,再證明同構。

(4)構造雙射f: f(a)=a-1,驗證同構性。

(1) <R, ⊕ >是阿貝爾群。

顯然R關於⊕是封閉的且⊕運算是可交換的。

結合性:對於任意的x,y,z\(\in\)R,有

(x⊕y)⊕z=(x+y+1)⊕z=x+y+z+2,而

x⊕(y⊕z )= x⊕ (y+z+1)=x+y+z+2, 即⊕運算滿足結合律。

幺元:對於任意x\(\in\)R, x⊕-1= x+(-1)+1=x,-1是R關於⊕運算的幺元。

逆元:對於任意x\(\in\)R, x⊕(-x-2)= x+(-x-2)+1=-1, +(-x-2)是x關於⊕運算的逆元。

所以<R, ⊕ >是阿貝爾群。

(2) <R, ⊙ >是含幺半群。

顯然R關於⊙是封閉的、可交換的。

結合性:對於任意的x,y,z ÎR,有

(x ⊙ y) ⊙ z=(xy+x+y) ⊙ z=xyz+xz+yz+xy+x+y+z,而

x ⊙(y ⊙ z )= x ⊙ (yz+y+z)=xyz+xy+xz+yz+x+y+z, 即⊙運算滿足結合律。

幺元:對於任意xÎR, x ⊙ 0=0+ x+0=x,0是R關於⊙運算的幺元。

所以<R, ⊙ >是含幺半群.

(3) ⊙對⊕可分配

對於任意的x,y,z\(\in\)R,有

x⊙(y⊕z )= x⊙(y+z+1)=xy+xz+x+x+y+z+1=xy+xz+2x+y+z+1

(x⊙y)⊕(x⊙z)=(xy+x+y)⊕(xz+x+z)=xy+xz+2x+y+z+1

同理可以證明右可分配性。

綜上所述, <R, ⊕, ⊙ >也是含幺環

再證明同構。

構造雙射f: f(a)=a-1,驗證同構性。

(4)證明同構。構造函數f: f(x)=x-1

雙射:對於任意x\(\in\)R,則有x+1\(\in\)R,使得f(x+1)=x,所以f是滿射

x,y\(\in\)R,若f(x)=f(y),則有x-1=y-1,即x=y,所以f是單射。

同態: f(x+y)=x+y-1

f(x)⊕f(y)=(x-1)⊕(y-1)=x-1+y-1+1=x+y-1

所以f(x+y)= f(x)⊕f(y)

又因為 f(x·y)=x·y-1

f(x)⊙f(y)=(x-1) ⊙(y-1)=(x-1)· (y-1)+x-1+y-1

​ =x·y-x-y+1+x-1+y-1=x·y-1

所以f(x·y)= f(x)⊙f(y)

​ 綜上,<R, ⊕, ⊙ >與<R,+, ∘ >同構。

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

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

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