改善java程序之数组和集合1
来源:互联网 发布:淘宝上便宜衣服能买吗 编辑:程序博客网 时间:2024/05/16 08:05
60 性能考虑,数组是首选
package qz.test.equals;
import java.util.List;
public class ArrayTest {
public static void main(String[] args) {
}
public static int sum(int[] datas){
int sum = 0;
for (int i = 0; i < datas.length; i++) {
sum += datas[i];
}
return sum;
}
public static int sum(List<Integer> datas){
int sum = 0;
for (int i = 0; i < datas.size(); i++) {
sum += datas.get(i);
}
return sum;
}
}
基本类型是在栈内存中操作的,而对象则是在堆内存中操作的,栈内存的特点是速度快,容量小,堆内存的特点是速度慢,容量大。
61 若有必要,使用变长数组
public static <T> T[] expandCapacity(T[] datas,int newLen){
//不能是负值
newLen = newLen < 0 ? 0 : newLen;
//生成一个新数组,并拷贝原值
return Arrays.copyOf(datas,newLen);
}
62 警惕数组的浅拷贝
package qz.test.equals;
import java.util.Arrays;
public class Client {
public static void main(String[] args) {
//气球数量
int ballonNum = 7;
//第一个箱子
Ballon[] box1 = new Ballon[ballonNum];
//初始化第一个箱子中的气球
for (int i = 0; i < box1.length; i++) {
box1[i] = new Ballon(Color.values()[i],i);
}
//第二个箱子的气球是拷贝的第一个箱子里的
Ballon[] box2 = Arrays.copyOf(box1, box1.length);
//修改最后一个气球颜色
box2[6].setColor(Color.Blue);
//打印出第一个箱子中的气球颜色
for (Ballon ballon : box1) {
System.out.println(ballon);
}
}
}
//气球颜色
enum Color {
Red,Orange,Yellow,Green,Indigo,Blue,Violet;
}
//气球
class Ballon{
//编号
private int id;
//颜色
private Color color;
public Ballon(Color _color,int _id){
id = _id;
color = _color;
}
//apache-common包下的ToStringBuilder重写toString方法
@Override
public String toString(){
return new ToStringBuilder(this).append("编号",id).append("颜色",color).toString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
}
编号:0,颜色:Red
编号:1,颜色:Orange
编号:2,颜色:Yellow
编号:3,颜色:Green
编号:4,颜色:Indigo
编号:5,颜色:Blue
编号:6,颜色:Blue
通过copyOf方法产生的数组是一个浅拷贝,这与序列化的浅拷贝完全相同:基本类型是直接拷贝值,其他都是拷贝引用地址。数组的clone方法也是与此相同,同样是浅拷贝而且集合的clone方法也都是浅拷贝。
63 在明确的场景下,为集合指定初始容量
ArrayList的add实现
public boolean add(E e){
//扩展长度
ensureCapacity(size + 1);
//追加元素
elementData[size++] = e;//数组存储
return true;
}
public void ensureCapacity(int minCapacity){
//修改计数器
modCount++;
//上次(原始)定义的数组长度
int oldCapacity = elementData.length;
//当前需要的长度超过了数组长度
if(minCapacity > oldCapacity){
Object oldData[] = elementData;
//计算新数组长度
int newCapacity = (OldCapacity * 3) / 2 + 1;
if(newCapacity < minCapacity)
newCapacity = minCapacity;
//数组拷贝,生成新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
elementData的默认长度是10,ArrayList的无参构造:
public ArrayList(){
//默认是长度为10的数组
this(10);
}
//指定数组长度的有参构造
public ArrayList(int initialCapacity){
super();
if(initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
//声明指定长度的数组,容纳element
this.elementData = new Object[initialCapacity];
}
Vector的处理方式与ArrayList像素,只是数组的长度计算方式不同而已
private void ensureCapacityHelper(int minCapacity){
int oldCapacity = elementData.length;
if(minCapacity > oldCapacity){
Object[] oldData = elementData;
//若有递增步长,则按照步长增长;否则,扩容两倍
int newCapacity = (capacityIncrement > 0) ? (olcCapacity + capacityIncrement) : (oldCapacity * 2);
//越界检查,否则超过int最大值
if(newCapacity < minCapacity)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
64 多种最值算法,适时选择
//(1)自行实现,快速查找最大值 速度最快的算法
public static int max(int[] data){
int max = data[0];
for (int i : data) {
max = max > i ?max : i;
}
return max;
}
//(2)先排序,后取值
public static int max(int[] data){
//先排序
Arrays.sort(data.clone());//数组也是一个对象,不拷贝就改变了原有数组元素的顺序
//然后取值
return data[data.length - 1];
}
//(3)先剔除重复数据,然后再排序
public static int getSecond(Integer[] data){
//转换为列表
List<Integer> dataList = Arrays.asList(data);
//转换为TreeSet,删除重复元素并升序排列
TreeSet<Integer> ts = new TreeSet<Integer>(dataList);
//取出比最大值小的最大值,第二大
return ts.lower(ts.last());
}
注意:最值计算时使用集合最简单,使用数组性能最优
65 避开基本类型数组转换列表陷阱
Arrays.asList()的方法说明:输入一个变长参数,返回一个固定长度的列表
public static <T> List<T> asList(T... a){
return new ArrayList<T>(a);
}
asList方法输入的是一个泛型变长参数,基本类型是不能泛型化的,也就是说8个基本类型不能作为泛型参数,要想作为泛型参数就必须使用其所对应的包装类型。
注意:原始类型数组不能作为asList的输入参数,否则会引起程序逻辑混乱。
66 asList方法产生的List对象不可更改
因为asList方法直接new了一个ArrayList对象返回,此ArrayList非java.util.ArrayList,而是Arrays工具类的一个内置类,其构造函数如下:
//这是一个静态私有内部类
private static class ArrayList<E> extends AbstractList<E> implements RandomAccess,java.io.Serializable{
//存储列表元素的数组
private final E[] a;
//唯一的构造函数
ArrayList(E[] array){
if(null == array)
throw new NullPointerException();
a = array;
}
@Override
public E get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
}
这里的ArrayList是一个静态私有内部类,除了Arrays能访问外,其他类都不能访问。这个类没有提供add方法,父类AbstractList提供了(但没有提供具体的实现):
public boolean add(E e){
throw new UnsupportedOperationException();
}
ArrayList静态内部类,仅仅实现了5个方法:
size:元素数量
toArray:转化为数组,实现了数组的浅拷贝
get:获取指定元素
set:重置某一个元素值
contains:是否包含某元素
对于我们经常使用的List.add和List.remove方法它都没有实现,也就是说asList返回的是一个长度不可变的列表,数组是多长,转换成的列表也就是多长,换句话说此处的列表只是数组的一个外壳,不再保持列表动态变长的特性。
通过如下方式定义和初始化列表是不可取的:
List<String> names = Arrays.adList("张三","李四","王五");
因为列表的长度无法修改。
67 不同的列表选择不同的遍历方法
ArrayList数组实现了RandomAccess接口(随机存取接口),标志着ArrayList是一个可以随机存取的列表。在Java中,RandomAccess和Cloneable、Serializable一样,都是标志性接口,不需要任何实现,只是用来表明其实现类具有某种特质的,实现了Cloneable表明可以被拷贝,实现了Serializable接口表明被序列化了,实现了RandomAccess则表明这个了可以随机存取;ArrayList数据元素之间没有关联,即两个位置相邻的元素之间没有相互依赖和索引关系,可以随机访问和存储;因此ArrayList采用下标方式遍历列表速度会更快。
LinkedList采用下标方式(get方法访问元素)遍历元素源码:
public E get(int index{
return entry(index).element;
}
private Entry<E> entry(int index){
/* 检查下标是否越界 */
Entry<E> e = header;
if(index < (size >> 1)){
//如果下标小于中间值。则从头节点开始搜索
for (int i = 0; i <= index; i++)
e = e.next;
}else{
//如果下标大于等于中间值,则从尾节点反向遍历
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
重构后的average方法代码如下:
public static int average(List<Integer> list){
int sum = 0;
//可以随机存取,则使用下标遍历
if(list instanceof RandomAccess){
for(int i = 0,size = list.size();i < size;i++)
sum += list.get(i);
}else{
//有序存取,使用foreach方式
for (int i : list) {
sum += i;
}
}
//除以人数,计算平均值
return sum / list.size();
}
68 频繁插入和删除时使用LinkedList
(1)插入元素
public void add(int index,E element){
/* 检查下标是否越界,代码不再拷贝 */
//若需要扩容,则增大底层数组的长度
ensureCapacity(size + 1);
//给index下标之后的元素(包括当前元素)的下标加1,空出index位置
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//赋值index位置原色
elementData[index] = element;
//列表长度+1
size++;
}
arraycopy方法只要是插入一个元素,其后的元素就会向后移动一位,频繁的插入,每次后面的元素都要拷贝一遍,效率就会变低,特别是在头位置插入元素时;可使用LinkedList类,LinkedList是一个双向链表,它的插入只是修改相邻元素的next和previous引用,其插入算法如下:
public void add(int index,E element){
addBefore(element,(index == size ? header : entry(index)));
}
private Entry<E> addBefore(E e,Entry<E> entry){
//组装一个新节点,previous指向原节点的前节点,next指向原节点
Entry<E> newEntry = new Entry<E>(e,entry,entry.previous);
//前节点的next指向自己
newEntry.previous.next = newEntry;
//后节点的previous指向自己
newEntry.next.previous = newEntry;
//长度+1
size++;
//修改计数器+1
modCount++;
return newEntry;
}
(2)删除元素
ArrayList提供了删除指定位置上额元素、删除指定值元素、删除一个下表范围内的元素集等删除动作,三者的实现原理基本相似,都是找到索引位置,然后删除,以remove方法为例,源码如下:
public E remove(int index){
//下标校验
RangeCheck(index);
//修改计数器+1
modCount++;
//记录要删除的元素值
E oldValue = (E) elementData(index);
//有多少个元素向前移动
int numMoved = size - index - 1;
if(numMoved > 0)
//index后的元素向前移动一位
System.arraycopy(elementData, index + 1, elementData, index, numMoved);
//列表长度减1,并且最后一位设为null
elementData[--size] = null;
//返回删除的值
return oldValue;
}
index位置后的 元素都向前移动了一位,最后一位空出来了,这又是一次数组拷贝,和插入一样,ArrayList其他的两个删除方法与此相似。
LinkedList提供了非常多的删除操作,比如删除指定位置元素、删除头元素等,与之相关的poll方法也会执行删除动作,删除指定位置元素的方法remove,源代码如下:
private E remove(Entry<E> e){
//取得原始值
E result = e.element;
//前节点next指向当前节点的next
e.previous.next = e.next;
//后节点的previous指向当前节点的previous
e.next.previous = e.previous;
//置空当前节点的next和previous
e.next = e.previous = null;
//当前元素置空
e.element = null;
//列表长度减1
size--;
//修改计数器+1
modCount++;
return result;
}
这也是双向链表的标准删除算法,没有任何耗时的操作,全部是引用指针的变更,效率高。
(3)修改元素
修改元素值这一点LinkedList输给了ArrayList,这是因为LinkedList是顺序存取的,因此定位元素必然是一个遍历过程,效率大打折扣,set方法的源码如下:
public E set(int index,E element){
//定位节点
Entry<E> e = entry(index);
E oldVal = e.element;
//节点的元素替换
e.element = element;
return oldVal;
}
这里使用了entry方法定位元素,LinkedList这种顺序存取列表的元素定位方式会折半遍历,这是一个极耗时的操作;而ArrayList的修改动作则是数组元素的直接替换,简单高效。
69 列表相等只需要关心元素数据
public static void main(String[] args) {
ArrayList<String> strs = new ArrayList<String>();
strs.add("A");
Vector<String> strs2 = new Vector<String>();
strs2.add("A");
System.out.println(strs.equals(strs2));
}
两个类不同,结果相同:两者都是列表,都实现了List接口,也都继承了AbstractList抽象类,其equals方法是在AbstractList中定义的,源码如下:
public boolean equals(Object o){
if(this == o)
return true;
//是否是List列表,注意这里:只要实现List接口即可
if(!(o instanceof List))
return false;
//通过迭代器访问list的所有元素
ListIterator<E> e1 = (ListIterator<E>) ((List) this).listIterator();
ListIterator e2 = (ListIterator) ((List) o).listIterator();
//遍历两个list元素
while(e1.hasNext() && e2.hasNext()){
E o1 = null;
try {
o1 = e1.next();
} catch (SAXException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
Object o2 = null;
try {
o2 = e2.next();
} catch (SAXException e) {
e.printStackTrace();
} catch (JAXBException e) {
e.printStackTrace();
}
//只要存在着不相等就退出
if(!(null == o1 ? null == o2 : o1.equals(o2)))
return false;
}
//长度是否也相等
return !(e1.hasNext() || e2.hasNext());
}
只要所有的元素相等,并且长度也相等就表明两个List是相等的,与具体的容量类型无关。
其他的集合类型,如Set、Map等与此相同,也是只关心集合元素,不用考虑集合类型。
70 子列表只是原列表的一个视图
List接口提供了subList方法,其作用是返回一个列表的子列表,这与String类的subString有点类似,
public static void main(String[] args) {
//定义一个包含两个字符串的列表
List<String> c= new ArrayList<String>();
c.add("A");
c.add("B");
//构造一个包含c列表的字符串列表
List<String> c1 = new ArrayList<String>(c);
//subList生成与c相同的列表
List<String> c2 = c.subList(0, c.size());
//c2增加一个元素
c2.add("C");
System.out.println("c == c1 ? " + c.equals(c1));
System.out.println("c == c2 ? " + c.equals(c2));
}
c == c1 ? false
c == c2 ? true
String类的subString方法
public static void main(String[] args) {
String str = "AB";
String str1 = new String(str);
String str2 = str.substring(0) + "c";
System.out.println("str == str1 ? " + str1.equals(str1));
System.out.println("str == str2 ? " + str1.equals(str2));
}
str与str1是相等的(虽然不是同一个对象,但用equals方法判断是相等的),但它们与str2不相等,因为str2在对象池中重新生成了一个新的对象,其表面值是ABC,那当然与str和str1不相等。
str == str1 ? true
str == str2 ? false
subList源码如下:
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList<E>(this, fromIndex, toIndex) :
new SubList<E>(this, fromIndex, toIndex));
}
subList方法是由AbstractList实现的,它会根据是不是可以随机存取来提供不同的SubList实现方式,RandomAccessSubList也是SubList子类,所以所有的操作都是由SubList类实现的(除了自身的SubList方法外),SubList类的代码如下:
class SubList<E> extends AbstractList<E> {
//原始列表
private AbstractList<E> l;
//偏移量
private int offset;
//构造函数,注意list参数就是我们的原始列表
SubList(AbstractList<E> list, int fromIndex, int toIndex){
/* 下标校验,略 */
//传递原始列表
l = list;
offset = fromIndex;
//子列表的长度
size = toIndex - fromIndex;
}
//获得指定位置的元素
public E get(int index){
/* 校验部分,略 */
//从原始字符串中获得指定位置的元素
return l.get(index + offset);
}
//增加或插入
public void add(int index,E element){
/* 校验部分,略 */
//直接增加到原始字符串上
l.add(index + offset, element);
/* 处理长度和修改计数器 */
}
@Override
public int size() {
return 0;
}
}
subList方法的实现原理:它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图(View),所有的修改动作都反映在了原列表上。
c与c1不相等:因为通过ArrayList构造函数创建的List对象c1实际上是新列表,它是通过数组的copyOf动作生成的,所生成的列表c1与原列表c之间没有任何关系(虽然是浅拷贝,但元素类型是String,也就是说元素是深拷贝的)。
71 推荐使用subList处理局部列表
public static void main(String[] args) {
//初始化一个固定长度,不可变列表
List<Integer> initData = Collections.nCopies(100, 0);
//转换为可变列表
List<Integer> list = new ArrayList<Integer>(initData);
//遍历,删除符合条件的元素
for (int i = 0, size = list.size(); i < size; i++) {
if(i >= 20 && i < 30)
list.remove(i);
}
for (int i = 20; i < 30; i++) {
if(i < list.size())
list.remove(i);
}
}
使用subList方法实现:
public static void main(String[] args) {
//初始化一个固定长度,不可变列表
List<Integer> initData = Collections.nCopies(100, 0);
//转换为可变列表
ArrayList<Integer> list = new ArrayList<Integer>(initData);
//删除指定范围的元素
list.subList(20, 30).clear();
}
用subList先取出一个子列表,然后清空;因为subList返回的List是原始列表的一个视图,删除这个视图中的所有元素,最终就会反映到原始字符串上。
72 生成子列表后不要再操作原列表
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
List<String> subList = list.subList(0, 2);
//原字符串增加一个元素
list.add("D");
System.out.println("原列表长度:" + list.size());
System.out.println("子列表长度:" + subList.size());
}
结果为:
原列表长度:4
Exception in thread "main" java.util.ConcurrentModificationException
出现这个问题的最终原因还是在子列表提供的size方法的检查上(修改计数器),size的源代码:
public int size(){
checkForComodification();
return size;
}
private void checkForComodification(){
//判断当前修改计数器是否与子列表生成时一致
if(l.modCount != expectedModCount)
throw new ConcurrentModificationException();
}
expectedModCount是在SubList子列表的构造函数中赋值的,其值等于生成子列表时的修改次数(modCount变量)。因此在生成子列表后在修改原始列表,l.modCount的值就必然比expectedModCount大1,不再保持相等了,于是就抛出了异常。
对于子列表操作,因为视图是动态生成的,生成子列表后再操作原列表,必然会导致“视图”的不稳定,最有效的办法就是通过Collections.unmodifiableList方法设置列表为只读状态,代码如下:
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
List<String> subList = list.subList(0, 2);
//设置列表为只读状态
list = Collections.unmodifiableList(list);
//对list进行只读操作
doReadSomething(list);
//对subList进行读写操作
doReadAndWriteSomething(subList);
}
注意:subList生成子列表后,保持原列表的只读状态。
0 0
- 改善java程序之数组和集合
- 改善java程序之数组和集合1
- 改善java程序的151个建议--数组和集合
- 读书笔记--编写高质量代码:改善java程序的151个建议(五)数组和集合
- 改善java程序之注解和枚举
- 改善java程序之java通用方法和准则
- 改善java程序之字符串
- Java基础基础之数组和集合(1)
- java集合和数组
- java数组和集合
- Java中的数组和集合(1)
- Java之数组array和集合list、set、map
- Java之数组array和集合list、set、map
- Java之数组array和集合list、set、map
- Java之数组和集合相互转化-yellowcong
- java详解 --- 集合之数组实现和链表实现
- java之数组与集合
- java程序优化之有助于改善性能的技巧
- c primer plus第16章总结:C预处理器和C库
- uva 658 bug and feature
- 5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果
- HDOJ 2717 Catch That Cow
- Log4j详细使用教程
- 改善java程序之数组和集合1
- 求和
- Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)
- C++位操作总结
- pdo预处理案例
- 图像的矩特征(转自http://www.cnblogs.com/pengkunfan/p/3998921.html)
- 挖个坑,没事就填
- ActiveMQ详细应用实例与配置
- Android五种布局方式——LinearLayout、RelativeLayout、TableLayout....(四)