先来看一段代码:
package com.utils; public class MemTest { public static void main(String[] args) { Integer i1 = 12; Integer i2 = 12; Integer i3 = 129; Integer i4 = 129; System.out.println(i1 == i2);//true System.out.println(i3 == i4);//false String s1 = "wuyue"; String s2 = "wuyue"; String s3 = new String("wuyue"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false } } 复制代码
我们都知道在java的世界中==操作符比较对象的时候,实际上是比较的对象的地址。 上述代码中Integer的例子可能很多人都知道了,太多的面试题考到他了,我们这里略过自动装箱和自动拆箱的部分 如果有兴趣的同学可以自己搜一下。网上的例子很多了。这里我们直接看一下源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 复制代码
讲白了,这里的valueOf 代码很简单,就是如果你传入的值 >=low 或者<= high 那么 就直接去一个cache里面 取一个对象。 否则就自己new一个对象出来。
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } 复制代码
这里其实看一下就明白,就好像是一个对象池的概念, 应该是Integer的作者觉得 大部分int值使用最多的范围就是-128~127,所以他干脆就把这些对象 全部创建出来, 这样用到的时候直接取就行了, 超过这个范围的 因为用的少,那就用到的时候再创建。
Integer a = new Integer(110); Integer a = 110; Integer a = Integer.valueOf(110); 复制代码
在使用的时候我们尽量使用 上述写法的后两种写法,否则使用第一种写法是利用不到integer的缓存特性的
说完Integer我们再来看看String。
其实明白了Integer的例子,那么Stringd的例子也很好理解。 无非就是创建了新对象,和复用了对象而已。 只不过这里我们要注意的是: String,因为是字符串,字符串的写法太多,也无法预测不同项目之间的特性,所以String的设计者就没有使用 Integer中的对象池的概念,Integer的对象池我们看过源码就知道,他是一启动程序就自动创建好了,但是String没有 String是创建一个字符串对象以后,如果新创建的和后面是一样的内容 那么就复用,否则不复用,但是一定要注意只要是使用new 关键字创建出来的那么一定不存在复用的现象。
了解到Integer与String的一些特殊机制以后,我们就可以利用源码中透露给我们的信息来适当优化一下我们的代码。
考虑一个图文混排,文本编辑器的实现,事实上这种东西在android开发中已经不少见了。
可以看一下这个字体的定义:
public class Character { private Char c;//字体内容 private Font font; //字体 private int size; // 字体大小 private int color;// 字体颜色 } 复制代码
假设用户编辑了几千个字,这里就有几千个Character对象,但是细想一下,我们这里真的需要这么多几千个完整的Character对象吗? 相信没有一个人写文章的时候 字体大小会不断的变化,字体颜色不断变化的。
我们变的就是字体内容,但是对应的字体,字体大小,和字体颜色 是不会变的。
对应的,我们来修改我们的代码,我们首先定义一个style。
他包含了所有Font相关的特性
public class CharacterStyle { private Font font; private int size; private int color; public CharacterStyle(Font font, int size, int color) { this.font = font; this.size = size; this.color = color; } @Override public boolean equals(Object o) { CharacterStyle otherStyle = (CharacterStyle) o; return font.equals(otherStyle.font) && size == otherStyle.size && colorRGB == otherStyle.colorRGB; } } 复制代码
提供一个工厂 来完成这个复用对象的功能
//这里我们仿照String的实现思路,只要找到之前有的,那我们就不要创建了, //这里其实还可以优化一下,大多数人其实使用的多数字体都是默认系统提供的, //所以这里还可以优化成static 代码块 固定先插入一个 默认的font public class CharacterStyleFactory { private static final List<CharacterStyle> styles = new ArrayList<>(); public static CharacterStyle getStyle(Font font, int size, int color) { for (CharacterStyle style : styles) { if (style.equals(newStyle)) { return style; } } CharacterStyle newStyle = new CharacterStyle(font, size, color); styles.add(newStyle); return newStyle; } } 复制代码
如果对性能不满意 还可以用时间为o1 的方法 来加快这个过程
public class CharacterStyleFactory { private static final Map<Integer, CharacterStyle> styles = new HashMap<>(); public static CharacterStyle getStyle(Font font, int size, int colorRGB) { //key = font的哈希值 + size + colorRGB 以保证哈希值唯一性, 同时也避免了重复创建CharacterStyle的开销 int key = font.hashCode() + size + colorRGB; if (styles.containsKey(key)) { return styles.get(key); } CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB); styles.put(key, newStyle); return newStyle; } } 复制代码
这样我们的文字类就变成, 内容+style
public class Character { private char c; private CharacterStyle style; public Character(char c, CharacterStyle style) { this.c = c; this.style = style; } } 复制代码
使用的时候就可以是:
变的是内容,不变的是字体style,所以style得到了复用,又可以节省内存,还可以省略new对象的过程,减少gc的过程
public class Editor { private List<Character> chars = new ArrayList<>(); public void appendCharacter(char c, Font font, int size, int colorRGB) { Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB)); chars.add(character); } } 复制代码
大家也可以多思考思考,平时业务模型中有哪些地方可以仿照string的设计,有哪些地方可以仿照integer的设计。