有一个题目要求:
给一个指定的文件夹,递归遍历该文件夹以及该文件夹下子文件夹,找到所有格式为jpg或者JPG的图片,把所有尺寸为800*800的并且大于300K的图片压缩。
找到700*700的图片并且大于500K的压缩。
当然,给定的这个文件夹相当的大,所以算法必须尽可能的优化。
这其实是我工作中遇到的一个任务把,我用了半天的时间写了出来,第一次跑的时候用了大约1天的时间。
师傅是不满意的,第二天陪我一起修改了我的代码,第二次跑了一遍,只用了8个小时。
而且给我的感觉不仅仅是算法上的优化,他修改完的代码就是给人一种一目了然的感觉,这就让我强烈的感觉到了一个初级开发人员和高级开发人员的差别。
各位如果有兴趣的话可以尝试写一下我上面的那个题目,也可以在此基础上添加一些特色的东西。
最后我会把我的代码也发上来和大家对比一下。
把代码相互对比一下,这就好比人写的字,一看就能看出来自己处于一个什么样的水平。200分赠送给算法最优化的那一位把。
给一个指定的文件夹,递归遍历该文件夹以及该文件夹下子文件夹,找到所有格式为jpg或者JPG的图片,把所有尺寸为800*800的并且大于300K的图片压缩。
找到700*700的图片并且大于500K的压缩。
当然,给定的这个文件夹相当的大,所以算法必须尽可能的优化。
这其实是我工作中遇到的一个任务把,我用了半天的时间写了出来,第一次跑的时候用了大约1天的时间。
师傅是不满意的,第二天陪我一起修改了我的代码,第二次跑了一遍,只用了8个小时。
而且给我的感觉不仅仅是算法上的优化,他修改完的代码就是给人一种一目了然的感觉,这就让我强烈的感觉到了一个初级开发人员和高级开发人员的差别。
各位如果有兴趣的话可以尝试写一下我上面的那个题目,也可以在此基础上添加一些特色的东西。
最后我会把我的代码也发上来和大家对比一下。
把代码相互对比一下,这就好比人写的字,一看就能看出来自己处于一个什么样的水平。200分赠送给算法最优化的那一位把。
不过感觉有点儿脱离 纯 java 实现的范畴了,另外还要求对系统命令的调用比较熟悉,
如果还要跨平台的话。。这个你懂的!压缩操作我觉得是个比较重要的地方,用 jni 调用 c 代码对符合要求的图片做压缩处理应该效率提升比较大吧还有就是递归遍历,到底是在一遍历到符合条件的图片就马上做压缩处理好,
还是先把符合条件的图片全部装起来,后面统一作压缩处理好呢?递归遍历还有水深水浅的问题么?
比如说,一般较常用的就是 File.list() 方法了,难不成在高效率的要求下还要自己写个遍历孩子节点的方法出来?
另外就是利用一些 java 底层的特性来达到提高效率的目的,这个我也是一头雾水,等高手了~还有一个比较关键的问题,获取图片的宽和高有简洁高效的方案么?
按照我自己的常用习惯来看,我一般是先读取为 BufferedImage,然后通过 getWidth(),getHeight() 得到宽高,
这样的话,有时候如果只是像了解一下图片的宽高,也得将整个图片读成 BufferedImage 对象,
感觉有些奢侈了~有什么巧妙的途径能够避开这一点么 ,
也就是说不用将图片数据全部加载到内存也能高效的获得图片宽高?权且抛砖引玉,坐等大婶发飙~
ImageUtilTest.java
package org.bruce.java.guru.performance;/**
* @author Bruce Yang
* 尝试以高效的方式获取图片的尺寸~
*
* 程序输出:
* ===========
* viaImageIO:
* 图片的宽度和高度分别为:3200 2000
* 耗费的时间为:1.08 秒
*
* ===============
* viaImageReader:
* 图片的宽度和高度分别为:3200 2000
* 耗费的时间为:0.0080 秒
*/
public class ImageUtilTest { /**
* @param args
*/
public static void main(String args[]) {
String imgPath = "/org/bruce/java/guru/res/lion.jpg";
viaImageIO(imgPath);
viaImageReader(imgPath);
}
/**
* ImageReader 用来获取图片的尺寸非常高效!!
*/
public static void viaImageReader(String imgPath) {
System.out.println("===============\nviaImageReader:");
long l0 = System.currentTimeMillis();
int[] imgSize = ImageUtil.getSize(imgPath);
System.out.println("图片的宽度和高度分别为:" + imgSize[0] + " " + imgSize[1]);
long l1 = System.currentTimeMillis();
System.out.println("耗费的时间为:" + (double)(l1 - l0) / 1000 + " 秒\n");
}
/**
* 常规方法,效率比较低~
*/
public static void viaImageIO(String imgPath) {
System.out.println("===========\nviaImageIO:");
long l0 = System.currentTimeMillis(); int[] imgSize = ImageUtil.getSizeDeprecated(imgPath);
System.out.println("图片的宽度和高度分别为:" + imgSize[0] + " " + imgSize[1]); long l1 = System.currentTimeMillis();
System.out.println("耗费的时间为:" + (double)(l1 - l0) / 1000 + " 秒\n");
}
}ImageUtil.java
package org.bruce.java.guru.performance;import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;/**
* @author Bruce Yang
* 获取图片尺寸的工具类~
*/
public class ImageUtil { /**
* @param imgPath 格式:/org/bruce/java/guru/res/lion.png
* @return array[0]<->width, array[1]<->height
*/
public static int[] getSize(String imgPath) {
Iterator<ImageReader> readers = null;
String lowerCase = imgPath.toLowerCase();
if (lowerCase.endsWith(".jpg") || lowerCase.endsWith(".jpeg")) {
readers = ImageIO.getImageReadersByFormatName("jpg");
} else if (lowerCase.endsWith(".png")) {
readers = ImageIO.getImageReadersByFormatName("png");
} else {
String strTips = "Unsupported image format!(try JPG or PNG)";
System.err.println(strTips);
System.exit(-1);
}
ImageReader reader = readers.next();
InputStream is = Class.class.getResourceAsStream(imgPath);
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(is);
reader.setInput(iis, true);
} catch (IOException e1) {
e1.printStackTrace();
}
int width = 0;
int height = 0;
try {
width = reader.getWidth(0);
height = reader.getHeight(0);
iis.close();
} catch (IOException e) {
e.printStackTrace();
}
int[] retVal = {width, height};
return retVal;
}
/**
* @param imgPath 格式:/org/bruce/java/guru/res/lion.png
* @return array[0]<->width, array[1]<->height
*/
public static int[] getSizeDeprecated(String imgPath) {
InputStream is = Class.class.getResourceAsStream(imgPath); BufferedImage bi = null;
try {
bi = ImageIO.read(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
int[] retVal = {bi.getWidth(), bi.getHeight()};
return retVal;
}
}
譬如说 jni 虽然带来了高效老大很开心,但如果有一天老大让你对其他各个平台也做兼容,就得自己伤心了~
某些人很明显理解错了意思。
找到300K以上的压缩肯定是图片压缩,打包成rar文件没什么用。
另外我想表达的意思是一个看起来并不是很难的任务,但是其代码水平不一样,编出来的东西就是不一样。
效率以及代码的清晰度差别其实挺大的。
另外这绝对不是标题党,你想着能写出来和自己写出来对比别人的代码差别是很大的。
我不相信一个人对比别人和自己代码的时候看不出来水平的高低。
import java.io.IOException;
import java.io.InputStream;public class JpegImageMetaReader { private static final byte TAG_START = (byte)0xff;
private static final byte START_OF_IMAGE = (byte)0xd8;
private static final byte END_OF_IMAGE = (byte)0xd9;
private static final byte START_OF_FRAME = (byte)0xc0;
private static final byte RESTART_MODULO_START = (byte)0xd0;
private static final byte RESTART_MODULO_END = (byte)0xd7;
private static final byte START_OF_SCAN = (byte)0xda; public static void main(String[] args) throws IOException {
InputStream in = null;
try {
in = new FileInputStream("d:/images.jpg");
ImageInfo info = getImageInfo(in);
System.out.println(info);
} finally {
if (in != null) {
in.close();
}
}
} public static ImageInfo getImageInfo(InputStream in) throws IOException { // 0-1: store JPEG tag
// 2-3: store JPEG tag length
// 4-5: store JPEG image height
// 6-7: store JPEG image width
byte[] seg = new byte[8]; // read JPEG START_OF_IMAGE tag
if (in.read(seg, 0, 2) == -1) {
return null;
} // if the first two bytes is not 0xff, 0xd8,
// that is the image format is not JPEG
if (seg[0] != TAG_START || seg[1] != START_OF_IMAGE) {
return null;
} while (true) { // read JPEG data tag, offset 0 must be 0xff
if (in.read(seg, 0, 2) == -1) {
return null;
} // if tag does not start with 0xff,
// the image format is not JPEG
if (seg[0] != TAG_START) {
return null;
} // Ignore JPEG RESTART_MODULO tag
if (seg[1] >= RESTART_MODULO_START && seg[1] <= RESTART_MODULO_END) {
continue;
} // find JPEG format START_OF_SCAN part,
// data that starts with poisition is JPEG compression image data,
// that never contains image meta information
if (seg[1] == START_OF_SCAN) {
return null;
} // find JPEG format END_OF_IMAGE tag, finish scan
if (seg[1] == END_OF_IMAGE) {
return null;
} // read JPEG data tag length
if (in.read(seg, 2, 2) == -1) {
return null;
} // find START_OF_FRAME tag
if (seg[1] == START_OF_FRAME) {
break;
} // skip JPEG data segement
byte[] skip = new byte[toInt(seg, 2) - 2];
if (in.read(skip) == -1) {
return null;
}
} // ignore JPEG image precision byte
if (in.read() == -1) {
return null;
} // read JPEG image height and width bytes
if (in.read(seg, 4, 4) == -1) {
return null;
}
return new ImageInfo(toInt(seg, 4), toInt(seg, 6));
} private static int toInt(byte[] bys, int start) {
return ((bys[start] & 0xff) << 8) | (bys[start + 1] & 0xff);
} public static class ImageInfo {
private int height;
private int width;
public ImageInfo(int height, int width) {
super();
this.height = height;
this.width = width;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public String toString() {
return "height: " + height + ", width: " + width;
}
}
}
1.class 前面带 static 是什么用法呢,
2.另外,宽高都拿到了,为什么还要将 宽高丢到一个 ImageInfo 对象里面呢,直接使用整形数组的结果不是更快捷?
1:内部类使用 static 的话,一般说明这个内部类不需要使用到外部类中的任何成员变量或者成员方法,也就是说这个内部类相对于较为独立。如果有需要使用到外部类中数据的话,则需要把 static 去掉。不对外部类数据进行引用时,内部类应使用 static 类。2:对于这个问题,举个很简单的例子,如果我还想获取图像长宽的 DPI 信息,以及颜色深度信息,或者是 JPEG 中 Exif 信息的话,那使用 int[] 就没办法表示了。在面向对象程序设计中,返回值应尽量少使用 int[], String[], Map<String, Object> 之类的返回值,因为这样的返回值不是自描述的,为什么 int[0] 是表示长度呢?int[0] 就不能表示宽度么?我很能理解你认为返回 int[] 的理由是什么,可能是因为 new 一个对象比较耗时和占用内存空间吧?实际上并不是这样的,new 一个对象而且这个类的构造中没有任何复杂耗时的操作,对于系统消耗来说可以忽略不计,而对于 HotSpot 的 JVM 而言,其内存分配的速度是大于 C 语言中 malloc 的内存分配速度。在 HotSpot 的 JVM 中一个 new 操作只占用了十几个 CPU 指令的时间。new 一个对象和 new 一个 int 数据的效率是等价的。另外,new 一个对象所分配的内存更是可以忽略不计了,只是在 JVM 的 PermGen 方法区中多存放一些类的信息,以及方法的信息。