從學代碼到跑路

最近給組里的小伙伴做了一次培訓,分享了一些編程的基礎和自我學習編程的方法。感覺對大家可能有用,里面很有很面試經常問到的基礎相關的東西,想分享給大伙。

計算機基礎篇

首先看三段代碼,分別為JS代碼、C#代碼和Java代碼。有:局部變量、全局變量,值類型和引用類型,靜態和常量、線程(JS沒有)。

// 定義了一個Person類
function Person(id,name,age){
  this.id = id;
  this.name = name;
  this.age = age;
}
function test(){
  // 局部變量
  let id = 0;
  console.log(id);
}
// 全局變量
var person = new Person(100,'梁先生',25);
test();
// 定義了一個Person類
public class Person {  
  // 常量
  public const string CodeTest = "梁先生";
  // 靜態變量
  public static int Code = 0; 
  // 私有變量
  private int testId;
  public void add(){
    // 局部變量
    int temp = 0;
    testId++;    
  }
}

// 線程類
public class Count implements Runnable{
  Person person;
  public Count(Person person){
      this.person = person;
  }    
  @Override
  public void run(){
     person.add();
  }
}

var person = new Person(100,'梁先生',25);
// 多線程的問題
int threadCount = 100;
for(int i = 0; i<threadCount;i++){
    ThreadMsDeal = new Thread(new Count(person));
    ThreadMsDeal.Start();
}
// 定義了一個Person類
public class Person{
  // 常量
  public const string CodeTest = "梁先生";
  // 靜態變量
  public static int Code = 0; 
    
  // 定義了一個私有變量
  private testId = 0;
  public Person(id,name,age){
    this.id = id;
    this.name = name;
    this.age = age;
  }
    
  public int id {get;set;}
  public string name{get;set;}
  public int age{get;set;}
    
  public void test(){
    // 局部變量
    int temp = 0;
    testId++;    
  }
}

var person = new Person(100,'梁先生',25);
// 多線程的問題
int threadCount = 100;
for(int i = 0; i<threadCount;i++){
    ThreadMsDeal = new Thread(new ThreadStart(test));
    ThreadMsDeal.Start();
}

上面代碼中的數據類型都能在下方的圖找到對應。


數據類型
怎么分配的呢

JVM和CLR和很類似,使用JVM來說明


JVM
  • new 的引用類型的值存在堆中。
  • 普通值類型的如int啥的放在棧中。
  • const的值是在編譯期間確定的,因此只能在聲明時通過常量表達式指定其值。
  • static限定,意味著“不管類的對象被創建多少次,都只有一個實例” 。
  • 本地方法棧:虛擬機使用到的Native 方法服務。
  • 常量和靜態變量都是放在方法區里面的,這個區域也對所有線程共享。

js的V8虛擬機和上面兩種有點像,但是又很不相同:JavaScript中所有的數據都是存放在堆內存中,為了基本數據類型和引用數據類型的理解區分開來。

V8

文字總結內存

值類型和引用類型

值類型的內存分配

引用類型的內存分配

Q:為什么阿里巴巴Java開發手冊不建議在for循環中使用“+”進行字符串拼接?

String

A:String是一個對象,且對象一旦被創建就是固定不變的了,對String對象的任何改變都不影響到原對象,所有一直在創建和銷毀對象,改變指針的指向,浪費內存和性能。我們可以使用StringBuilder和StringBuffer,他們是可變的。

GC垃圾回收

垃圾回收
  • 如果不進行垃圾回收,內存遲早都會被消耗空
  • 內存溢出:就是你要求分配的內存超出了系統能給你的,系統不能滿足需求,于是產生溢出
  • 內存泄漏:是指你向系統申請分配內存進行使用(new),可是使用完了以后卻不回收

垃圾回收的幾種方式

標記清除:后會產生大量不連續的內存碎片

標記-整理算法:GC暫停的時間會增長,需要將所有的對象都拷貝到一個新的地方,并更新它們的地址
復制算法:需要一塊能容納下所有存活對象的額外的內存空間,可一次性分配的最大內存縮小了一半
跳過

GC調優:很多次面試的時候有問道,gc垃圾回收的調優,你又了解嘛?

  1. 不要顯式調用GC的回收函數,比如:Java中的 System.gc()、C#中的析構(2次回收)
  2. 盡量減少臨時對象的使用(不使用的變量就給刪掉)
  3. 對象不用時最好顯式置為Null
  4. 不用大量的String來字符串拼接
  5. 能用基本類型如Int,Long,就不用Integer,Long對象
  6. 盡量少用靜態對象變量,因為靜態變量只有在程序結束時才釋放
  7. 分散對象創建或刪除的時間,比如避免:new 類[10000]

線程不安全

線程不安全

不安全

線程不安全:就是不提供數據訪問保護,有可能出現多個線程先后更改數據造成所得到的數據是臟數據。

Q: i++是線程安全嗎?
A: 是不安全的

jvm的線程模型

每個線程都有自己的工作內存,每個線程需要對共享變量操作時必須先把共享變量從主內存 load 到自己的工作內存,等完成對共享變量的操作時再 save 到主內存。比如上面的i,是先拷貝到線程中的,然后做完++以后,不是很及時的刷新到主存。

問題就出在這了,如果一個線程運算完后還沒刷到主內存,此時這個共享變量的值被另外一個線程從主內存讀取到了,這個時候讀取的數據就是臟數據了,它會覆蓋其他線程計算完的值。

線程安全必要條件

必要條件

原子性:跟數據庫事務的原子性概念差不多,即一個操作(有可能包含有多個子操作)
要么全部執行(生效),要么全部都不執行(都不生效)。

可見性:當多個線程并發訪問共享變量時,一個線程對共享變量的修改,
其它線程能夠立即看到。可見性問題是好多人忽略或者理解錯誤的一點。

順序性:是程序執行的順序按照代碼的先后順序執行。

解決辦法

原子性解決:保證操作原子性的工具是鎖和同步方法(或者同步代碼塊)。使用鎖,可以保證同一時間只有一個線程能拿到鎖,也就保證了同一時間只有一個線程能執行申請鎖和釋放鎖之間的代碼。 Java中的synchronized、c#中的lock

可見性解決:volatile關鍵字來保證可見性。當使用volatile修飾某個變量時,它會保證對該變量的修改會立即被更新到內存中,因此其它線程需要讀取該值時必須從主內存中讀取,從而得到最新的值。

高并發,目前比較流行的有兩種,一種為多線程的方式(java中后臺就是這樣的),一種是事件驅動,異步IO(node中是這樣的)。

高并發-多線程

多線程
線程的調度

線程的在操作系統的調度中,某個時刻,只能將資源讓給一個線程。而線程的上下文的切換,會有很大的消耗。

高并發-異步IO

異步IO
事件隊列

只有一個線程,沒有上下文的切換消耗,但是只有一個線程干活。

選擇場景如果 IO時間多,那異步IO效率高,選擇場景如果 計算時間多,那同步IO效率高。Nginx 和 Node就是基于這種模式

常用的數據結構

  • 數組(數組不要定義太長,因為需要連續的空間。不夠的時候會引起GC)
  • 棧(遞歸的時候利用的是棧)
  • 隊列(日志的讀寫分離)
  • 鏈表
  • 樹 (菜單遞歸建樹、數據庫索引)
  • 哈希表(查詢數據快,只需要一次哦)

時間復雜度

時間復雜度

這個方法需要 (n + 1 + n + 1) = 2n + 2 次運算。我們一般會把系數和尾巴去掉,因為在n趨近無窮大的時候,系數和尾巴都不咋起作用,差不了多少。故:時間復雜度為:O(n)

void Func(int n) {
    for(int i = 0; i < n; i++) {         
        for(int j = 0; j < n; j++) {       
            printf("Hello, World!\n");      
        }
    }
}

時間復雜度為:O(n × n × 1),即 O(n^2)。:

數據結構的時間復雜度

數據結構的時間復雜度

時間復雜度的圖像,根據自己的編碼情況選擇對應的數據結構

時間復雜度的圖表

大家可以對比一下,在x軸趨于無窮大時,x為數據量,而O(1)和log(n)的y值還只是1或者很小很小,y為查詢的操作的次數。所以大家可以選擇一定的數據結構來優化自己的代碼


面向對象篇

在編碼的時候去理解和運用一下三個特征。因為培訓例子是舉的工作中的例子,不咋好拿代碼出來講解。在我們發現有相同的代碼的時候,我們是可以抽象出來的。還有一個是:對接口編程,而不是對實現編程。

面向對象

五大原則也是需要一定的編碼去支撐,然后才能自己的理解

五大原則

還有常用的設計模式,這個也是很重要的,需要掌握常用的,因為時間問題,不在這里講解。

總結篇

這個是個人總結,和語言無關,和框架無關。個人還是覺得基礎還是很重要的,如果你懂基礎,在代碼優化,和出問題的時候去排查,站的角度是不一樣的。

可能涉及到的書或者知識點比較多,但是沒有必要全看完整的東西 ,只需要掌握基礎,常用的知識點即可。不常用的東西,或者都不會涉及到的東西就不用看。

很多如代碼重構,優化,是可以一直貫穿在整個圖中的。

編程學習

下方是是個人的一些學習方法和經常看到一些知識點的出處。

學習方法

希望能幫助到大家,謝謝!

https://github.com/liangwei0101 可以的話,給個星星哦!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。