JAVA基礎之七-Collection和它的並行和流處理

来源:https://www.cnblogs.com/lzfhope/p/18408762
-Advertisement-
Play Games

動態代理的API Proxy 動態代理類 生成代理對象:Proxy.newProxyInstance( 類載入器,介面數組,處理器 ) 類載入器:對象.getClass( ).getClassLoader( ) 介面數組-被代理類的所有介面:被代理對象.getClass( ).getInterfac ...


Collection 翻下詞典,有許多含義:

收集;聚集;(常指同類的)收藏品;募捐,募集;作品集;聚積;取走;一群人;拿走;(常為季節性推出的)系列時裝(或家用品);一批物品

選擇“集合”作為翻譯名,我覺得可行,除非我們現在重新創造一個漢語片語。

 

對於CRUD和非CRUD,集合都是一個無比重要的東西,因為電腦的本質是對信息的處理。

信息一般不是單個,是一堆,一堆堆,一塊塊,一個個....

 

網上關於集合的資料無比多,所以本文主要是做一個簡要的介紹,並添加一些註意事項和個人感悟。

 

一、簡介

不過Collection的子孫過於多,用現有辭彙命名這些子孫並不容易,有待創建新的辭彙。

常用知名子孫有:

List  --  列表,javaDoc的釋義是:有序集合。

   --ArrayList   動態大小列表 ,這是crud中最常用的類型 。不保證順序

   --LinkedList  雙鏈列表,可以固定成員順序。本身實現了Deque的介面,可用於輔助實現FiLo的演算法

Set  - 無重覆集合,允許有一個null成員

  ---TreeSet   有序集合

  -- HastSet  哈希集合  ,主要是操作的性能好一些

    -- LinkedHashSet  雙向鏈哈希集合,保持了插入順序,又具有對應的性能

Queue -隊列

   --Deque  雙端操作隊列。它有一個著名的實現  LinkedList

Buffer --緩衝

  不過這個主要是阿帕奇的實現org.apache.commons.collections.Buffer,算不得java的基礎類型

 

如果是初級程式員,或者以CRUD為主的,那麼只要學些掌握ArrayList就差不多了,因為現在的大部分的ORM或者JDBC的上級實現都適用ArrayList來存儲數據集。

 

二、集合的基本方法

僅僅介紹Collection的介面方法,為了便於理解,以LinkedList為例子。

這些方法都極其簡單,也沒有什麼特別好解釋的,直接上例子吧!

package study.base.types.collection.list;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 演示Collection介面的基本操作和LinkedList的一些典型操作
 * @author lto
 */
public class TestLinkedList {
    private LinkedList<MoneyJar> list;
    private String[] givers = new String[]{"爸爸","媽媽","哥哥","姐姐","爺爺","奶奶"};
    private Random random = new Random();
    private Map<String,Long> realGivers;

    public TestLinkedList(int size) {
        this.list = new LinkedList<>();
        this.realGivers = new HashMap<>();
        //插入100個MoneyJar,金額和日期都是隨機的,giver是隨機
        for (int i = 0; i < size; i++) {
            String giver = givers[random.nextInt(givers.length)];
            int amount = random.nextInt(100);
            this.list.add(new MoneyJar(giver, amount, new Date()));
        }
        //按照giver分組統計個數,並賦值給realGivers
        this.list.stream().collect(Collectors.groupingBy(MoneyJar::giver,
                Collectors.counting())).forEach((k,v)->{
            realGivers.put(k,(long)v);});
        //列印realGivers
        this.realGivers.forEach((k,v)->{System.out.println(String.format("%s共有%d個", k, v));});
    }

    public void count(){
        long start = System.currentTimeMillis();
        final long[] total = {0};
        this.list.spliterator().forEachRemaining(mj-> total[0] += mj.amount());
        System.out.println(String.format("總共%d元",total[0]));
        System.out.println("耗費時間:"+(System.currentTimeMillis()-start));
    }

    public void sortByAmount(){
        this.list.sort((o1, o2) -> o1.amount().compareTo(o2.amount()));
    }
    /**
     * 統計每個giver給的錢,並列印結果
     */
    public void sumByGiver(){
        System.out.println("--------------------****************-----------------------------");
        //根據giver分組統計每個giver給的錢,並返回一個ListMap
        long start = System.currentTimeMillis();
        Map<String, Integer> result= this.list.stream().collect(Collectors.groupingBy(MoneyJar::giver,
                Collectors.summingInt(MoneyJar::amount)));
        //列印統計結果
        result.forEach((k,v)->{System.out.println(String.format("%s給的錢是%d", k, v));});
        System.out.println("耗費時間:"+(System.currentTimeMillis()-start));

        //採用for迴圈的方式,分組統計
        System.out.println("採用for迴圈的方式,分組統計-----------------------------");
        long start1 = System.currentTimeMillis();
        Map<String,List<Integer>> result1= new HashMap<>();
        //初始化result1,把realGivers的每個元素作為key,初始值為0
         this.realGivers.forEach((k,v)->{
            result1.put(k,new ArrayList<>());
        });
        //遍歷list,計算每個giver給的錢

        for (MoneyJar moneyJar : list) {
           result1.get(moneyJar.giver()).add(moneyJar.amount());
        }

        //根據result1的成員個數,創建對應的線程,然後線上程中計算每個giver給的錢,並計算總和
        int numThreads=result1.size();
        CountDownLatch latch = new CountDownLatch(numThreads);

        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        result1.forEach((k,v)->{
            Runnable worker = () -> {
                try {
                    long sum=0;
                    for (int i : v) {
                        sum+=i;
                    }
                    System.out.println(String.format("%s給的錢是%d", k, sum));
                } finally {
                    latch.countDown(); // 計數減一
                }
            };
            // 使用executor提交任務,而不是直接啟動Thread
            executor.submit(worker);
        });
        try {
            // 等待所有線程完成
            latch.await();
            System.out.println("All threads have finished.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 關閉executor,釋放資源
        executor.shutdown();
        System.out.println("耗費時間:"+(System.currentTimeMillis()-start1));
    }


    public void splitToSum(){
        //把錢罐的錢分為n份,分別統計,然後再合併總的金額,並統計耗費時間
        System.out.println("--   採用並行流的方法");
        long start = System.currentTimeMillis();
        Long total=list.parallelStream().mapToLong(MoneyJar::amount).sum();
        System.out.println("耗費時間:"+(System.currentTimeMillis()-start));
        System.out.println("總金額是"+total.toString());

        //採用傳統的for迴圈方式累積
        System.out.println("--   採用傳統的for迴圈的方法");
        start = System.currentTimeMillis();
        long sum=0;
        for (MoneyJar moneyJar : list) {
            sum+=moneyJar.amount();
        }
        System.out.println("總金額是"+sum);
        System.out.println("耗費時間:"+(System.currentTimeMillis()-start));
    }

    /**
     * 把小於等於指定的金額的錢都清理掉
     * @param amount
     */
    public void purgeSmallMoney(int amount){
        this.list.removeIf(moneyJar -> moneyJar.amount()<=amount);
    }

    record MoneyJar(String giver,Integer amount,
                    Date putDay){
    }

    public static void main(String[] args) {
        //當10萬個的時候,並行的速度反而是for的3倍左右。
        TestLinkedList test = new TestLinkedList(200);
        test.splitToSum();
        test.sortByAmount();
        System.out.println("-- 排序後 -----");
        for (MoneyJar moneyJar : test.list) {
            System.out.println(moneyJar);
        }
        //測試100萬的情況
        TestLinkedList test100 = new TestLinkedList(1000000);
        test100.splitToSum();

        //測試2000萬的情況
        TestLinkedList test1000 = new TestLinkedList(20000000);
        test1000.splitToSum();

        //以上三個例子,哈無例外,都是簡單的迴圈勝出。那麼parametrizedStream的效率就值得懷疑了。
        //是否因為沒有正確設置並行度,還是電腦的環境存在問題
        test1000.sumByGiver();
        test1000.count();
    }
}

以上例子並沒有測試每一個介面方法,是因為有些太簡單不值得浪費篇幅。

三、並行處理和流處理

在J8之前,如果把一個集合,以ArrayList為例子,進行並行處理,那麼必須自己來動手,過程可能是這樣的:

1.分隔集合為n個子集

2.創建n個線程,用於分別處理n個子集

3.如果需要合併處理,還需要特定註意線程的等待和合併

寫起來還是相對比較麻煩的。當然,現在藉助於ai,沒有那麼複雜。但和J8之後提供的特性相比,自然還是麻煩一些。

至於流,更不用說了,J8之前並沒有這個概念。

 

在JDK17中,可以看到Collection介面和併發以及流有關的方法:

default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
@Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

三個都是預設方法,可以直接使用

parallelStream可以提供並行流處理。

--

根據已知的一些報告和我幾次不是很嚴謹的測試,Stream和for相比並沒有什麼優勢。

由此可以得出一個不是很嚴謹的結論:

在相當大的業務場景(crud為主的信息系統)中,甚至可以說,在大部分的業務場景中,Stream其實居於下風。

stream的作用僅僅是為了節約工程師的精力和體力

 

只有數據集巨大,且cpu充足的情況下,例如千萬級別左右,並行流才會有一些可見的優勢。但是,又有多少面向

企業基別的信息系統,會在應用級別這樣瘋狂地處理千萬級別的數據,難道不怕jvm爆了嗎?

用資料庫的集合運算功能不是更好更簡單嗎?

 

四、工具類

 4.1官定工具- Collections

這是集合最重要的工具類。

全路徑:java.util.Collections

 

需要特別申明的是,Collections不僅僅會處理Colletion的子子孫孫,也會處理Map,所以不能被它的名稱騙了。

由於存在JAVADOC,且這個Colllections的成員巨多,所以不逐一列出,避免浪費篇幅。

Collections方法大體包含三類:

1.運算

例如排序(sort)、翻轉(reverse)、打亂(shuffle)、交換元素(swap)、填充元素(fill),經典聚集(min,max),集合運算等等

其中和經典集合運算有關的:

frequency -頻率

disjoint-判斷是否有交集

總之,結合Collecion自身的實現和Collections工具,要實現兩個集合的並集、交集、差集、是否包含等等都是可以的,只不過有點麻煩。

   

2.構造特定類型的對象  

   a.不可修改集合(含map)

   b.線程同步集合(含map)

   c.鎖定類型集合

   d.空集合(無元素集合)

   e.單元素集合(Singleton

  前四個都容易理解,最後一個Singleton有點迷惑,就是為了返回只有一個成員的集合?

3.其它雜項

諸如複製、替換等等。

不過沒有提供深度複製的方法。

4.2阿帕奇集合工具(CollectionUtils

相比java自帶的集合工具,阿帕奇的工具主要集中在以下幾個用途:

1.集合運算

這個比java官方的強大多了,所以還是用這個把。看看都有什麼:

union(並集),intersection(交集),disjunction(!交集,或者獨立並集),substract(移除子集),containAny(是否有交集)

isSubCollection(是否子集),isEqualCollection(是否相等),retainAll(交集),以及其它。

註意:retainAll和intersection都可以用於獲取交集,但是二者還是有明顯區別的,後者(intersection)會給出不重覆的結果,而前者(retainAll)會給出重覆的結果

以下是關於這些本人重視的集合運算方法的示例:

public void testApacheCollectionUtils(){
        List<Integer>  me = Arrays.asList(90, 80, 70,90,92,88);
        List<Integer>  mother = Arrays.asList(90, 80, 70,90,92,88);
        List<Integer>  auntScore = Arrays.asList(90, 80, 70,90,92,88);
        List<Integer>  fatherScore = Arrays.asList(99, 81, 71,90,98,88);
        List<Integer>  趙雲 = Arrays.asList(90,80);
        List<Integer>  崔顥 = Arrays.asList(77);

        List<Integer>  myNewScore = (List<Integer>) CollectionUtils.union(me, 趙雲);
        System.out.println("我和趙雲的合併∪="+myNewScore);
        List<Integer>  myIntersectionScore = (List<Integer>) CollectionUtils.intersection(me, fatherScore);
        System.out.println("我和爸爸交集="+myIntersectionScore);
        //差集
        List<Integer>  myDifferenceScore = (List<Integer>) CollectionUtils.subtract(me, fatherScore);
        System.out.println("我和爸爸的差集="+myDifferenceScore);
        //非公共部分
        List<Integer>  myDisJointScore = (List<Integer>) CollectionUtils.disjunction(me, fatherScore);
        System.out.println("我和爸爸的非公共部分="+myDisJointScore);

        //我和爸爸是否有交集
        if(!CollectionUtils.containsAny(me, fatherScore)) {
            System.out.println("我和爸爸沒有交集");
        } else {
            System.out.println("我和爸爸有交集");
        }
        //我和崔顥是否有交集
        if(!CollectionUtils.containsAny(me, 崔顥)) {
            System.out.println("我和崔顥沒有交集");
        } else {
            System.out.println("我和崔顥有交集");
        }
        //我和趙雲的交集
        List<Integer>  myIntersectionScore2 = (List<Integer>) CollectionUtils.retainAll(me, 趙雲);
        System.out.println("我和趙雲的交集(retainAll)="+myIntersectionScore2);
        System.out.println("我和趙雲的交集(inter)="+ CollectionUtils.intersection(me, 趙雲));
        //和崔顥Score的交集
        List<Integer>  myIntersectionScore3 = (List<Integer>) CollectionUtils.retainAll(崔顥, 趙雲);
        System.out.println("和崔顥的交集="+myIntersectionScore3);

        //趙雲是否是me的子集
        if(CollectionUtils.isSubCollection(趙雲, me)) {
            System.out.println("趙雲是me的子集");
        } else {
            System.out.println("趙雲不是me的子集");
        }
        //崔顥Score是否是me的子集
        if(CollectionUtils.isSubCollection(崔顥, me)) {
            System.out.println("崔顥是me的子集");
        } else {
            System.out.println("崔顥不是me的子集");
        }

        //媽媽和阿姨是否一致
        if(CollectionUtils.isEqualCollection(mother, auntScore)) {
            System.out.println("媽媽和阿姨一致");
        } else {
            System.out.println("媽媽和阿姨不一致");
        }
    }

 

輸出結果:

我和趙雲的合併∪=[80, 70, 88, 90, 90, 92]
我和爸爸交集=[88, 90]
我和爸爸的差集=[80, 70, 90, 92]
我和爸爸的非公共部分=[80, 81, 98, 99, 70, 71, 90, 92]
我和爸爸有交集
我和崔顥沒有交集
我和趙雲的交集(retainAll)=[90, 80, 90]
我和趙雲的交集(inter)=[80, 90]
和崔顥的交集=[]
趙雲是me的子集
崔顥不是me的子集
媽媽和阿姨一致

 

 

2.元素處理

find,filter,exists,countMatches、select、collect、get、

3.構造特定類型集合

  • synchronizedCollection
  • unmodifiableCollection
  • predicatedCollection
  • typedCollection

需要註意的是,這裡的幾個方法,個人傾向於少用,儘量用java標準的Collections。

4.雜項

isEmpty,isNotEmpty,cardinality...

4.3其它雜項工具

現在工具有點泛濫了。這是因為複製工具代碼已經很簡單,再加上實在有一些個性化的需要,所以越做越多。

Spring有,JSON有,mybatis有...

這些已經泛濫的就不提了,它們主要用於一些極其個性化的,或者自認為更有效率更安全(存疑)。

4.4 小結

為安全起見,我個人都是儘量用官方的Collections和阿帕奇的CollectionUtils。

從工程角度出發,儘量少依賴也是一個大體正確的選擇。

 

其它的不是萬不得已不要用。當然各個組織也完全可以自行創建工具。

只不過,這兩個工具集已包含絕大部分集合有關的操作,再結合Stream和Colllection自有的功能,應該很夠用了。

五、CRUD和集合

編寫crud的時候,我們可能會常常使用以下幾種基於jdbc的方式創建集合:

1.使用基於jdbc的orm,例如典型的mybatis

2.基於sping的jdbcTemplate

實際是對原生jdbc的封裝

3.基於原生jdbc

現在已經很少人用jpa來訪問處理數據。

在絕大部分CRUD項目中,一般都用mytabis之類的Orm

 

所以,這裡主要討論mybatis(或者類似的框架工具即可)。

當返回集合的時候,mytais支持返回List(ArrayList),Set ,對這兩個類型的支持是很友好的。

以下是方法(org.apache.ibatis.jdbc.SqlRunner#getResults,selectAll)的部分



public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
try (PreparedStatement ps = connection.prepareStatement(sql)) {
setParameters(ps, args);
try (ResultSet rs = ps.executeQuery()) {
return getResults(rs);
}
}
}

private
List<Map<String, Object>> getResults(ResultSet rs) throws SQLException { List<Map<String, Object>> list = new ArrayList<>(); ..... while (rs.next()) { Map<String, Object> row = new HashMap<>(); for (int i = 0, n = columns.size(); i < n; i++) { String name = columns.get(i); TypeHandler<?> handler = typeHandlers.get(i); row.put(name.toUpperCase(Locale.ENGLISH), handler.getResult(rs, name)); } list.add(row); } return list; }

 

可以看出,在mybatis的底層是用ArrayList來承接原生數據集的結果的。用ArrayList是因為一個性能較好,另外一個是因為集合的數量不可測的緣故。

在不考慮極端性能的要求下,用mybatis還是不錯的,因為它提供了主要的類型轉換和spring的集成。

很少有人考慮使用LinkedList等其它集合來承接數據即可。

由於List實現了Collection介面,所以可以使用mybatis在獲得List之後,再做流處理。

六、適用場景和挑戰

集合的子孫巨多,有不同的業務場景對應,以最常見的來說:

ArrayList -- crud,隨機訪問性能高。但crud很少隨機訪問某個,一般都丟到前端處理了。

   如前,Colletions提供了大量構建特定用途的集合的方法,可以讓動態列表用於線程安全等場景。

LinkedList -- 雙向鏈表用途很廣,一般不CRUD的時候,常常會考慮用它,它的優缺點:

   頻繁進行插入和刪除更高效;可以用作用作棧(Stack)和隊列(Queue);保持元素插入順序的場景實現雙向遍歷

  缺點:隨機訪問慢

Set   -大量的非crud的,需要保持元素唯一的情況

Queue -隊列,主要用於需要堆棧操作的情況

再結合線程同步、不可修改、指定類型等等,可以細分為更多的子場景。

由於子孫太多,如果個人對每個類型的優缺點不是太明白,那麼至少要知道大類的適用場景,然後再查看javaDoc/ai即可。

 

6.1 挑戰-線程安全

如果,java工具Colleections已經提供了適用於大部分業務場景的併發集合對象,以便線上程操作情況下,能夠保證安全。

以非常典型的java.util.Collections.synchronizedList(List<T>)為例子,下麵是相關代碼:

 public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        @java.io.Serial
        private static final long serialVersionUID = -7754090372962971524L;

        @SuppressWarnings("serial") // Conditionally serializable
        final List<E> list;

        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return list.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return list.hashCode();}
        }

        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }
        public E remove(int index) {
            synchronized (mutex) {return list.remove(index);}
        }

        public int indexOf(Object o) {
            synchronized (mutex) {return list.indexOf(o);}
        }
        public int lastIndexOf(Object o) {
            synchronized (mutex) {return list.lastIndexOf(o);}
        }

        public boolean addAll(int index, Collection<? extends E> c) {
            synchronized (mutex) {return list.addAll(index, c);}
        }

        public ListIterator<E> listIterator() {
            return list.listIterator(); // Must be manually synched by user
        }

        public ListIterator<E> listIterator(int index) {
            return list.listIterator(index); // Must be manually synched by user
        }

        public List<E> subList(int fromIndex, int toIndex) {
            synchronized (mutex) {
                return new SynchronizedList<>(list.subList(fromIndex, toIndex),
                                            mutex);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            synchronized (mutex) {list.replaceAll(operator);}
        }
        @Override
        public void sort(Comparator<? super E> c) {
            synchronized (mutex) {list.sort(c);}
        }   
        @java.io.Serial
        private Object readResolve() {
            return (list instanceof RandomAccess
                    ? new SynchronizedRandomAccessList<>(list)
                    : this);
        }
    }

 

從代碼可以看出,這個SynchronizedList對大部分的集合操作都使用關鍵字synchronized,包括基本的get,add,indexOf...

但是需要註意,並不是所有的操作都是上同步鎖,例如獲得迭代器(iterator())就不會。具體哪些不會,需要工程師自己去閱讀代碼。

實現單個jvm內的線程安全問題不大,工程師主要的調整來自於性能要求,需要謹慎地分辨這些上鎖的代價是否過於大,大到不如直接使用串列的

方式進行處理。

通常而言,如果鎖內操作很短,而鎖外的操作相對長的多,那麼還是值得那樣進行操作的。

七、小結

1.集合的子孫比較多,建議先認識一遍,這樣有助於開發,不要浪費自己的時間

2.應付一般的CRUD,依靠JAVA和阿帕奇的已經基本夠了用了。

如果實在不夠可以自己額外編寫工具集,不推薦採用三方的工具集(存在安全和更新問題)當然類似阿帕奇這樣的可以例外。

如果是開發產品,更不推薦採用非知名的小組織/個人的工具包。

3.需要註意線程安全情況下的用法,這個有賴於個人實踐之後的體驗,雖然JAVADOC有一些說明,但是不夠。

4.使用ai輔助編寫代碼的時候,應該有適當的辨別能力,避免每個集合都是stream()之後再操作

最簡單的,例如 list.filter(),沒有必要list.stream().filter,除非filter後還掛著其它操作。sort()也是類似。

不能太機械。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 可擴展資料庫基礎 1.1. 絕大多數應用程式都是基於關係資料庫技術構建的 1.2. 資料庫必須存儲大量數據,為分佈在全球的客戶端提供快速的查詢響應,並且全天候可用 1.3. NoSQL資料庫採用簡單的數據模型,可以複製和分區以支持海量數據集和請求量 1.4. Facebook以使用MySQL管 ...
  • 1. 微服務 1.1. 微服務的起源可以追溯到2008年左右 1.1.1. 在Amazon,​“兩個比薩原則”成為一個單系統組件團隊規模的管理原則,後來被稱為微服務 1.1.1.1. 每個內部團隊都應該小到可以用兩個比薩餅喂飽 1.1.2. Amazon和Netflix是微服務架構的先驅,他們在20 ...
  • 1. 無伺服器的魅力 1.1. 對於某些應用程式,負載在工作時間可能很高,而在非工作時間可能很低或者不存在 1.2. 其他應用程式後臺流量可能在99%的時間里都很低 1.2.1. 一旦到了一些大型節目的門票發佈時間,負載需求可能會在數小時內飆升至平均水平的10000倍,然後回落至正常水平 1.3.  ...
  • 1. 非同步消息傳遞 1.1. 通信是分散式系統的基礎,也是架構師需要納入其系統設計的主要問題 1.2. 客戶端發送請求並等待伺服器響應 1.2.1. 這就是大多數分散式通信的設計方式,因為客戶端需要得到即時響應後才能繼續 1.2.2. 並非所有系統都有這個要求 1.3. 使用非同步通信的方式,客戶端( ...
  • 在Python中,協議(Protocol)和介面(Interface)是用於定義類和對象之間交互的一種方式,特別是在實現多態性和代碼可重用性時,協議是一種抽象概念,描述了對象所需實現的方法和屬性,而不關心具體的類或實現。 ...
  • JMM記憶體模型 定義 java記憶體模型(即 java Memory Model,簡稱JMM),不存在的東西,是一個概念,約定 主要分成兩部分來看,一部分叫做主記憶體,另一部分叫做工作記憶體。 java當中的共用變數;都放在主記憶體當中,如類的成員變數(實例變數),還有靜態的成員變數(類變數),都是存儲在主 ...
  • 1. 什麼是gprof? 2. gprof的用法 2.1. 編譯程式 2.2. 運行程式 2.3. 生成分析報告 2.4. gprof常用參數說明 2.5. 分析報告解讀 2.5.1. Flat profile 各個欄位的含義 2.5.2. Call graph 各個欄位的含義 3. Demo演示 ...
  • 一、基礎階段 Python 基礎語法 學習內容:變數、數據類型(如整數、浮點數、字元串、布爾等)、運算符、控制流語句(如 if-else、for、while 等)、函數定義與調用等。 學習網站及網址: 廖雪峰的 Python 入門教程:https://www.liaoxuefeng.com/wiki ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...