亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java并發(fā)程序刺客之假共享的原理及復(fù)現(xiàn)

 更新時(shí)間:2022年08月04日 14:55:34   作者:一無(wú)是處的研究僧  
前段時(shí)間在各種社交平臺(tái)“雪糕刺客”這個(gè)詞比較火,而在并發(fā)程序中也有一個(gè)刺客,那就是假共享。本文將通過(guò)示例詳細(xì)講解假共享的原理及復(fù)現(xiàn),需要的可以參考一下

前言

前段時(shí)間在各種社交平臺(tái)“雪糕刺客”這個(gè)詞比較火,簡(jiǎn)單的來(lái)說(shuō)就是雪糕的價(jià)格非常高!其實(shí)在并發(fā)程序當(dāng)中也有一個(gè)刺客,如果在寫并發(fā)程序的時(shí)候不注意不小心,這個(gè)刺客很可能會(huì)拖累我們的并發(fā)程序,讓我們并發(fā)程序執(zhí)行的效率變低,讓并發(fā)程序付出很大的代價(jià),這和“雪糕刺客”當(dāng)中的“刺客”的含義是一致的。這個(gè)并發(fā)程序當(dāng)中的刺客就是——假共享(False Sharing)。

假共享(False Sharing)

緩存行

當(dāng)CPU從更慢級(jí)別的緩存讀取數(shù)據(jù)的時(shí)候(三級(jí)Cache會(huì)從內(nèi)存當(dāng)中讀取數(shù)據(jù),二級(jí)緩存會(huì)從三級(jí)緩存當(dāng)中讀取數(shù)據(jù),一級(jí)緩存會(huì)從二級(jí)緩存當(dāng)中讀取數(shù)據(jù),緩存級(jí)別越低執(zhí)行速度越快),CPU并不是一個(gè)字節(jié)一個(gè)字節(jié)的讀取的,而是一次會(huì)讀取一塊數(shù)據(jù),然后將這個(gè)數(shù)據(jù)緩存到CPU當(dāng)中,而這一塊數(shù)據(jù)就叫做緩存行。有一種緩存行的大小就是64字節(jié),那么我們?yōu)槭裁磿?huì)做這種優(yōu)化呢?這是因?yàn)?strong>局部性原理,所謂局部性原理簡(jiǎn)單說(shuō)來(lái)就是,當(dāng)時(shí)使用一個(gè)數(shù)據(jù)的時(shí)候,它附近的數(shù)據(jù)在未來(lái)的一段時(shí)間你也很可能用到,比如說(shuō)我們遍歷數(shù)組,我們通常從前往后進(jìn)行遍歷,比如我們數(shù)組當(dāng)中的數(shù)據(jù)大小是8個(gè)字節(jié),如果我們的緩存行是64個(gè)字節(jié)的話,那么一個(gè)緩存行就可以緩存8個(gè)數(shù)據(jù),那么我們?cè)诒闅v第一個(gè)數(shù)據(jù)的時(shí)候?qū)⑦@8個(gè)數(shù)據(jù)加載進(jìn)入緩存行,那么我們?cè)诒闅v未來(lái)7個(gè)數(shù)據(jù)的時(shí)候都不需要再?gòu)膬?nèi)存當(dāng)中拿數(shù)據(jù),直接從緩存當(dāng)中拿就行,這就可以節(jié)約程序執(zhí)行的時(shí)間。

假共享

當(dāng)兩個(gè)線程在CPU上兩個(gè)不同的核心上執(zhí)行代碼的時(shí)候,如果這兩個(gè)線程使用了同一個(gè)緩存行C,而且對(duì)這個(gè)緩存行當(dāng)中兩個(gè)不同的變量進(jìn)行寫操作,比如線程A對(duì)變量a進(jìn)行寫操作,線程B對(duì)變量b進(jìn)行寫操作。而由于緩存一致性(Cache coherence)協(xié)議的存在,如果其中A線程對(duì)緩存行C中變量a進(jìn)行了寫操作的話,為了保證各個(gè)CPU核心的數(shù)據(jù)一致(也就是說(shuō)兩個(gè)CPU核心看到了a的值是一樣的,因?yàn)閍的值已經(jīng)發(fā)生變化了,需要讓另外的CPU核心知道,不然另外的CPU核心使用的就是舊的值,那么程序結(jié)果就不對(duì)了),其他核心的這個(gè)緩存行就會(huì)失效,如果他還想使用這個(gè)緩存行的話就需要重新三級(jí)Cache加載,如果數(shù)據(jù)不存在三級(jí)Cache當(dāng)中的話,就會(huì)從內(nèi)存當(dāng)中加載,而這個(gè)重新加載的過(guò)程就會(huì)很拖累程序的執(zhí)行效率,而事實(shí)上線程A寫的是變量a,線程B寫的是變量b,他們并沒(méi)有真正的有共享的數(shù)據(jù),只是他們需要的數(shù)據(jù)在同一個(gè)緩存行當(dāng)中,因此稱這種現(xiàn)象叫做假共享(False Sharing)

上面我們談到了,當(dāng)緩存行失效的時(shí)候會(huì)從三級(jí)Cache或者內(nèi)存當(dāng)中加載,而多個(gè)不同的CPU核心是共享三級(jí)Cache的(上圖當(dāng)中已經(jīng)顯示出來(lái)了),其中一個(gè)CPU核心更新了數(shù)據(jù),會(huì)把數(shù)據(jù)刷新到三級(jí)Cache或者內(nèi)存當(dāng)中,因此這個(gè)時(shí)候其他的CPU核心去加載數(shù)據(jù)的時(shí)候就是新值了。

上面談到的關(guān)于CPU的緩存一致性(Cache coherence)的內(nèi)容還是比較少的,如果你想深入了解緩存一致性(Cache coherence)和緩存一致性協(xié)議可以仔細(xì)去看這篇文章。

我們?cè)賮?lái)舉一個(gè)更加具體的例子:

假設(shè)在內(nèi)存當(dāng)中,變量a和變量b都占四個(gè)字節(jié),而且他們的內(nèi)存地址是連續(xù)且相鄰的,現(xiàn)在有兩個(gè)線程A和B,線程A要不斷的對(duì)變量a進(jìn)行+1操作,線程B需要不斷的對(duì)變量進(jìn)行+1操作,現(xiàn)在這個(gè)兩個(gè)數(shù)據(jù)所在的緩存行已經(jīng)被緩存到三級(jí)緩存了。

  • 線程A從三級(jí)緩存當(dāng)中將數(shù)據(jù)加載到二級(jí)緩存和一級(jí)緩存然后在CPU- Core0當(dāng)中執(zhí)行代碼,線程B從三級(jí)緩存將數(shù)據(jù)加載到二級(jí)緩存和一級(jí)緩存然后在CPU- Core1當(dāng)中執(zhí)行代碼。
  • 線程A不斷的執(zhí)行a += 1,因?yàn)榫€程B緩存的緩存行當(dāng)中包含數(shù)據(jù)a,線程A在修改a的值之后,就會(huì)在總線上發(fā)送消息,讓其他處理器當(dāng)中含有變量a的緩存行失效,在處理器將緩存行失效之后,就會(huì)在總線上發(fā)送消息,表示緩存行已經(jīng)失效,線程A所在的CPU- Core0收到消息之后將更新后的數(shù)據(jù)刷新到三級(jí)Cache。
  • 這個(gè)時(shí)候線程B所在的CPU-Core1當(dāng)中含有a的緩存行已經(jīng)失效,因?yàn)樽兞縝和變量a在同一個(gè)緩存行,現(xiàn)在線程B想對(duì)變量b進(jìn)行加一操作,但是在一級(jí)和二級(jí)緩存當(dāng)中已經(jīng)沒(méi)有了,它需要三級(jí)緩存當(dāng)中加載這個(gè)緩存行,如果三級(jí)緩存當(dāng)中沒(méi)有就需要去內(nèi)存當(dāng)中加載。
  • 仔細(xì)分析上面的過(guò)程你就會(huì)發(fā)現(xiàn)線程B并沒(méi)有對(duì)變量a有什么操作,但是它需要的緩存行就失效了,雖然和線程B共享需要同一個(gè)內(nèi)容的緩存行,但是他們之間并沒(méi)有真正共享數(shù)據(jù),所以這種現(xiàn)象叫做假共享。

Java代碼復(fù)現(xiàn)假共享

復(fù)現(xiàn)假共享

下面是兩個(gè)線程不斷對(duì)兩個(gè)變量執(zhí)行++操作的代碼:

class Data {
  public volatile long a;
  public volatile long b;
}
 
public class FalseSharing {
  public static void main(String[] args) throws InterruptedException {
    Data data = new Data();
    long start = System.currentTimeMillis();
    Thread A = new Thread(() -> {
      for (int i = 0;  i < 500_000_000; i++) {
        data.a += 1;
      }
    }, "A");
 
    Thread B = new Thread(() -> {
      for (int i = 0;  i < 500_000_000; i++) {
        data.b += 1;
      }
    }, "B");
    A.start();
    B.start();
    A.join();
    B.join();
    long end = System.currentTimeMillis();
    System.out.println("花費(fèi)時(shí)間為:" + (end - start));
    System.out.println(data.a);
    System.out.println(data.b);
  }
}

上面的代碼比較簡(jiǎn)單,這里就不進(jìn)行說(shuō)明了,上面的代碼在我的筆記本上的執(zhí)行時(shí)間大約是17秒。

上面的代碼變量a和變量b在內(nèi)存當(dāng)中的位置是相鄰的,他們?cè)诒籆PU加載之后會(huì)在同一個(gè)緩存行當(dāng)中,因此會(huì)存在假共享的問(wèn)題,程序的執(zhí)行時(shí)間會(huì)變長(zhǎng)。

下面的代碼是優(yōu)化過(guò)后的代碼,在變量a前面和后面分別加入56個(gè)字節(jié)的數(shù)據(jù),再加上a的8個(gè)字節(jié)(long類型是8個(gè)字節(jié)),這樣a前后加上a的數(shù)據(jù)有64個(gè)字節(jié),而現(xiàn)在主流的緩存行是64個(gè)字節(jié),夠一個(gè)緩存行的大小,因?yàn)閿?shù)據(jù)a和數(shù)據(jù)b就不會(huì)在同一個(gè)緩存行當(dāng)中,因此就不會(huì)存在假共享的問(wèn)題了。而下面的代碼在我筆記本當(dāng)中執(zhí)行的時(shí)間大約為5秒。這就足以看出假共享會(huì)對(duì)程序的執(zhí)行帶來(lái)多大影響了。

class Data {
  public volatile long a1, a2, a3, a4, a5, a6, a7;
  public volatile long a;
  public volatile long b1, b2, b3, b4, b5, b6, b7;
  public volatile long b;
}
 
public class FalseSharing {
  public static void main(String[] args) throws InterruptedException {
    Data data = new Data();
    long start = System.currentTimeMillis();
    Thread A = new Thread(() -> {
      for (int i = 0;  i < 500_000_000; i++) {
        data.a += 1;
      }
    }, "A");
 
    Thread B = new Thread(() -> {
      for (int i = 0;  i < 500_000_000; i++) {
        data.b += 1;
      }
    }, "B");
    A.start();
    B.start();
    A.join();
    B.join();
    long end = System.currentTimeMillis();
    System.out.println("花費(fèi)時(shí)間為:" + (end - start));
    System.out.println(data.a);
    System.out.println(data.b);
  }
}

JDK解決假共享

為了解決假共享的問(wèn)題,JDK為我們提供了一個(gè)注解@Contened解決假共享的問(wèn)題。

import sun.misc.Contended;
 
class Data {
//  public volatile long a1, a2, a3, a4, a5, a6, a7;
  @Contended
  public volatile long a;
//  public volatile long b1, b2, b3, b4, b5, b6, b7;
  @Contended
  public volatile long b;
}
 
public class FalseSharing {
  public static void main(String[] args) throws InterruptedException {
    Data data = new Data();
 
    long start = System.currentTimeMillis();
    Thread A = new Thread(() -> {
      for (long i = 0;  i < 500_000_000; i++) {
        data.a += 1;
      }
    }, "A");
 
    Thread B = new Thread(() -> {
      for (long i = 0;  i < 500_000_000; i++) {
        data.b += 1;
      }
    }, "B");
    A.start();
    B.start();
    A.join();
    B.join();
    long end = System.currentTimeMillis();
    System.out.println("花費(fèi)時(shí)間為:" + (end - start));
    System.out.println(data.a);
    System.out.println(data.b);
  }
}

上面代碼的執(zhí)行時(shí)間也是5秒左右,和之前我們自己在變量的左右兩邊插入變量的效果是一樣的,但是JDK提供的這個(gè)接口和我們自己實(shí)現(xiàn)的還是有所區(qū)別的。(注意:上面的代碼是在JDK1.8下執(zhí)行的,如果要想@Contended注解生效,你還需要在JVM參數(shù)上加入-XX:-RestrictContended,這樣上面的代碼才能生效否則是不能夠生效的)

  • 在我們自己解決假共享的代碼當(dāng)中,是在變量a的左右兩邊加入56個(gè)字節(jié)的其他變量,讓他和變量b不在同一個(gè)緩存行當(dāng)中。
  • 在JDK給我們提供的注解@Contended,是在被加注解的字段的右邊加入一定數(shù)量的空字節(jié),默認(rèn)加入128空字節(jié),那么變量a和變量b之間的內(nèi)存地址大一點(diǎn),最終不在同一個(gè)緩存行當(dāng)中。這個(gè)字節(jié)數(shù)量可以使用JVM參數(shù)-XX:ContendedPaddingWidth=64,進(jìn)行控制,比如這個(gè)是64個(gè)字節(jié)。
  • 除此之外@Contended注解還能夠?qū)⒆兞窟M(jìn)行分組:
class Data {
  @Contended("a")
  public volatile long a;
  
  @Contended("bc")
  public volatile long b;
  @Contended("bc")
  public volatile long c;
}

在解析注解的時(shí)候會(huì)讓同一組的變量在內(nèi)存當(dāng)中的位置相鄰,不同的組之間會(huì)有一定數(shù)量的空字節(jié),配置方式還是跟上面一樣,默認(rèn)每組之間空字節(jié)的數(shù)量為128。

比如上面的變量在內(nèi)存當(dāng)中的邏輯布局詳細(xì)布局如下:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           20 0a 06 00 (00100000 00001010 00000110 00000000) (395808)
     12   132        (alignment/padding gap)                  
    144     8   long Data.a                                    0
    152   128        (alignment/padding gap)                  
    280     8   long Data.b                                    0
    288     8   long Data.c                                    0
    296   128        (loss due to the next object alignment)
Instance size: 424 bytes
Space losses: 260 bytes internal + 128 bytes external = 388 bytes total

上面的內(nèi)容是通過(guò)下面代碼打印的,你只要在pom文件當(dāng)中引入包jol即可:

從更低層次C語(yǔ)言看假共享

前面我們是使用Java語(yǔ)言去驗(yàn)證假共享,在本小節(jié)當(dāng)中我們通過(guò)一個(gè)C語(yǔ)言的多線程程序(使用pthread)去驗(yàn)證假共享。(下面的代碼在類Unix系統(tǒng)都可以執(zhí)行)

#include <stdio.h>
#include <pthread.h>
#include <time.h>
 
#define CHOOSE // 這里定義了 CHOOSE 如果不想定義CHOOSE 則將這一行注釋掉即可
 
// 定義一個(gè)全局變量
int data[1000];
 
void* add(void* flag) {
  // 這個(gè)函數(shù)的作用就是不斷的往 data 當(dāng)中的某個(gè)數(shù)據(jù)進(jìn)行加一操作
  int idx = *((int *)flag);
  for (long i = 0; i < 10000000000; ++i) {
    data[idx]++;
  }
}
 
int main() {
  pthread_t a, b;
#ifdef CHOOSE // 如果定義了 CHOOSE 則執(zhí)行下面的代碼 讓兩個(gè)線程操作的變量隔得遠(yuǎn)一點(diǎn) 讓他們不在同一個(gè)緩存行當(dāng)中
  int flag_a = 0;
  int flag_b = 100;
  printf("遠(yuǎn)離\n");
#else // 如果沒(méi)有定義 讓他們隔得近一點(diǎn) 也就是說(shuō)讓他們?cè)谕粋€(gè)緩存行當(dāng)中
  int flag_a = 0;
  int flag_b = 1;
  printf("臨近\n");
#endif
  pthread_create(&a, NULL, add, &flag_a); // 創(chuàng)建線程a 執(zhí)行函數(shù) add 傳遞參數(shù) flag_a 并且啟動(dòng)
  pthread_create(&b, NULL, add, &flag_b); // 創(chuàng)建線程b 執(zhí)行函數(shù) add 傳遞參數(shù) flag_b 并且啟動(dòng)
  long start = time(NULL);
  pthread_join(a, NULL); // 主線程等待線程a執(zhí)行完成
  pthread_join(b, NULL); // 主線程等待線程b執(zhí)行完成
  long end = time(NULL);
  printf("data[0] = %d\t data[1] = %d\n", data[0], data[1]);
  printf("cost time = %ld\n", (end - start));
  return 0;
}

上面代碼的輸出結(jié)果如下圖所示:

我們首先來(lái)解釋一下上面time命令的輸出:

  • readl:這個(gè)表示真實(shí)世界當(dāng)中的墻鐘時(shí)間,就是表示這個(gè)程序執(zhí)行所花費(fèi)的時(shí)間,這個(gè)秒單位和我們平常說(shuō)的秒是一樣的。
  • user:這個(gè)表示程序在用戶態(tài)執(zhí)行的CPU時(shí)間,CPU時(shí)間和真實(shí)時(shí)間是不一樣的,這里需要注意區(qū)分,這里的秒和我們平常的秒是不一樣的。
  • sys:這個(gè)表示程序在內(nèi)核態(tài)執(zhí)行所花費(fèi)的CPU時(shí)間。

從上面程序的輸出結(jié)果我們可以很明顯的看出來(lái)當(dāng)操作的兩個(gè)整型變量相隔距離遠(yuǎn)的時(shí)候,也就是不在同一個(gè)緩存行的時(shí)候,程序執(zhí)行的速度是比數(shù)據(jù)隔得近在同一個(gè)緩存行的時(shí)候快得多,這也從側(cè)面顯示了假共享很大程度的降低了程序執(zhí)行的效率。

總結(jié)

在本篇文章當(dāng)中主要討論了以下內(nèi)容:

  • 當(dāng)多個(gè)線程操作同一個(gè)緩存行當(dāng)中的多個(gè)不同的變量時(shí),雖然他們事實(shí)上沒(méi)有對(duì)數(shù)據(jù)進(jìn)行共享,但是他們對(duì)同一個(gè)緩存行當(dāng)中的數(shù)據(jù)進(jìn)行修改,而由于緩存一致性協(xié)議的存在會(huì)導(dǎo)致程序執(zhí)行的效率降低,這種現(xiàn)象叫做假共享
  • 在Java程序當(dāng)中我們?nèi)绻胱尪鄠€(gè)變量不在同一個(gè)緩存行當(dāng)中的話,我們可以在變量的旁邊通過(guò)增加其他變量的方式讓多個(gè)不同的變量不在同一個(gè)緩存行。
  • JDK也為我們提供了Contended注解可以在字段的后面通過(guò)增加空字節(jié)的方式讓多個(gè)數(shù)據(jù)不在同一個(gè)緩存行,而且你需要在JVM參數(shù)當(dāng)中加入-XX:-RestrictContended,同時(shí)你可以通過(guò)JVM參數(shù)-XX:ContendedPaddingWidth=64調(diào)整空字節(jié)的數(shù)目。JDK8之后注解Contended在JDK當(dāng)中的位置有所變化,大家可以查詢一下。
  • 我們也是用了C語(yǔ)言的API去測(cè)試了假共享,事實(shí)上在Java虛擬機(jī)當(dāng)中底層的線程也是通過(guò)調(diào)用pthread_create進(jìn)行創(chuàng)建的。

到此這篇關(guān)于Java并發(fā)程序刺客之假共享的原理及復(fù)現(xiàn)的文章就介紹到這了,更多相關(guān)Java并發(fā) 假共享內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot中整合MyBatis-Plus的方法示例

    SpringBoot中整合MyBatis-Plus的方法示例

    這篇文章主要介紹了SpringBoot中整合MyBatis-Plus的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    這篇文章主要介紹了關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 解決Mybatis中mapper.xml文件update,delete及insert返回值問(wèn)題

    解決Mybatis中mapper.xml文件update,delete及insert返回值問(wèn)題

    這篇文章主要介紹了解決Mybatis中mapper.xml文件update,delete及insert返回值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-11-11
  • JavaWeb文件上傳下載功能示例解析

    JavaWeb文件上傳下載功能示例解析

    這篇文章主要介紹了JavaWeb中的文件上傳和下載功能的實(shí)現(xiàn),文件上傳和下載功能是非常常用的功能,需要的朋友可以參考下
    2016-06-06
  • SpringBoot超詳細(xì)講解yaml配置文件

    SpringBoot超詳細(xì)講解yaml配置文件

    這篇文章主要介紹了SpringBoot中的yaml配置文件問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • 舉例講解Java的RTTI運(yùn)行時(shí)類型識(shí)別機(jī)制

    舉例講解Java的RTTI運(yùn)行時(shí)類型識(shí)別機(jī)制

    這篇文章主要介紹了Java的RTTI運(yùn)行時(shí)類型識(shí)別機(jī)制,包括泛化的Class引用以及類型檢查instanceof等知識(shí)點(diǎn),需要的朋友可以參考下
    2016-05-05
  • SpringBoot中配置Web靜態(tài)資源路徑的方法

    SpringBoot中配置Web靜態(tài)資源路徑的方法

    這篇文章主要介紹了SpringBoot中配置Web靜態(tài)資源路徑的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java8中的默認(rèn)方法(面試者必看)

    Java8中的默認(rèn)方法(面試者必看)

    這篇文章主要介紹了Java8中的默認(rèn)方法(面試者必看),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • java數(shù)據(jù)結(jié)構(gòu)與算法之簡(jiǎn)單選擇排序詳解

    java數(shù)據(jù)結(jié)構(gòu)與算法之簡(jiǎn)單選擇排序詳解

    這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之簡(jiǎn)單選擇排序,結(jié)合實(shí)例形式分析了選擇排序的原理、實(shí)現(xiàn)方法與相關(guān)操作技巧,需要的朋友可以參考下
    2017-05-05
  • Java將網(wǎng)絡(luò)圖片轉(zhuǎn)成輸入流以及將url轉(zhuǎn)成InputStream問(wèn)題

    Java將網(wǎng)絡(luò)圖片轉(zhuǎn)成輸入流以及將url轉(zhuǎn)成InputStream問(wèn)題

    這篇文章主要介紹了Java將網(wǎng)絡(luò)圖片轉(zhuǎn)成輸入流以及將url轉(zhuǎn)成InputStream問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01

最新評(píng)論