享元模式的应用场景
当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。
例子一
开发一个棋牌游戏(比如象棋)。一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,比如:棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,我们就能显示一个完整的棋盘给玩家。具体的代码如下所示。其中,ChessPiece 类表示棋子,ChessBoard 类表示一个棋局,里面保存了象棋中 30 个棋子的信息。
普通实现
public class ChessPiece {//棋子 private int id; private String text; private Color color; private int positionX; private int positionY; public ChessPiece(int id, String text, Color color, int positionX, int positionY) { this.id = id; this.text = text; this.color = color; this.positionX = positionX; this.positionY = positionX; } public static enum Color { RED, BLACK } // ...省略其他属性和getter/setter方法... } public class ChessBoard {//棋局 private Map<Integer, ChessPiece> chessPieces = new HashMap<>(); public ChessBoard() { init(); } private void init() { chessPieces.put(1, new ChessPiece(1, "車", ChessPiece.Color.BLACK, 0, 0)); chessPieces.put(2, new ChessPiece(2,"馬", ChessPiece.Color.BLACK, 0, 1)); //...省略摆放其他棋子的代码... } public void move(int chessPieceId, int toPositionX, int toPositionY) { //...省略... } }
如果有千万房间就会创建千万个棋局, 棋子数=棋局数据*棋子数量
我们可以发现每个棋局棋子都一样 只是棋子的坐标不一样。我们可以把棋子的的坐标抽出来 让棋子作为一个享元类
// 享元类 public class ChessPieceUnit { private int id; private String text; private Color color; public ChessPieceUnit(int id, String text, Color color) { this.id = id; this.text = text; this.color = color; } public static enum Color { RED, BLACK } // ...省略其他属性和getter方法... } public class ChessPieceUnitFactory { private static final Map<Integer, ChessPieceUnit> pieces = new HashMap<>(); static { pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK)); pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK)); //...省略摆放其他棋子的代码... } public static ChessPieceUnit getChessPiece(int chessPieceId) { return pieces.get(chessPieceId); } } public class ChessPiece { private ChessPieceUnit chessPieceUnit; private int positionX; private int positionY; public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) { this.chessPieceUnit = unit; this.positionX = positionX; this.positionY = positionY; } // 省略getter、setter方法 } public class ChessBoard { private Map<Integer, ChessPiece> chessPieces = new HashMap<>(); public ChessBoard() { init(); } private void init() { chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(1), 0,0)); chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(2), 1,0)); //...省略摆放其他棋子的代码... } public void move(int chessPieceId, int toPositionX, int toPositionY) { //...省略... } }
这样我们的棋子只会有一份 从而实现了节约内存
源码中的应用
Integer
Integer i1 = 56; Integer i2 = 56; Integer i3 = 129; Integer i4 = 129; System.out.println(i1 == i2);//true System.out.println(i3 == i4);//false
自动装箱后的代码
Integer i1 = Integer.valueOf(56); Integer i2 = Integer.valueOf(56); Integer i3 = Integer.valueOf(129); Integer i4 = Integer.valueOf((129); System.out.println(i1 == i2);//true System.out.println(i3 == i4);//false
按照我们的理解 可能2个都是false 因为是不同的对象
public static Integer valueOf(int i) { //low -128 higt=127 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
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() {} }
可以看到提前将-128~127的数值缓存了起来用于共享,为什么缓存-128~127 因为数值太多不可能全部缓存,只能缓存大多数情况出现的数值 当然我们也可以动态改变
//方法一:-Djava.lang.Integer.IntegerCache.high=255
//方法二:-XX:AutoBoxCacheMax=255
String
String s1 = "小争哥"; String s2 = "小争哥"; String s3 = new String("小争哥"); System.out.println(s1 == s2);//true System.out.println(s1 == s3);//false
1.new 会先在常量池找有没有,如果没有会在常量池看是否存在 如果不存在则创建,并在堆空间开辟的空间引用常量池,s1会直接引用常量池
String s3 = new String("小争哥"); //可能会认为会从常量池拿 因为上面是new 并不会放入常量池 String s1 = "小争哥"; System.out.println(s1 == s3);//false