一个分类,两个问题之ArrayList
前段时间,在做一个商品的分类,分类有3级,类似于以下这种形式的:
---食物
---蔬菜
---白菜
---材料
---鸡肉
.......
而我需要做的是将取得的一个商品的字符串类型的分类ID集,然后在前台显示其分类。显示的模式就像这样的:"口味:甜、酸;材料:鸡肉、牛肉..."。商品的分类是可能多选的,而且所选分类也不一定就是一个大分类里面的,所以就必须在取得分类集合后进行同类型分类。而根据同类型分类的依据就是该分类的最大父类与其他分类是相同的。在其获得的分类中,有一个getParent()方法可以获取父类对象,当不存在父类类型的时候,会取得空值。
我首先想到的是把字符串ID集转换成分类集合是第一步。即String[] productCategories => List<ProductCategory>。这步很简单,不多说明。
第二步,我最初的想法是,建立一个List<List<ProductCategory>>集合,这个集合里面存放分类集合,一个元素作为同一类型存放。先将转换好的List<ProductCategory>集合copy一份,然后用这两个集合两层循环嵌套比较,
代码类似下面的这样的:
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
List<String> list2 = list;
List<List<String>> bigCate = new ArrayList<List<String>>();
for (String str : list) {
List<String> cate = new ArrayList<String>();
cate.add(str);
for (String str2 : list2) {
if(str.equals(str2)){
cate.add(str2);
list.remove(str2);
}
}
bigCate.add(cate);
}
System.out.println(bigCate);
这样就把相同分类比较出来了,然后存进List<List<ProductCategory>>集合中。可是并不是那么简单,其中有两个问题需要解决,也是我对java面向对象理解不深刻的地方。
其一:java对象都是引用对象,java没有指针,并不是真的没有指针,而是被封装起来了,我们没有接触到而已。所以把集合copy一份,仅仅是把已经存在于stack中的指向存放集合数据的地址copy了一份。所以在执行remove方法时,实际上也移除了list2集合的元素。在行14的操作中我想在copy的集合中移除掉已经分类好的元素,结果报出java.util.ConcurrentModificationException异常。
其二:java中List集合的安全机制。list集合在增加元素的时候,查看ArrayList的源码中,可以看到以下代码:
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
而其中的ensureCapacity(size+1)方法是增加modcount变量值的。即以下代码:
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;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
那么我在执行for-each循环的时候,又移除元素(实际上循环遍历与移除都是同一个对象),那么在remove(object)的时候,又是执行什么操作的呢,在源码中发现:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
其实内部是先用普通for循环遍历,然后if判断找出元素索引,然后再执行fastRemove(index)。那我们再看看这个fastRemove(int index)方法是什么样的:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
我们发现它也执行了modCount++操作,那么在增加元素和移除元素的时候,这个modCount都在执行自增操作,那么到这里,你可能已经猜到一些原因了。我们继续查看源码,ArrayList执行for-each迭代时候,实际上是实现了一个Iterator接口的方法,iterator有几个方法,代码如下:
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
那么在AbstractList中,它使用了一个内部类来实现Iterator:
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
它在内部定义了一个游标变量cursor,通过游标来获取集合元素并且判断集合是否还有下一个值,确定集合执行hasNext()方法之后还有数据,然后会执行next()方法取得下一个值。我们还观察到,在这个迭代器中第6行,它定义了一个变量int expectedModCount = modCount;那么从我刚才写的那个分类来看,在第二次for-each循环中,在list执行了remove方法之后,modCount已经等于5了。但是在一开始执行for-each迭代器已经将modCount变量赋值给expectedModCount了,那个时候集合还没有执行remove方法,此时modCount=4。那么在next()方法中它首先会调用一个checkForComodification()方法, checkForComodification()方法的代码如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
看到没有,它会检查modCount是否等于expectedModCount,如果不相等,说明集合在迭代的时候结构发生了变化,然后抛出ConcurrentModificationException异常。
整个过程就是这样的。也许看起来是比较简单的,但是我觉得了解java的一些原理和机制,学习一下它里面的数据结构也并不是什么坏事。
本篇纯原创,转载请注明出处。最近经常用到集合,java为集合提供了很多简洁快捷的方法。所以没事就随意看了下java源码,如有不同见解,欢迎交流!谢谢!
一个分类,两个问题之ArrayList的更多相关文章
- js字符串长度计算(一个汉字==两个字符)和字符串截取
js字符串长度计算(一个汉字==两个字符)和字符串截取 String.prototype.realLength = function() { return this.replace(/[^\x00-\ ...
- 性能优化(一个)Hibernate 使用缓存(一个、两、查询)提高系统性能
在hibernate有三种类型的高速缓存,我们使用最频繁.分别缓存.缓存和查询缓存.下面我们使用这三个缓存中的项目和分析的优点和缺点. 缓存它的作用在于提高性能系统性能,介于应用系统与数据库之间而存在 ...
- 同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId
同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId >>>>>>>>>>>>>& ...
- Python selenium 一个节点两个关联input
HTML代码: 一个节点两个关联input 多出现于密码框 先需要模拟点击进入第一个input,才能激活第二个input. 代码: driver.find_element_by_name('Text ...
- 一个月薪两万的Web安全工程师要掌握哪些技能?
作为一个薪水两万起步的工作,我想知道这些牛人们都会哪些技能呢? Web安全相关概念.熟悉渗透相关工具.渗透实战操作.关注安全圈动态.熟悉Windows/Kali Linux.服务器安全配置.脚本编程学 ...
- 一个label两种颜色,一个label两种字体
-(void)addLabel{ UILabel *label = [[UILabel alloc]init]; label.backgroundColor = [UIColor grayColor] ...
- NSIS编译报错:您可能有有一个或两个(大)的旧临时文件
一.有时在编译NSIS时会出现如下错误: 注意: 您可能有有一个或两个(大)的旧临时文件 残留在临时目录文件夹中 (通常这种情况只会发生在 Windows 9x 系统中). 二.本人遇到的问题原因: ...
- wordpress通过$wpdb获取一个分类下所有的文章
在wordpress程序根目录下新建一个php文件,粘贴下面的代码 如下面的代码注释,修改$CID这个分类id,就可以获取这个分类下的文章了.这个查询需要联合三个表wp_posts.wp_term_r ...
- ApachShiro 一个系统 两套验证方法-(后台管理员登录、前台App用户登录)同一接口实现、源码分析
需求: 在公司新的系统里面博主我使用的是ApachShiro 作为安全框架.作为后端的鉴权以及登录.分配权限等操作 管理员的信息都是存储在管理员表 前台App 用户也需要校验用户名和密码进行登录.但是 ...
随机推荐
- NSFetchedResultController与UITableView
1 #import "AppDelegate.h" #import "Book.h" @interface AppDelegate () @end @imple ...
- 四月兄弟AprilBeacon
硬件相关ibeacon https://www.aprbrother.com/
- 用vim写python脚本的自动缩进格式设置
- 算法总结之 构造数组MaxTree
一个数组的MaxTree定义如下: 数组必须没有重复元素 MaxTree是一颗二叉树,数组的每一个值对应一个二叉树的节点 包括MaxTre树在内且在其中的每一颗子树上,值最大的节点都是树的头 给定一个 ...
- netty上手
关于netty的基础NIO,请参见:NIO原理及实例 下面介绍Netty的上手使用: 首先为项目添加jar依赖: <dependency> <groupId>io.netty& ...
- jQuery计时器插件
/** * 定义一个jQuery计时插件,实现记录计时开始时间.结束时间,总共耗时的功能 * @param $ * * @author Ivan 2862099249@qq.com * @date 2 ...
- HBase-存储-KeyValue格式
HBase-存储-KeyValue格式 本质上,HFile中的每个KeyValue都是一个低级的字节数组,它允许零复制访问数据. KeyValue格式如下 该结构以两个分别表示键长度(Key Leng ...
- JavaWeb -- 邮件收发
smtp协议: telnet smtp.qq.com 25 ehlo kevin auth login eGlhbmdqaWU1NUBxcS5jb20= a2V2aW5feGlhbmc1NQ== ma ...
- 将本地jar包制作成maven依赖
首先,需要配置maven环境,如果没配置,传送门:http://www.cnblogs.com/hyyq/p/6557683.html 然后,需要一个ben地的jar包,这里以阿里云支付宝sdk为例, ...
- 求两个有序序列合并成新有序序列的中位数,求第k小数
此算法涉及一个重要数学结论:如果A[k/2-1]<B[k/2-1],那么A[0]~A[k/2-1]一定在第k小的数的序列当中,可以用反证法证明. 算法思想如下: 1,假设A长度为m,B长度为n, ...