Android性能优化-----代码(Java)

一、变量的使用

1)变量定义
    私有内部类要访问外部类的变量或方法时,其成员变量不要用private,因为在编译时会生成setter/getter,影响性能,是可以设置为public;
    简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会被重载;
    临时变量提高Java代码性能
2)如果方法用不到成员变量,可以把方法申明为static,性能会提高到15%到20%;但是变量和对象不要随意使用静态变量,gc通常是不会回收这个对象所占有的堆内存的
3)如果能确定类是不会派生的,可以生命为final类,则该类所有的方法都是final的;Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50%。
4)避免枚举的使用
    枚举会增加内存的使用、枚举会增加字符串常量、枚举会增加函数调用时间
    可以使用int常量,也最好选用int常量的方式来实现相关的功能,避免对文件大小,方法数量,内存,性能产生不利的影响
5)合理利用浮点数,浮点数比整型慢两倍;
6)使用移位操作来代替乘除操作

public class DIV {
    public void calculate(int a) {
        int div = a >> 2;    //a / 4  
        int div2 = a >> 3;   //a / 8
        int temp = a / 3;    // 不能转换成位移操作
        int mul = a << 2;    //a * 4  
    }
}

7)合理的使用String,
    不要随意的使用stingA=StringB+StringC的写法,有大量拼接操作的地方用StringBuilder代替;
    StringBuffer中的方法大都采用了synchronized关键字进行修饰,因此是线程安全的;
    在创建 StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能
8)分割字符串
    Android 中StringTokenizer 实现字符串分割,相比于split要好;
    应该避免使用split,split由于支持正则表达式,所以效率比较低
    如果确实需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char)

String str = "abc-de" ;
StringTokenizer st = new StringTokenizer(str , "-") ;
System.out.println(st.nextToKen());

String[] s = str.split("-");  
for(int i = 0 ; i < s.length ; i++){  
	System.out.println(s[i]);  
} 

9)在字符串相加的时候,如果该字符串只有一个字符的话,使用'a'代替"a";
10)如果只是查找单个字符的话,用charAt()(性能高些)代替startsWith(),
11)尽量重用对象,
12)字符串变量和字符串常量equals的时候将字符串常量写在前面,可以避免空指针异常
13)把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:
    A、Integer.toString()方法就不说了,直接调用了
    B、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断
    C、i + ""底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串
    三者对比下来,明显是A最快、B次之、C最慢
 

二、循环

1)避免在循环条件中使用复杂表达式,因为循环条件会被反复计算;可把不变的计算条件提出来计算
2)使用增强foreach循环
    foreach>fori>forlength
    size()和length当变量提出来
3)循环内避免创建不必要的对象和变量
4)降低算法复杂度毫无疑问是改善性能最行之有效的办法
5)不要在循环中调用synchronized(同步)方法,如果必须同步的话,在循环外使用synchronized
6)把try/catch块放入循环体内,会极大的影响性能;放在循环外

三、数组

1)基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList
2)Array 数组效率最高,但容量固定,无法动态改变,ArrayList容量可以动态增长,但牺牲了效率。

 

四、列表和集合

1)为'Vectors' 和 'Hashtables'定义初始大小,防止扩容
也包括rrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等;JVM为Vector扩充大小的时候需要重新创建一个更大的数组,将原原先数组中的内容复制过来,最后,原先的数组再被回收。可见Vector容量的扩大是一个颇费时间的事。


public class DIC {
    public void addObjects (Object[] o) {
        // if length > 10, Vector needs to expand 
        for (int i = 0; i< o.length;i++) {    
            v.add(o);   // capacity before it can add more elements.
        }
    }
    public Vector v = new Vector();  // no initialCapacity.
}
自己设定初始大小。
    public Vector v = new Vector(20);  
    public Hashtable hash = new Hashtable(10); 

2)如何选择使用

顺序插入比较多的场景使用ArrayList,
    随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指针,
    中间插入、添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据,
    理解ArrayList和LinkedList的原理就知道了
Array 数组效率最高,但容量固定,无法动态改变,ArrayList容量可以动态增长,但牺牲了效率。

Hashtable、Vector、StringBuffer是线程安全的,由于使用同步机制而导致了性能开销

3)使用SparseArray和ArrayMap代替HashMap
    针对Android这种移动平台,也推出了更符合自己的api,比如SparseArray(SparseBooleanArray、LongSpareArray)、ArrayMap用来代替HashMap在有些情况下能带来更好的性能提升。
    1、如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用
    2、如果key类型为其它的类型,则使用ArrayMap

4)Map的遍历

如果只是获取key,或者value,推荐使用keySet或者values方式
如果同时需要key和value推荐使用entrySet
如果需要在遍历过程中删除元素推荐使用Iterator
如果需要在遍历过程中增加元素,可以新建一个临时map存放新增的元素,等遍历完毕,再把临时map放到原来的map中;

====如果是随机访问的,使用普通 for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高
方法一:在for循环中使用entries实现Map的遍历:
//最常见也是大多数情况下用的最多的,一般在键值对都需要使用,尤其是容量大时
for(Map.Entry<String, String> entry : map.entrySet()){
    String mapKey = entry.getKey();
    String mapValue = entry.getValue();
    System.out.println(mapKey+":"+mapValue);

方法二:在for循环中遍历key或者values,一般适用于只需要map中的key或者value时使用,在性能上比使用entrySet较好;
for(String key : map.keySet()){
    System.out.println(key);
}
for(String value : map.values()){
    System.out.println(value);
}
方法三:通过Iterator遍历;
Iterator也有其优势:在用foreach遍历map时,如果改变其大小,会报错,但如果只是删除元素,可以使用Iterator的remove方法删除元素
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while(entries.hasNext()){
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key+":"+value);
}
方法四:Lambda
java8提供了Lambda表达式支持,语法看起来更简洁,可以同时拿到key和value,不过,经测试,性能低于entrySet,所以更推荐用entrySet的方式
public void testLambda() {
	map.forEach((key, value) -> {
	  System.out.println(key + ":" + value);
	});
}
方法五、通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作;
for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

 

五、文件和流操作

1)进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源;在finally块中关闭Stream,防止资源泄漏

2)使用带缓冲的输入输出流进行IO操作,带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率

六、正则表达式

1、计算密集型代码中避免使用正则表达式
1)正则表达式是十分有用,但是在使用时也要付出代价。
2)还要小心各种使用到正则表达式的JDK字符串方法,比如 String.replaceAll() 或 String.split()。可以选择用比较流行的开发库,比如 Apache Commons Lang 来进行字符串操作。
3)如果万不得已非要在计算密集型代码中使用正则表达式的话,至少要将 Pattern 缓存下来,避免反复编译Pattern。
    final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");
4)如果仅使用到了如下这样简单的正则表达式的话:
    String[] parts = ipAddress.split("\\.");
    这是最好还是用普通的 char[] 数组或者是基于索引的操作。比如下面这段可读性比较差的代码其实起到了相同的作用。

    int length = ipAddress.length();
    int offset = 0;
    int part = 0;
    for (int i = 0; i < length; i++) {
        if (i == length - 1 || ipAddress.charAt(i + 1) == '.') {
            parts[part] = ipAddress.substring(offset, i + 1);
            part++;
            offset = i + 2;
        }
    }

七、并行和串行、同步和异步

1)除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。

八、对象

1)不必要的强转

所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的“等于”其父类。那么,由子类造型至父类的操作就是不必要的了。

class Dog extends Animal {
    void method () {
        Dog dog = new Dog();
        Animal animal = (Animal)dog;  // not necessary.
        Object o = (Object)dog;         // not necessary.
    }
}
更正:         
class Dog extends Animal {
    void method () {
        Dog dog = new Dog();
        Animal animal = dog;
        Object o = dog;
    }
}

2)尽量缓存经常使用的对象
尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。
3)尽量避免非常大的内存分配
有时候问题不是由当时的堆状态造成
4)尽量重用对象,不要重复初始化变量
5)建议在对象使用完毕后,手动设置成null
6)放在频繁创建对象,可以使用clone()方法
    public static Credit getNewCredit()
    {
        return (Credit)BaseCredit.clone();
    }

九、线程安全

1)尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销
2)java自带了线程安全的基本类型
包括: AtomicInteger, AtomicLong, AtomicBoolean, AtomicIntegerArray,AtomicLongArray; 

public static void main(String[] args) {
	AtomicInteger atomicInteger = new AtomicInteger(1);
	
	int number = 0;
	
	//getAndAdd: 先获取值,再自增10,返回值为自增前的值
	number = atomicInteger.getAndAdd(10);
	System.out.println("getAndAdd       --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//getAndDecrement: 先获得值,再自减1,返回值为自减前的
	number = atomicInteger.getAndDecrement();
	System.out.println("getAndDecrement --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//getAndIncrement: 先获得值,再自增1,返回值为自增前的值
	number = atomicInteger.getAndIncrement();
	System.out.println("getAndIncrement --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//getAndSet: 先获得值,再赋值,返回值为赋值前的值
	number = atomicInteger.getAndSet(10);
	System.out.println("getAndSet       --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//addAndGet: 先自增,再返回值,返回值为自增后的值
	number = atomicInteger.addAndGet(10);
	System.out.println("addAndGet       --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//decrementAndGet: 先自减1,再获取返回值,返回值为自减后的值
	number = atomicInteger.decrementAndGet();
	System.out.println("decrementAndGet --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//incrementAndGet: 先自增1,再返回值,返回值为自增后的值
	number = atomicInteger.incrementAndGet();
	System.out.println("incrementAndGet --> number:" + number + ", atomicInteger: " + atomicInteger);
	
	//compareAndSet: 如果当前值为20,则设置为100
	boolean isSuccess = atomicInteger.compareAndSet(20, 100);
	System.out.println("compareAndSet: " + isSuccess);
}

getAndAdd       --> number:1, atomicInteger: 11
getAndDecrement --> number:11, atomicInteger: 10
getAndIncrement --> number:10, atomicInteger: 11
getAndSet       --> number:11, atomicInteger: 10
addAndGet       --> number:20, atomicInteger: 20
decrementAndGet --> number:19, atomicInteger: 19
incrementAndGet --> number:20, atomicInteger: 20
compareAndSet: true

3)Java中线程安全的类

    A、通过synchronized 关键字给方法加上内置锁来实现线程安全 
    Timer,TimerTask,Vector,Stack,HashTable,StringBuffer
    B、原子类Atomicxxx—包装类的线程安全类 
    如AtomicLong,AtomicInteger等等 
    Atomicxxx 是通过Unsafe 类的native方法实现线程安全的
    C、BlockingQueue 和BlockingDeque 
    BlockingDeque接口继承了BlockingQueue接口, 
    BlockingQueue 接口的实现类有ArrayBlockingQueue ,LinkedBlockingQueue ,PriorityBlockingQueue 而BlockingDeque接口的实现类有LinkedBlockingDeque 
    BlockingQueue和BlockingDeque 都是通过使用定义为final的ReentrantLock作为类属性显式加锁实现同步的
    D、CopyOnWriteArrayList和 CopyOnWriteArraySet 
    CopyOnWriteArraySet的内部实现是在其类内部声明一个final的CopyOnWriteArrayList属性,并在调用其构造函数时实例化该CopyOnWriteArrayList,CopyOnWriteArrayList采用的是显式地加上ReentrantLock实现同步,而CopyOnWriteArrayList容器的线程安全性在于在每次修改时都会创建并重新发布一个新的容器副本,从而实现可变性。
    E、Concurrentxxx 
    最常用的就是ConcurrentHashMap,当然还有ConcurrentSkipListSet和ConcurrentSkipListMap等等。 
    ConcurrentHashMap使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访问容器,而是使用一种粒度更细的加锁机制——分段锁来实现更大程度的共享
    在这种机制中,任意数量的读取线程可以并发访问Map,执行读取操作的线程和执行写入操作的线程可以并发地访问Map,并且一定数量的写入线程可以并发地修改Map,这使得在并发环境下吞吐量更高,而在单线程环境中只损失非常小的性能
    F、ThreadPoolExecutor 
    ThreadPoolExecutor也是使用了ReentrantLock显式加锁同步
    G、Collections中的synchronizedCollection(Collection c)方法
    可将一个集合变为线程安全,其内部通过synchronized关键字加锁同步
 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值