2017-05-24 28 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