2017-05-24 200 views
0

我正在加載將在我的應用程序中使用的圖標。我計劃在服務器啓動時從jar中加載它們。但是,有數百張圖片加起來只有9MB,但它仍然需要30多秒來完成這項任務。我現在正在一個單獨的線程中執行它,但它讓我懷疑在代碼中是否無效地做了某些事情。我借用了SO的代碼將信息加載到我的結構中。我將代碼放入測試類並對其進行分析。該配置文件的99%位於ImageIO.read(..)方法中。所以這絕對是瓶頸。下面是應該提供關於我如何使用ImageIO的圖片的測試類。ImageIO.read(...) - 速度很慢,有沒有更好的方法?

public class IconTest { 

/** 
* @param args the command line arguments 
* @throws java.net.URISyntaxException 
* @throws java.io.IOException 
*/ 
public static void main(String[] args) throws URISyntaxException, IOException { 
    URI uri = IconTest.class.getResource("Icons").toURI(); 
    Path myPath; 
    if (uri.getScheme().equals("jar")) { 
     FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()); 
     myPath = fileSystem.getPath("Icons/"); 
    } else { 
     myPath = Paths.get(uri); 
    } 
    IconFolder root = new IconFolder(myPath.toFile().getName()); 
    IconFolder parentFolder = root; 
    HIcon currentIcon = null; 
    IconFolder folder = null; 
    HashMap<String,IconFolder> folders = new HashMap<>(); 
    folders.put(parentFolder.getName(), parentFolder); 
    Stream<Path> walk = Files.walk(myPath, 5); 
    Iterator<Path> it = walk.iterator();it.next(); 
    while(it.hasNext()){ 
     Path path = it.next(); 
     if(path.toFile().isDirectory()){ 
      folder = new IconFolder(path.toFile().getName()); 
      folders.put(folder.getName(), folder); 
      String parentName = path.getParent().toFile().getName(); 
      parentFolder = folders.get(parentName); 
      parentFolder.addSubFolder(folder); 
      currentIcon =null; 
      System.out.println("Directory: " + path); 
     }else{ 
      URL url = path.toUri().toURL(); 

      ImageIcon icon = new ImageIcon(ImageIO.read(url)); 
      //Image image = Toolkit.getDefaultToolkit().getImage(url); 
      //ImageIcon icon = new ImageIcon(image); 
      String[] iconName; 
      iconName = path.getFileName().toString().replaceAll("_000000", "").replaceAll(".png","").split("_",2); 
      String imageName = iconName[0]; 
      String imageSize = iconName[1]; 
      if(currentIcon==null||!currentIcon.getName().equals(imageName)){ 
       currentIcon = new HIcon(imageName); 
       folder.addIcon(currentIcon); 
       currentIcon.setIcon(icon, imageSize); 
      }else{ 
       currentIcon.setIcon(icon, imageSize); 
      } 
      //System.out.println("Image: " + imageName+"-->"+imageSize); 
     } 
    } 
    System.out.println(""); 
} 
} 

任何指針都會有幫助。我看了很多SO帖子,我認爲這些帖子都指出了同樣的事情。我正在使用帶有SSD的MacBook Air,所以我認爲這將會閃電般快速。

我加了下面的輪廓結果的截圖:

Java profile in netbeans

這是設置setUseCache假後的輪廓:

Java profile after setUseCache = false

+0

我會質疑整個策略。我會加載他們,因爲你需要他們。分散痛苦。 – EJP

+2

將圖標放在一張圖像中並稍後將它們剪下可能會大大加快。從罐子裏讀取的100個圖像可能是低效的。 – pvg

+0

@pvg - 請參閱我對以下答案的評論。將它們放入zip文件,將1個文件加載到內存中然後執行我的步行和在內存中讀取? – Mark

回答

0

我終於找到了解決一些。下面是最新的配置文件:

enter image description here

這是一個非常有趣的解決方案。我決定使用執行程序,以便讀取操作可以並行化。我已經創建了一個用於執行整個任務的線程,但是將其分解爲更加細化的任務真的使這一切變得更快。

最終我能從38.9秒降到3.4秒。這是我書中的一大收穫。這對我來說很重要,因爲我希望我的服務器儘可能快地啓動,而且我絕對不想丟失39秒的啓動時間。

我選擇將其硬編碼爲5個線程,因爲我正在開發我的Mac並且它只有4個內核。我嘗試了15以上的核心+ 1它失去了效率。

下面是調整後的示例代碼:

public class IconTest { 

/** 
* @param args the command line arguments 
* @throws java.net.URISyntaxException 
* @throws java.io.IOException 
*/ 
public static void main(String[] args) throws URISyntaxException, IOException { 
    test4(); 
} 

public static void test4() throws MalformedURLException, IOException, URISyntaxException{ 
    ExecutorService service = Executors.newFixedThreadPool(5); 
    URI uri = IconTest.class.getResource("../resources/Icons").toURI(); 
    Path myPath; 
    if (uri.getScheme().equals("jar")) { 
     FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()); 
     myPath = fileSystem.getPath("Icons/"); 
    } else { 
     myPath = Paths.get(uri); 
    } 
    IconFolder root = new IconFolder(myPath.toFile().getName()); 
    IconFolder parentFolder = root; 
    HIcon currentIcon = null; 
    IconFolder folder = null; 
    HashMap<String,IconFolder> folders = new HashMap<>(); 
    folders.put(parentFolder.getName(), parentFolder); 
    Stream<Path> walk = Files.walk(myPath, 10); 
    Iterator<Path> it = walk.iterator();it.next(); 
    Info.Info("Starting loading icons...."); 
    ImageIO.setUseCache(false); 
    while(it.hasNext()){ 
     Path path = it.next(); 
     if(path.toFile().isDirectory()){ 
      folder = new IconFolder(path.toFile().getName()); 
      folders.put(folder.getName(), folder); 
      String parentName = path.getParent().toFile().getName(); 
      parentFolder = folders.get(parentName); 
      parentFolder.addSubFolder(folder); 
      currentIcon =null; 
      Info.Info("Directory: " + path); 
     }else{ 
      ImageLoadingTask task; 
      URL url = path.toUri().toURL(); 
      String[] iconName; 
      iconName = path.getFileName().toString().replaceAll("_000000", "").replaceAll(".png","").split("_",2); 
      String imageName = iconName[0]; 
      String imageSize = iconName[1]; 
      if(currentIcon==null||!currentIcon.getName().equals(imageName)){ 
       currentIcon = new HIcon(imageName); 
       folder.addIcon(currentIcon); 
       task = new ImageLoadingTask(url,currentIcon,imageSize); 
       service.submit(task); 
      }else{ 
       task = new ImageLoadingTask(url,currentIcon,imageSize); 
       service.submit(task); 
      } 
      //Info.Info("Image: " + imageName+"-->"+imageSize); 
     } 
    } 
    service.shutdown(); 
    Info.Info("Finished loading icons...."); 
} 

static public class ImageLoadingTask implements Callable<ImageIcon> { 

    private final URL url; 
    private final HIcon hIcon; 
    private final String size; 

    public ImageLoadingTask(URL url, HIcon hIcon, String size) { 
     this.url = url; 
     this.hIcon=hIcon; 
     this.size=size; 
    } 

    @Override 
    public ImageIcon call() throws Exception { 
     ImageIcon icon = new ImageIcon(ImageIO.read(url)); 
     hIcon.setIcon(icon, size); 
     return icon; 
    } 
} 
} 
+1

不錯的解決方案,請嘗試使用'Runtime.getRuntime()。availableProcessors()'而不是5,以便您的應用程序將根據機器有多少本機線程自動選擇線程數。 – Jire

+0

謝謝!是的,我在其他地方使用,我寫了這個筆記,因爲我意識到,當我正在寫答案時,我忘了從硬編碼中改變它......#懶惰哈哈。我選擇了指出它而不是修改它... haha​​ha – Mark

+0

如果你想分享你的圖像文件夾,我可以看看通過內存映射文件進一步加速它。 – Jire

0

你可以嘗試以使用ImageIO.setUseCache(false)使用基於內存的緩存而不是基於磁盤的緩存(因爲它是默認緩存)。

參考:https://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html#setUseCache(boolean)

+0

我確實做到了這一點......它的確顯着降低了時間,但我仍然超過了20秒。我有一個新的配置文件,我將發佈。 – Mark

+0

我剛剛更新了我的帖子,提出了另一個改進'getImageReaders'方法花費16秒的建議。 –

+0

oops ...請忽略我的建議,我剛剛看到您的第二張截圖,我不確定它是否與此相關...... –

0

ImageIO#read是一個實用方法,該方法不會在幕後一堆東西。

例如,如果您知道所有圖像格式相同,則可以使用ImageIO#getImageReadersByFormatName來獲取讀取一次(顯然這是啓動時間的3/4),請獲取要使用的讀取器(通常只是第一個),然後使用該閱讀器查看所有圖像,而無需再次搜索。

+0

如果你看第二個配置文件,我完全展開它。時間在readBytes方法中。 – Mark

+0

啊。那麼......我知道我會如何加快這一進程 - 將所有圖標分組爲單個圖像,並在讀取後將其剪切掉 - 但這不是我必須推薦的所有情況下的解決方案。 :) – Trejkaz