自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

全棧必備 你需要了解的Java編程基礎(chǔ)

開發(fā) 開發(fā)工具
對一個全棧而言,Java 是必備的編程語言之一。 而談到Java,雖萬語千言卻不知從何開始,老碼農(nóng)從個人的角度看一下Java 語言的編程基礎(chǔ)。

那一年,從北郵畢業(yè),同一年,在大洋的彼岸誕生了一門對軟件業(yè)將產(chǎn)生重大影響的編程語言,它就是——Java。1998年的時候,開始學(xué)習(xí)Java1.2,并在Java Orbix 上做服務(wù),而如今Java 9 已經(jīng)來了,而且 Java 10 也已經(jīng)不遠(yuǎn)了。

對一個全棧而言,Java 是必備的編程語言之一。 而談到Java,雖萬語千言卻不知從何開始,老碼農(nóng)從個人的角度看一下Java 語言的編程基礎(chǔ)。

虛擬機

Java 真正牛X的地方就在于JVM。JVM是一個抽象的計算機,具有指令集、寄存器、垃圾回收堆、棧、存儲區(qū)、類文件的格式等細(xì)節(jié)。所有平臺上的JVM向上提供給Java字節(jié)碼的接口完全相同,但向下提供適應(yīng)不同平臺的接口,規(guī)定了JVM的統(tǒng)一標(biāo)準(zhǔn)并實現(xiàn)了Java程序的平臺無關(guān)性。這就是常說的,Java的跨平臺,但跨越不同實現(xiàn)的JVM時還是有些許不同的。

JVM是運行java程序的核心虛擬機,而運行java程序不僅需要核心虛擬機,也需要其他的類加載器,字節(jié)碼校驗器以及大量的基礎(chǔ)類庫。JRE除了包含JVM之外還包含運行Java程序的其他環(huán)境支持。

當(dāng)JVM啟動時,由三個類加載器對類進(jìn)行加載:

  1. bootstrap classloader 是由JVM實現(xiàn)的,不是java.lang.ClassLoader的子類 ,負(fù)責(zé)加載Java的核心類,其加載的類由 sun.boot.class.path指定,或者在執(zhí)行java命令時使用-Xbootclasspath選項, 還可以使用-D選項指定sun.boot.class.path系統(tǒng)屬性值
  2. extension classloader ,它負(fù)責(zé)加載JRE的擴展目錄中JAR的類包,為引入除Java核心類以外的新功能提供了一個標(biāo)準(zhǔn)機制。
  3. system/application classloader,加載來自-classpath或者java.class.path系統(tǒng)屬性以及CLASSPATH操作系統(tǒng)屬性所指定的JAR包和類路徑??梢酝ㄟ^靜態(tài)方法ClassLoader.getSystemClassLoader()找到該類加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作為它的父加載器。

ClassLoader加載Class的一般過程如下:

垃圾回收是JVM 中的一項重要技術(shù)。所謂垃圾回收只是針對內(nèi)存資源,而對于物理資源如數(shù)據(jù)庫連接、IO讀寫等JVM無能為力,所有程序中都需要顯式釋放。為了更快回收垃圾,可以將對象的引用變量設(shè)為null。垃圾回收具有不可預(yù)知性,即使調(diào)用了對象的finalize() ,System.gc()方法也不能確定何時回收,只是通知JVM而已。垃圾回收機制能精確標(biāo)記活著的對象,能精確定位對象之間關(guān)系,前者是完全回收的前提,后者實現(xiàn)歸并和復(fù)制等功能?,F(xiàn)在JVM有多種不同的垃圾回收算法實現(xiàn),不同的垃圾回收算法都有著典型的場景, 根據(jù)內(nèi)存和cpu使用的不同可以對垃圾回收算法進(jìn)行調(diào)整。

語法

作為一種編程語言,基本語法都是類似的,包括數(shù)據(jù)類型,操作符,語句,判斷和分支,循環(huán),遞歸等。

對于Java 的關(guān)鍵字可以做個文字游戲,排列成打油詩。

  • if volatile default, catch class short, 
  • abstract package private, throw this protected.
  • else char break, return super true, 
  • instanceof interface long, switch null native.
  • while boolean case, try final static, 
  • extends false transient, throws void public.
  • import new float, continue for double,
  •  implements int byte, do synchronized.
  • finally, goto const......

如果沒有記錯的話,goto 和 const 是 java 的保留字而不是關(guān)鍵字。弄清楚每個關(guān)鍵字的意義、用法、典型場景等,才算是“磨刀不誤砍柴功”。

數(shù)據(jù)

java 中的基本類型有4類8種:整型(int, short, long, byte),浮點型( float, double),邏輯型 boolean和 文本型 char。

Java中的基本數(shù)據(jù)結(jié)構(gòu)大多在java.util 中體現(xiàn),主要分為Collection和map兩個主要接口,而程序中最終使用的數(shù)據(jù)結(jié)構(gòu)則是繼承自這些接口的數(shù)據(jù)結(jié)構(gòu)類。

  1. import java.util.Hashtable; 
  2. import java.util.ArrayList; 
  3. import java.util.HashMap; 
  4. import java.util.HashSet; 
  5. import java.util.LinkedHashMap; 
  6. import java.util.LinkedHashSet; 
  7. import java.util.LinkedList; 
  8. import java.util.Stack; 
  9. import java.util.TreeMap; 
  10. import java.util.TreeSet; 
  11. import java.util.Vector; 
  12. ..... 

一般的,一個空的對象需要占用12字節(jié)的堆空間,一個空的String就要占用40字節(jié)的堆空間,這或許就是推薦用stringbuilder的一個原因吧。在Java中,類型決定行為,例如byte可以起到限制數(shù)據(jù)的作用,但是并不能節(jié)約內(nèi)存,在內(nèi)存中byte和int一樣是占用4字節(jié)的空間。一個對象的占用堆空間的多少一般與類中非static的基本數(shù)據(jù)類型和引用變量有關(guān)。每一個數(shù)組中的元素都是一個對象,每一個對象都有一個16字節(jié)的數(shù)組對象頭。

回憶一下堆棧,Java 的堆是一個運行時數(shù)據(jù)區(qū),類的對象從中分配空間。只有通過new()方法才能保證每次都創(chuàng)建一個新的對象,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負(fù)責(zé)的。Java的棧存取速度比堆要快,棧數(shù)據(jù)可以共享,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,主要存放一些基本類型的變量和對象句柄。

(圖片來自 https://www.programcreek.com/2013/09/top-8-diagrams-for-understanding-java/)

可以通過如下的方式粗略的判斷不同數(shù)據(jù)類型的內(nèi)存使用狀況:

  1. Runtime.getRuntime().gc(); 
  2. Thread.yield(); 
  3. iBefore = Runtime.getRuntime().freeMemory(); 
  4. 類 變量 = new 類(參數(shù)類別); 
  5. Runtime.getRuntime().gc(); 
  6. Thread.yield(); 
  7. iAfter = Runtime.getRuntime().freeMemory(); 
  8. System.out.println(iBefore-iAfter);  

另外,Java 中的引用對內(nèi)存也有著不同的影響,主要包括:

  • 強引用: strong reference
  • 軟引用: soft reference
  • 弱引用: weak reference
  • 虛引用: Phantom Reference 

接口

抽象類和接口是Java 的兩大利器, 抽象類是OOP 的共性,而接口則簡單規(guī)范,提高了代碼的可維護(hù)性和可擴展性,同時是軟件松耦合的重要方式。對修改關(guān)閉,對擴展(不同的實現(xiàn)implements)開放,接口本身就是對開閉原則的一種體現(xiàn)。

Java接口是一系列方法的聲明,是一些方法特征的集合,一個接口只有方法而沒有方法的實現(xiàn)。弄一點玄虛,接口是一組規(guī)則的集合,它規(guī)定了實現(xiàn)本接口的類或接口必須擁有的一組規(guī)則,是在一定粒度上同類事物的抽象表示。

  1. <修飾符>interface<接口名>{  
  2. [<常量聲明>]  
  3. [<抽象方法聲明>]  
  4. }  

接口是類型轉(zhuǎn)換的前提和動態(tài)調(diào)用的保證。實現(xiàn)某一接口就完成了類型的轉(zhuǎn)換也就是多重繼承,一般用來作為一個類型的等級結(jié)構(gòu)的起點;動態(tài)調(diào)用則只關(guān)心類型,不關(guān)心具體類。接口可以為不同類順利交互提供標(biāo)準(zhǔn)。

  Java中的類描述了一個實體,包括實體的狀態(tài),也包括實體可能發(fā)出的動作。而接口定義了一個實體可能發(fā)出的動作,但只是定義了這些動作的原型,沒有實現(xiàn),也沒有任何狀態(tài)信息。所以接口有點象一個規(guī)范、一個協(xié)議,是一個抽象的概念;而類則是實現(xiàn)了這個協(xié)議,滿足了這個規(guī)范的具體實體,是一個具體的概念。

從程序角度簡單理解,接口就是函數(shù)聲明,類就是函數(shù)實現(xiàn)。需要注意的是同一個聲明可能有很多種實現(xiàn)。   

泛型

所謂“泛型”,就是寬泛的數(shù)據(jù)類型,任意的數(shù)據(jù)類型。Java 中的泛型是以C++模板為參照的,本質(zhì)是參數(shù)化類型的應(yīng)用,主要包括:

泛型類,例如:

  1. public class MyGeneric<T,V> { 
  2. T obj_a; 
  3. V obj_b; 
  4. MyGeneric(T obj_1,V obj_2){ 
  5. this.obj_a = obj_1; 
  6. this.obj_b = obj_2; 

泛型接口,例如:

  1. interface MyInterface<T extends Comparable<T>>{ 
  2. //... 

泛型方法,例如:

  1. <T extends Comparator<T>, V extends T> boolean MyIn(T x, V[] y) 

泛型中的類型參數(shù)只能用來表示引用類型,不能用來表示基本類型,如 int、double、char 等。但是傳遞基本類型不會報錯,因為它們會自動裝箱成對應(yīng)的包裝類。類型參數(shù)必須是一個合法的標(biāo)識符,習(xí)慣上使用單個大寫字母,通常情況下,K 表示鍵,V 表示值,E 表示異?;蝈e誤,T 表示一般意義上的數(shù)據(jù)類型。

使用有界通配符,可以為參數(shù)類型指定上界和下界,從而能夠限制方法能夠操作的對象類型。最常用的是指定有界通配符上界,使用extends子句創(chuàng)建。 對于實現(xiàn)了<? extends T>的集合類只能將它視為生產(chǎn)者向外提供元素(get),而不能作為消費者來對外獲取元素(add)。

Java泛型只能用于在編譯期間的靜態(tài)類型檢查,然后編譯器生成的代碼會擦除相應(yīng)的類型信息,這樣到了運行期間實際上JVM根本就知道泛型所代表的具體類型。在Java中不允許創(chuàng)建泛型數(shù)組,無法對泛型代碼直接使用instanceof。

使用泛型,可以消除顯示的強制類型轉(zhuǎn)換,提高代碼復(fù)用,還可以提供更強的類型檢查,避免運行時的ClassCastException。

反射

JAVA反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調(diào)用它的任意一個方法。普通調(diào)用需要在編譯前必須了解所有的class,包括成員變量,成員方法,繼承關(guān)系等。而反射可以于運行時加載、探知、使用編譯期間完全未知的類。也就是說,Java程序可以加載一個運行時才得知名稱的class,獲悉其完整構(gòu)造。

Java反射的方式主要分為兩類:Java.lang.reflect.*和Cg-lib工具包。

因為在反射調(diào)用中同樣要遵循java的可見性規(guī)約,因此Class.getMethod方法只能查找到該類的public方法。如果要獲取聲明為private的方法對象,則需要通過Class.getDeclaredMethod,而且在invoke前要設(shè)置setAccessable(true)才能保證調(diào)用成功。如果的確需要調(diào)用父類方法,可以通過Class.getInterface方法查找父類,再實例化一個父類對象,然后按照調(diào)用private Method的方式進(jìn)行調(diào)用。

反射的應(yīng)用廣泛,例如Spring容器的注入,就是運用了反射的方式,通過配置文件讀取欲實例化的類的名稱,屬性,然后由spring容器統(tǒng)一實例化,既達(dá)到了注入的目的,又可以通過容器統(tǒng)一控制bean的作用域、生命周期等。J

在框架和容器中,比較廣泛的就是java bean的規(guī)范,或者POJO,以及一些作為與數(shù)據(jù)庫交互載體的持久化對象,都會有要求:

每個field都要有setXxx/getXxx方法,命名符合駝峰命名法,且需要聲明為public的。

含有一個無參的構(gòu)造方法。 ***條就是為了方便反射屬性值,通過get/set方法。另一條是為了保證可以通過cls.newInstance()實例化一個新對象。 另外還有servlet(要有init、service、doGet、doPost方法),filter(要有doFilter方法)。這些組件定義的規(guī)范就是為了容器可以通過反射的方式進(jìn)行統(tǒng)一調(diào)用和管理。

ava.lang.reflect包中還自帶了代理模式的一個實現(xiàn),靜態(tài)代理和動態(tài)代理都是有意思的事, 很多插件化開發(fā)都使用了代理模式。

注解

注解這種機制允許在編寫代碼的同時可以直接編寫元數(shù)據(jù)。注解就是代碼的元數(shù)據(jù),包含了代碼自身的信息。

注解可以被用在包,類,方法,變量,參數(shù)上。自Java8開始,有一種注解幾乎可以被放在代碼的任何位置,叫做類型注解。被注解的代碼并不會直接被注解影響,只會向第三系統(tǒng)提供關(guān)于自己的信息以用于不同的需求。注解會被編譯至class文件中,而且會在運行時被處理程序提取出來用于業(yè)務(wù)邏輯。當(dāng)然,創(chuàng)建在運行時不可用的注解也是可能的,甚至可以創(chuàng)建只在源文件中可用,在編譯時不可用的注解。

Java自帶的內(nèi)建注解可以叫元注解,由JVM 對這些注解進(jìn)行執(zhí)行。常見的元注解如下:

@Retention:用來說明如何存儲已被標(biāo)記的注解,值包括:SOURCE, CLASS和RUNTIME。

@Target:這個注解用于限制某個元素可以被注解的類型。例如:

  • ANNOTATION_TYPE :應(yīng)用到其他注解上
  • CONSTRUCTOR:使用到構(gòu)造器上
  • FIELD:使用到域或?qū)傩陨?/li>
  • LOCAL_VARIABLE:使用到局部變量上。
  • METHOD:使用到方法級別的注解上。
  • PACKAGE:使用到包聲明上
  • PARAMETER:使用到方法的參數(shù)上
  • TYPE:使用到一個類的任何元素上。

@Documented:被注解的元素將會作為Javadoc產(chǎn)生的文檔中的內(nèi)容,都默認(rèn)不會成為成為文檔中的內(nèi)容。這個注解可以對其它注解使用。

@Inherited:在默認(rèn)情況下,注解不會被子類繼承。被此注解標(biāo)記的注解會被所有子類繼承。

還有 @Deprecated,@SuppressWarnings,@Override等等。

Java反射API包含了許多方法來在運行時從類、方法或者其它元素獲取注解的手段。接口AnnotatedElement包含了大部分重要的方法,如下:

  • getAnnotations(): 返回該元素的所有注解,包括沒有顯式定義該元素上的注解。
  • isAnnotationPresent(annotation): 檢查傳入的注解是否存在于當(dāng)前元素。
  • getAnnotation(class): 按照傳入的參數(shù)獲取指定類型的注解。返回null說明當(dāng)前元素不帶有此注解。

自己寫個注解,會讓代碼變得簡潔。一些類庫如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit等,使用注解來完成代碼質(zhì)量分析,單元測試,XML解析,依賴注入和許多其它的工作。

線程

一個JVM 相當(dāng)于操作系統(tǒng)的一個進(jìn)程,Java線程是進(jìn)程的一個實體,是CPU調(diào)度和分派的基本單位,JVM線程調(diào)度程序是基于優(yōu)先級的搶先調(diào)度機制。 線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程包含以下內(nèi)容:

  • 一個指向當(dāng)前被執(zhí)行指令的指令指針
  • 一個棧
  • 一個寄存器值的集合,定義了一部分描述正在執(zhí)行線程的處理器狀態(tài)的值
  • 一個私有的數(shù)據(jù)區(qū)

在 Java程序中,有兩種方法創(chuàng)建線程:對 Thread 類進(jìn)行派生并覆蓋 run方法和通過實現(xiàn)Runnable接口創(chuàng)建。獲取當(dāng)前線程的對象的方法是Thread.currentThread()。實現(xiàn)Runnable接口相對于繼承Thread類而言,更適合多個相同的程序代碼的線程去處理同一個資源,繞過單繼承限制,而且線程池只能放入實現(xiàn)Runable或callable類線程,一般不直接放入繼承Thread的類。

線程池的基本思想還是一種對象池的思想,開辟一塊內(nèi)存空間,里面存放了眾多(未死亡)的線程,池中線程執(zhí)行調(diào)度由池管理器來處理。當(dāng)有線程任務(wù)時,從池中取一個,執(zhí)行完成后線程對象歸池,這樣可以避免反復(fù)創(chuàng)建線程對象所帶來的性能開銷,節(jié)省了系統(tǒng)的資源。線程池分好多種:固定尺寸的線程池、單任務(wù)線程池、可變尺寸連接池、延遲連接池、自定義線程池等等。

理解Java線程的狀態(tài)機(新建,就緒,運行,睡眠/阻塞/等待,消亡等)對于線程的使用很有幫助。

(圖片來自http://blog.csdn.net/Evankaka/article/details/44153709)

在使用任何多線程技術(shù)的時候,都要關(guān)注線程安全。盡管線程安全類中封裝了必要的同步機制,從而客戶端無須進(jìn)一步采取同步措施,但還是要關(guān)注一下資源競爭即所謂的競態(tài)條件。競態(tài)條件成立的三個條件: 1)兩個處理共享變量 2)至少一個處理會對變量進(jìn)行修改 3)一個處理未完成前另一個處理會介入進(jìn)來 只要三個條件有一個不具備,就可以寫線程安全的程序了。 規(guī)避一,沒有共享內(nèi)存,就不存在競態(tài)條件了,例如利用獨立進(jìn)程和actor模型。 規(guī)避二,比如Java中的immutable 規(guī)避三,不介入,使用協(xié)調(diào)模式的線程如coroutine等,也可以使用表示不便介入的標(biāo)識——鎖、mutex、semaphore,實際上是使用中的狀態(tài)牌。鎖的使用問題包括死鎖和無法組合,只能寄托于事務(wù)內(nèi)存來奢望解決了。

通過Java多線程技術(shù),可以提高資源利用率,程序擁有更好的響應(yīng)。

排錯

Zero Bug 是每個程序員的目標(biāo), debug 是項繁重的工作,減少bug一般從Error Handling 開始,在Java 中主要體現(xiàn)在異常處理。

異常處理

Java 中 Exception的繼承關(guān)系如下圖:

(圖片來自https://www.programcreek.com/2013/09/top-8-diagrams-for-understanding-java/)

紅色部分為必須被捕獲,或者在函數(shù)中聲明為拋出該異常。其中,throwable 是一個有趣的東西, 在某些極端情況下, 直接catch throwable 才能得到想要的效果。

靜態(tài)代碼分析

據(jù)說,在整個軟件開發(fā)生命周期中,30% 至 70% 的代碼邏輯設(shè)計和編碼缺陷是可以通過靜態(tài)代碼分析來發(fā)現(xiàn)和修復(fù)的。但是,code review 往往要求大量的時間消耗和相關(guān)知識的積累,因此使用靜態(tài)代碼分析工具自動化執(zhí)行代碼檢查和分析,能夠極大地提高軟件可靠性并節(jié)省軟件開發(fā)和測試成本。

靜態(tài)代碼分析是指無需運行被測代碼,僅通過分析或檢查源程序的語法、結(jié)構(gòu)、過程、接口等來檢查程序的正確性,找出代碼隱藏的錯誤和缺陷,如參數(shù)不匹配,有歧義的嵌套語句,錯誤的遞歸,非法計算,可能出現(xiàn)的空指針引用等等。靜態(tài)代碼分析主要是基于缺陷模式匹配,類型推斷,模型檢查和數(shù)據(jù)流分析等。

通過靜態(tài)代碼分析工具可以自動執(zhí)行靜態(tài)代碼分析,快速定位代碼隱藏錯誤和缺陷;幫助我們更專注于分析和解決bug;顯著減少在代碼逐行檢查上花費的時間,提高軟件可靠性并節(jié)省軟件開發(fā)和測試成本。

常用的靜態(tài)代碼工具有checkstyle,findbugs,PMD等,其中Checkstyle 更加偏重于代碼編寫格式檢查,而 FindBugs,PMD,Jtest 等著重于發(fā)現(xiàn)代碼缺陷,但個人還是喜歡Sonar。

內(nèi)存泄漏

在Java中排錯的一個麻煩就是內(nèi)存泄露。內(nèi)存泄漏是指無用對象持續(xù)占用內(nèi)存或無用對象的內(nèi)存得不到及時釋放,從而造成內(nèi)存空間的浪費。內(nèi)存泄露有時不嚴(yán)重且不易察覺,這樣可能不知道存在內(nèi)存泄露,但有時也會很嚴(yán)重,會引發(fā)Out of memory。

常用的Java內(nèi)存分析工具有VisualVM、jconsole、jhat、JProfiler、Memory Analyzer (MAT)等??紤]能處理的Heapdump大小及速度,網(wǎng)絡(luò)環(huán)境,可視化分析,內(nèi)存資源限制,是否免費使用等,推薦的工具為jmap + MAT。

Java中內(nèi)存分析的一般步驟如下: 

  1. 把Java應(yīng)用程序使用的堆dump下來,啟動時加虛擬機參數(shù):-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=path,這樣在程序發(fā)生OOM時,會自動在相關(guān)路徑下生成dump文件
  2. 然后使用Java heap分析工具,找出對象數(shù)量或占用內(nèi)存太多的對象 執(zhí)行jmap -dump:format=b,file=heap.bin pid 其中,format=b,表示dump出來的文件是二進(jìn)制格式,file=heap.bin,表示dump出來的文件名是heap.bin,pid是進(jìn)程號。
  3. 需要分析嫌疑對象和其他對象的引用關(guān)系,結(jié)合程序的源代碼,找出原因。 可以將Heapdump拉到本地,使用MAT打開進(jìn)行分析。如果Heapdump較大,本地內(nèi)存不夠,可以在服務(wù)器上執(zhí)行sh ParseHeapDump.sh Heapdumpfile,得到分解后的文件,然后拉到本地,再使用MAT打開,就可以進(jìn)一步分析了。

【本文來自51CTO專欄作者“老曹”的原創(chuàng)文章,作者微信公眾號:喔家ArchiSelf,id:wrieless-com】

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2018-01-09 15:35:54

Python編程基礎(chǔ)

2023-05-18 09:00:00

人工智能StarCoder編程語言

2015-12-23 10:00:04

多種編程語言

2017-04-06 10:27:01

JavaScript基礎(chǔ)Java

2020-07-20 08:23:04

Redis分布式系統(tǒng)

2012-06-27 09:11:47

2012-06-26 10:13:55

2011-04-01 11:16:06

hessian

2021-06-01 07:16:21

C語言基礎(chǔ)代碼

2017-10-12 18:42:08

前端HTML5基礎(chǔ)知識

2015-09-17 09:36:46

Chrome改變

2020-10-13 06:56:19

JavaScript異常類型開發(fā)

2022-01-04 19:28:05

VMware云端虛擬化

2016-11-01 16:41:08

直通網(wǎng)線連接端口傳輸數(shù)據(jù)

2022-03-18 12:46:56

Go 語言編程語言

2017-01-15 17:48:04

Java開發(fā)者編程語言

2016-12-26 17:53:05

Java開發(fā)者編程語言

2017-02-05 16:00:35

Java編程語言

2015-10-22 17:20:46

命令工具Linux

2019-07-18 12:57:21

大數(shù)據(jù)互聯(lián)網(wǎng)算法
點贊
收藏

51CTO技術(shù)棧公眾號