加速=優化前耗時/優化後耗時比
公式圖:
靜態內部類的方式:
/**
* 內部類的單例模式
*/
public class StaticSingleton {
private StaticSingleton(){
System.out.println("aaa");
}
private static class StaticSingletonHolder{
private static StaticSingleton singleton=new StaticSingleton();
}
public static StaticSingleton getInstance(){
return StaticSingletonHolder.singleton;
}
}
除了反射機制強制呼叫私有建構函式,生成多個範例外,序列化和反序列化也可能會導致。
防止序列化的單例:
/**
* 可以被序列化的單例
*/
public class SerSingleton implements Serializable {
String name;
private SerSingleton(){
System.out.println("SerSingleton is create");
name="SerSingleton";
}
private static SerSingleton instance=new SerSingleton();
public static SerSingleton getInstance(){
return instance;
}
public static void createString(){
System.out.println("createString in Singleton");
}
//阻止生成新的範例,總是返回當前物件
private Object readResolve(){
return instance;
}
}
關鍵是實現了readResolve方法
1、代理模式用於延遲載入
2、動態代理
動態代理是指再執行時,動態生成代理類。
注意:動態代理使用位元組碼動態生成載入技術,在執行時生成並載入類。
常見的有jdk動態代理,cglib和javassist三種方式。
JDK方式:
/**
* JDK的動態代理
*/
public class JdkDBQueryHandler implements InvocationHandler {
IDBQuery real=null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(real==null){
real=new DBQuery();
}
return real.request();
}
public static IDBQuery createJdkProxy(){
IDBQuery jdkProxy=(IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IDBQuery.class},
new JdkDBQueryHandler());
return jdkProxy;
}
}
CGLIB:
/**
* cglib方式
*/
public class CglibDBQueryInterceptor implements MethodInterceptor {
IDBQuery real=null;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(real==null){
real=new DBQuery();
}
return real.request();
}
public static IDBQuery createCglibProxy(){
Enhancer enhancer=new Enhancer();
enhancer.setCallback(new CglibDBQueryInterceptor()); //指定切入器
enhancer.setInterfaces(new Class[]{IDBQuery.class});
IDBQuery cglibQuery=(IDBQuery)enhancer.create();
return cglibQuery;
}
}
結論:jdk方式建立物件很快,但是呼叫方法較慢。
Hibernate中代理模式的應用:
User u=(User)HibernateSessionFactory.getSession().load(User.class,1);
System.out.print(u.getClass().getName());
System.out.print(u.getName());
以上程式碼中,load方法後,並沒有查詢資料庫,在呼叫u.getName()時才查詢的資料庫,這就是Hibernate用代理模式做了延遲載入。
概念:如果在一個系統中存在多個相同的物件,那麼只需要共用一份物件的拷貝,而不必每一次使用都建立新的物件。
功能元件如圖:
注意:享元模式時為數不多的只為了提升系統效能而生的設計模式。他的主要作用就是複用大物件,節省記憶體和物件建立時間。
享元模式和物件池的最大不同在於:享元模式是不能互相替代的,他們有
/**
* 報表介面
*/
public interface IReportManager {
String createReport();
}
/**
* 員工報表
*/
public class EmployeeReportManager implements IReportManager {
//租戶ID
protected String tenanId=null;
public EmployeeReportManager(String tenanId) {
this.tenanId = tenanId;
}
@Override
public String createReport() {
return "this is a employee Report";
}
}
/**
* 財務報表
*/
public class FinancialReportManager implements IReportManager {
//租戶ID
protected String tenanId=null;
public FinancialReportManager(String tenanId) {
this.tenanId = tenanId;
}
@Override
public String createReport() {
return "this is a Financial Report";
}
}
/**
* 享元工廠類
* 保證同一個id獲取到的是同一個物件
*/
public class ReportManagerFactory {
Map<String,IReportManager> financialReportManager=new HashMap<>();
Map<String,IReportManager> employeeReportManager=new HashMap<>();
IReportManager getFinancialReportManager(String tenantId){
IReportManager r=financialReportManager.get(tenantId);
if(r==null){
r=new FinancialReportManager(tenantId);
financialReportManager.put(tenantId,r);
}
return r;
}
IReportManager getEmployeeReportManager(String tenantId){
IReportManager r=employeeReportManager.get(tenantId);
if(r==null){
r=new EmployeeReportManager(tenantId);
employeeReportManager.put(tenantId,r);
}
return r;
}
}
裝飾者(Decorator)和被裝飾者(ConcreteComponent)擁有相同的介面,裝飾者可以在被裝飾者的方法上加上特定的前後置處理,增強被裝飾者的功能。
/**
* 介面
*/
public interface IPacketCreator {
String handleContent();
}
public abstract class PacketDecorator implements IPacketCreator {
IPacketCreator iPacketCreator;
public PacketDecorator(IPacketCreator iPacketCreator) {
this.iPacketCreator = iPacketCreator;
}
}
public class PacketHTMLHeaderCreator extends PacketDecorator {
public PacketHTMLHeaderCreator(IPacketCreator iPacketCreator) {
super(iPacketCreator);
}
/**
* 將資料封裝成html格式
* @return
*/
@Override
public String handleContent() {
StringBuffer sb=new StringBuffer();
sb.append("<html>");
sb.append("<body>");
sb.append(iPacketCreator.handleContent());
sb.append("</body>");
sb.append("</html>\n");
return sb.toString();
}
}
public class PacketHTTPHeaderCreator extends PacketDecorator {
public PacketHTTPHeaderCreator(IPacketCreator iPacketCreator) {
super(iPacketCreator);
}
@Override
public String handleContent() {
StringBuffer sb=new StringBuffer();
sb.append("Cache-Control:no-cache\n");
sb.append(iPacketCreator.handleContent());
return sb.toString();
}
}
使用類:
@Test
public void test3(){
IPacketCreator iPacketCreator = new PacketHTTPHeaderCreator(new PacketHTMLHeaderCreator(new PacketBodyCreator()));
System.out.println(iPacketCreator.handleContent());
}
在軟體系統中,當一個物件的行為依賴於另一個物件的狀態時,觀察者模式就非常有用。
程式碼範例如下:
package com.mmc.concurrentcystudy.design.guanchazhe;
import java.util.Observable;
import java.util.Observer;
/**
* 讀者類,實現了觀察者介面
*/
public class Reader implements Observer {
private String name;
public Reader(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 關注作者
* @param writerName
*/
public void subscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).addObserver(this);
}
/**
* 取消關注
* @param writerName
*/
public void unsunbscribe(String writerName){
WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
}
/**
* 業務方法
* @param o
* @param arg
*/
@Override
public void update(Observable o, Object arg) {
if(o instanceof Writer){
Writer writer=(Writer)o;
System.out.println(name+"知道"+writer.getName()+"釋出了新書《"+writer.getLastNovel()+"》");
}
}
}
package com.mmc.concurrentcystudy.design.guanchazhe;
import java.util.Observable;
/**
* 作者類,要繼承自被觀察者類
*/
public class Writer extends Observable {
private String name; //作者的名稱
private String lastNovel; //記錄作者最新發布的小說
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastNovel() {
return lastNovel;
}
public void setLastNovel(String lastNovel) {
this.lastNovel = lastNovel;
}
public Writer(String name) {
this.name = name;
WriterManager.getInstance().add(this);
}
//釋出新書
public void addNovel(String novel){
System.out.println(name+"釋出了新書:《"+novel+"》");
lastNovel=novel;
setChanged();
notifyObservers();
}
}
package com.mmc.concurrentcystudy.design.guanchazhe;
import java.util.HashMap;
import java.util.Map;
/**
* 管理器,保持一份獨有的作者列表
*/
public class WriterManager {
private Map<String,Writer> map=new HashMap<>();
public void add(Writer writer){
map.put(writer.getName(),writer);
}
public Writer getWriter(String name){
return map.get(name);
}
//單例
private WriterManager(){}
public static WriterManager getInstance(){
return WriterManagerInstance.writerManager;
}
private static class WriterManagerInstance{
private static WriterManager writerManager=new WriterManager();
}
}
測試方法:
@Test
public void test4(){
Reader reader=new Reader("小明");
Reader reader2=new Reader("小紅");
Reader reader3=new Reader("小李");
Writer writer=new Writer("韓寒");
Writer writer2=new Writer("李敖");
reader.subscribe("韓寒");
reader2.subscribe("韓寒");
reader3.subscribe("李敖");
writer.addNovel("三重門");
writer2.addNovel("不知道");
}
示意圖:
IO操作很容易形成效能瓶頸,所以儘可能加入緩衝元件。
快取是為了系統效能而開闢的記憶體空間。最為簡單的快取是使用HashMap,但是這樣做會遇到很多問題,比如不知道合適清理無效的資料,如何防止資料過多而記憶體溢位。
現在有很多快取框架,如EHCache,OSCache,JBossCache等。
如果一個類被頻繁的使用,那麼不必每次都生成一個範例,可以將這個範例儲存在一個池中,待需要的時候直接從池中獲取。這個池就稱為物件池。
Apache中提供了一個Jakarta Commons Pool物件池元件,可以直接使用。
API列表:
public interface ObjectPool<T> extends Closeable {
//從物件池中獲取到一個物件
T borrowObject() throws Exception, NoSuchElementException, IllegalStateException;
//物件返回給物件池
void returnObject(T var1) throws Exception;
}
Common Pool中內建了3個物件池,分別是StackObjectPool,GenericObjectPool,SoftReferenceObjectPool。
StackObjectPool:利用Stack來儲存物件,可以指定初始化大小。
GenericObjectPool:是一個通用的物件池,可以設定物件池的容量,也可以設定無可用物件時應該怎樣,有一個複雜的建構函式來定義這些行為。
SoftReferenceObjectPool:使用的是ArrayList儲存,儲存的是物件的軟參照。
使用範例:
/**
* 物件池
*/
public class PoolFactory extends BasePooledObjectFactory<Object> {
static GenericObjectPool<Object> pool = null;
// 取得物件池工廠範例
public synchronized static GenericObjectPool<Object> getInstance() {
if (pool == null) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxIdle(-1);
poolConfig.setMaxTotal(-1);
poolConfig.setMinIdle(100);
poolConfig.setLifo(false);
pool = new GenericObjectPool<Object>(new PoolFactory(), poolConfig);
}
return pool;
}
public static Object borrowObject() throws Exception{
return (Object) PoolFactory.getInstance().borrowObject();
}
public static void returnObject(Object jdbcUtils) throws Exception{
PoolFactory.getInstance().returnObject(jdbcUtils);
}
public static void close() throws Exception{
PoolFactory.getInstance().close();
}
public static void clear() throws Exception{
PoolFactory.getInstance().clear();
}
@Override
public Object create() throws Exception {
return new Object();
}
@Override
public PooledObject<Object> wrap(Object obj) {
return new DefaultPooledObject<Object>(obj);
}
}
測試程式碼:
@Test
public void test6() throws Exception {
Object o=PoolFactory.borrowObject();
PoolFactory.returnObject(o);
Object o2=PoolFactory.borrowObject();
PoolFactory.returnObject(o2);
System.out.println(o==o2);
}
注意:只有對重量級物件使用物件池技術才能提高系統效能,對輕量級的物件使用反而會降低效能。
為保證應用程式的服務質量,需要使用多臺計算機協同工作,將系統負載儘可能分配到各個計算機節點上。
一般用於嵌入式裝置或者記憶體,硬碟不足的情況下。
比如一個簡單的例子,a和b兩個變數的值的替換。最常用的方法是引入一箇中間變數。為了省去中間變數可以用這樣的方法:
@Test
public void test7(){
int a=3;
int b=5;
a=a+b;
b=a-b;
a=a-b;
System.out.println(a);
System.out.println(b);
}
最典型的應用就是快取了,除了緩以外,有一些排序方法也會用到。
一個空間換時間的排序範例:
/**
* 空間換時間的排序
*/
public class SpaceSort {
public static int arrayLen=1000000;
public static void main(String[] args) {
int [] a=new int[arrayLen];
int [] old=new int[arrayLen];
Map<Integer,Object> map=new HashMap<>();
int count=0;
while (count<a.length){ //初始化陣列
int value=(int)(Math.random()*arrayLen*10);
if(map.get(value)==null){
map.put(value,value);
a[count]=value;
count++;
}
}
System.arraycopy(a,0,old,0,a.length);
long start=System.currentTimeMillis();
Arrays.sort(a);
System.out.println("Arrays.sort spend:"+(System.currentTimeMillis()-start));
start=System.currentTimeMillis();
spaceToTime(old);
System.out.println("spaceToTime spend:"+(System.currentTimeMillis()-start));
}
public static void spaceToTime(int[] array){
int i=0;
int max=array[0];
int l=array.length;
//找出最大值
for (i=0;i<l;i++){
if(array[i]>max)
max=array[i];
}
int []temp=new int[max+1]; //分配臨時空間
for (i=0;i<l;i++){
temp[array[i]]=array[i]; //以索引下標標識數位大小
}
int j=0;
int max1=max+1;
for (i=0;i<max1;i++){ //線性複雜度
if (temp[i]>0){
array[j++]=temp[i];
}
}
}
}
使用StringTokenizer類分割字串
public void test9(){
StringBuilder sb=new StringBuilder();
int len=1000;
for (int i=0;i<len;i++){
sb.append(i);
sb.append(";");
}
String str=sb.toString();
long start=System.currentTimeMillis();
StringTokenizer st=new StringTokenizer(str,";");
for (int i=0;i<10000;i++){
while (st.hasMoreElements()){
String s = st.nextToken();
}
st=new StringTokenizer(str,";");
}
System.out.println(System.currentTimeMillis()-start);
}
目前來說這種方法也沒快多少
ArrayList和LinkedList
刪除對比:
TreeMap和LinkedHashMap的區別。
他們都是可排序的,LinkedHashMap基於元素進入集合或者被存取的先後順序排序,TreeMap是根據元素的固有順序(Comparator或者Comparable)
for (int i=0;i<list.size();i++){
count++;
}
len=list.size();
for (int i=0;i<len;i++){
count++;
}
在讀寫檔案上使用NIO會更快
以寫入4000000的int數位為例
--- | Stream | ByteBuffer | MappedByteBuffer |
---|---|---|---|
寫耗時 | 295ms | 123ms | 32ms |
讀耗時 | 433ms | 59ms | 17ms |
測試範例:
/**
* IO寫檔案
* @throws IOException
*/
@Test
public void test12() throws IOException {
long start=System.currentTimeMillis();
DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("d://1.txt")));
for (int i=0;i<4000000;i++){
dos.writeInt(i);
}
if(dos!=null)
dos.close();
System.out.println(System.currentTimeMillis()-start);
}
/**
* IO讀檔案
* @throws IOException
*/
@Test
public void test13() throws IOException {
long start=System.currentTimeMillis();
DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("d://1.txt")));
for (int i=0;i<4000000;i++){
dis.readInt();
}
if(dis!=null)
dis.close();
System.out.println(System.currentTimeMillis()-start);
}
/**
* NIO寫檔案
* @throws IOException
*/
@Test
public void test14() throws IOException {
long start=System.currentTimeMillis();
FileOutputStream fos=new FileOutputStream("d:/1.txt");
FileChannel fc=fos.getChannel();
ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);
for (int i = 0; i <4000000 ; i++) {
byteBuffer.put(int2byte(i));
}
byteBuffer.flip();
fc.write(byteBuffer);
System.out.println(System.currentTimeMillis()-start);
}
/**
* NIO讀檔案
* @throws IOException
*/
@Test
public void test15() throws IOException {
long start=System.currentTimeMillis();
FileInputStream fin=new FileInputStream("d://1.txt");
FileChannel channel = fin.getChannel();
ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4);
channel.read(byteBuffer);
channel.close();
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());
}
System.out.println(System.currentTimeMillis()-start);
}
public static byte[] int2byte(int res){
byte[] targets=new byte[4];
targets[3]=(byte)(res&0xff);
targets[2]=(byte)((res>>8)&0xff);
targets[1]=(byte)((res>>16)&0xff);
targets[0]=(byte)(res>>24);
return targets;
}
public static int byte2int(byte b1,byte b2,byte b3,byte b4){
return ((b1&0xff)<<24)|((b2&0xff)<<16)|((b3&0xff)<<8)|(b4&0xff);
}
/**
* NIO MappedByteBuffer寫檔案
* @throws IOException
*/
@Test
public void test16() throws IOException {
long start=System.currentTimeMillis();
FileChannel fc=new RandomAccessFile("d://1.txt","rw").getChannel();
IntBuffer ib=fc.map(FileChannel.MapMode.READ_WRITE,0,4000000*4).asIntBuffer();
for (int i = 0; i < 4000000; i++) {
ib.put(i);
}
if(fc!=null)
fc.close();
System.out.println(System.currentTimeMillis()-start);
}
/**
* NIO MappedByteBuffer讀檔案
* @throws IOException
*/
@Test
public void test17() throws IOException {
long start=System.currentTimeMillis();
FileChannel fc=new FileInputStream("d://1.txt").getChannel();
IntBuffer ib=fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer();
while (ib.hasRemaining()){
ib.get();
}
if(fc!=null)
fc.close();
System.out.println(System.currentTimeMillis()-start);
}
2、直接記憶體存取
DirectBuffer:直接可以存取系統實體記憶體的類,在對普通的ByteBuffer存取時,系統會使用一個「核心緩衝區」進行間接的操作,而DirectBuffer所處的位置就相當於這個「核心緩衝區」。因此他更接近底層,更快。
但是DirectBuffer建立銷燬都比較費時間,在需要頻繁建立和銷燬的情況下不適合用。
/**
* 使用DirectBuffer
* @throws IOException
*/
@Test
public void test18() throws IOException {
long start=System.currentTimeMillis();
ByteBuffer b=ByteBuffer.allocateDirect(500);
for (int i=0;i<100000;i++){
for (int j=0;j<99;j++)
b.putInt(j);
b.flip();
for (int j=0;j<99;j++)
b.getInt();
b.clear();
}
System.out.println(System.currentTimeMillis()-start);
}
@Test
public void test19() throws IOException {
long start=System.currentTimeMillis();
ByteBuffer b=ByteBuffer.allocate(500);
for (int i=0;i<100000;i++){
for (int j=0;j<99;j++)
b.putInt(j);
b.flip();
for (int j=0;j<99;j++)
b.getInt();
b.clear();
}
System.out.println(System.currentTimeMillis()-start);
}
@Test
public void test20(){
MyObject myObject=new MyObject();
//建立參照佇列
ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
//建立軟參照
SoftReference<MyObject> softReference=new SoftReference<>(myObject,referenceQueue);
myObject=null;
System.gc();
System.out.println("After Gc:soft get="+softReference.get());
System.out.println("分配大記憶體");
byte[] b=new byte[4*1024*925];
System.out.println("After new byte[]:soft get="+softReference.get());
}
@Test
public void test20(){
MyObject myObject=new MyObject();
//建立參照佇列
ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
//建立軟參照
WeakReference<MyObject> softReference=new WeakReference<>(myObject,referenceQueue);
myObject=null;
System.gc();
System.out.println("After Gc:soft get="+softReference.get());
System.out.println("分配大記憶體");
byte[] b=new byte[4*1024*925];
System.out.println("After new byte[]:soft get="+softReference.get());
}
呼叫方法時傳遞的引數和在方法在建立的臨時變數都儲存在棧中,速度較快。其他變數,如靜態變數,範例變數都在堆中。
@Test
public void test21() throws IOException {
long start=System.currentTimeMillis();
int a=0;
for (int i=0;i<2000000000;i++){
a++;
}
System.out.println(a);
System.out.println(System.currentTimeMillis()-start);
}
private static int ta=0;
@Test
public void test22() throws IOException {
long start=System.currentTimeMillis();
int a=0;
for (int i=0;i<2000000000;i++){
ta++;
}
System.out.println(ta);
System.out.println(System.currentTimeMillis()-start);
}
a*2 用a<<1
a/2 用a>>1
3. 替代switch
用陣列替代switch,效率會更高
public int sw(int a){
switch (a){
case 1:return 1;
case 2:return 3;
case 3:return 5;
case 4:return 9;
default:return 0;
}
}
//用陣列來實現
public int sw2(int a){
int[] array=new int[]{1,3,5,9,0};
return array[a];
}
4、複製陣列用System.arraycopy
System.arraycopy時淺拷貝。對於非基本型別而言,他拷貝的是物件的參照,而非新建一個物件。
5、clone方法代替new
clone方法不會呼叫建構函式,所以能夠快速的創造一個範例。預設情況下是淺拷貝。但是拷貝的物件修改屬性,舊的物件的值可能是不會變的。
6、靜態方法代替實體方法
實體方法需要維護一張類似虛擬結構表的東西以支援對多型的實現。所以比靜態方法慢。