【原理探究】女朋友问我ArrayList遍历时删除元素的正确姿势是什么?
本文已收录到1.1K Star数开源学习指南——《大厂面试指北》,如果想要了解更多大厂面试相关的内容及获取《大厂面试指北》离线PDF版,请扫描下方二维码码关注公众号“大厂面试”,谢谢大家了!
《大厂面试指北》最佳阅读地址:
http://notfound9.github.io/interviewGuide/
《大厂面试指北》项目地址:
https://github.com/NotFound9/interviewGuide
获取《大厂面试指北》离线PDF版,请扫描下方二维码关注公众号“大厂面试”
《大厂面试指北》项目截图:
简介
我们在项目开发过程中,经常会有需求需要删除ArrayList中的某个元素,而使用不正确的删除方式,就有可能抛出异常。或者在面试中,会遇到面试官询问遍历时如何正常删除元素。所以在本篇文章中,我们会对几种删除元素的方式进行测试,并对原理进行研究,希望可以帮助到大家!
ArrayList遍历时删除元素的几种姿势
首先结论如下:
第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)
第2种方法 - 普通for循环倒序删除(结果:正确删除)
第3种方法 - for-each循环删除(结果:抛出异常)
第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)
第5种方法 - Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)
下面让我们来详细探究一下原因吧!
首先初始化一个数组arrayList,假设我们要删除等于3的元素。
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(3);
arrayList.add(4);
arrayList.add(5);
removeWayOne(arrayList);
}
第1种方法 - 普通for循环正序删除(结果:会漏掉元素判断)
for (int i = 0; i < arrayList.size(); i++) {
if (arrayList.get(i) == 3) {//3是要删除的元素
arrayList.remove(i);
//解决方案: 加一行代码i = i - 1; 删除元素后,下标减1
}
System.out.println("当前arrayList是"+arrayList.toString());
}
//原ArrayList是[1, 2, 3, 3, 4, 5]
//删除后是[1, 2, 3, 4, 5]
输出结果:
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
可以看到少删除了一个3,
原因在于调用remove删除元素时,remove方法调用System.arraycopy()方法将后面的元素移动到前面的位置,也就是第二个3会移动到数组下标为2的位置,而在下一次循环时,i+1之后,i会为3,不会对数组下标为2这个位置进行判断,所以这种写法,在删除元素时,被删除元素a的后一个元素b会移动a的位置,而i已经加1,会忽略对元素b的判断,所以如果是连续的重复元素,会导致少删除。
解决方案
可以在删除元素后,执行i=i-1,使得下次循环时再次对该数组下标进行判断。
第2种方法 - 普通for循环倒序删除(结果:正确删除)
for (int i = arrayList.size() -1 ; i>=0; i--) {
if (arrayList.get(i).equals(3)) {
arrayList.remove(i);
}
System.out.println("当前arrayList是"+arrayList.toString());
}
输出结果:
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
当前arrayList是[1, 2, 4, 5]
当前arrayList是[1, 2, 4, 5]
当前arrayList是[1, 2, 4, 5]
这种方法可以正确删除元素,因为调用remove删除元素时,remove方法调用System.arraycopy()将被删除元素a后面的元素向前移动,而不会影响元素a之前的元素,所以倒序遍历可以正常删除元素。
第3种方法 - for-each循环删除(结果:抛出异常)
public static void removeWayThree(ArrayList<Integer> arrayList) {
for (Integer value : arrayList) {
if (value.equals(3)) {//3是要删除的元素
arrayList.remove(value);
}
System.out.println("当前arrayList是"+arrayList.toString());
}
}
输出结果:
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)
at com.test.ArrayListTest1.main(ArrayListTest1.java:24)
会抛出ConcurrentModificationException异常,主要在于for-each的底层实现是使用ArrayList.iterator的hasNext()方法和next()方法实现的,我们可以使用反编译进行验证,对包含上面的方法的类使用以下命令反编译验证
javac ArrayTest.java//生成ArrayTest.class文件
javap -c ArrayListTest.class//对class文件反编译
得到removeWayThree方法的反编译代码如下:
public static void removeWayThree(java.util.ArrayList<java.lang.Integer>);
Code:
0: aload_0
1: invokevirtual #12 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
4: astore_1
5: aload_1
6: invokeinterface #13, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 调用Iterator.hasNext()方法
11: ifeq 44
14: aload_1
15: invokeinterface #14, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;调用Iterator.next()方法
20: checkcast #9 // class java/lang/Integer
23: astore_2
24: aload_2
25: iconst_3
26: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
29: invokevirtual #10 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
32: ifeq 41
35: aload_0
36: aload_2
37: invokevirtual #15 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z
40: pop
41: goto 5
44: return
可以很清楚得看到Iterator.hasNext()来判断是否还有下一个元素,和Iterator.next()方法来获取下一个元素。而因为在删除元素时,remove()方法会调用fastRemove()方法,其中会对modCount+1,代表对数组进行了修改,将修改次数+1。
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;
}
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; // clear to let GC do its work
}
而当删除完元素后,进行下一次循环时,会调用下面源码中Itr.next()方法获取下一个元素,会调用checkForComodification()方法对ArrayList进行校验,判断在遍历ArrayList是否已经被修改,由于之前对modCount+1,而expectedModCount还是初始化时ArrayList.Itr对象时赋的值,所以会不相等,然后抛出ConcurrentModificationException异常。
那么有什么办法可以让expectedModCount及时更新呢?
可以看到下面Itr的源码中,在Itr.remove()方法中删除元素后会对 expectedModCount更新,所以我们在使用删除元素时使用Itr.remove()方法来删除元素就可以保证expectedModCount的更新了,具体看第5种方法。
private class Itr implements Iterator<E> {
int cursor; // 游标
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;//期待的modCount值
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//判断expectedModCount与当前的modCount是否一致
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;//更新expectedModCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
第4种方法 - Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
if (value.equals(3)) {//3是要删除的元素
arrayList.remove(value);
}
System.out.println("当前arrayList是"+arrayList.toString());
}
输出结果:
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.test.ArrayListTest1.removeWayFour(ArrayListTest1.java:61)
at com.test.ArrayListTest1.main(ArrayListTest1.java:25)
第3种方法在编译后的代码,其实是跟第4种是一样的,所以第四种写法也会抛出ConcurrentModificationException异常。这种需要注意的是,每次调用iterator的next()方法,会导致游标向右移动,从而达到遍历的目的。所以在单次循环中不能多次调用next()方法,不然会导致每次循环时跳过一些元素,我在一些博客里面看到了一些错误的写法,比如这一篇《在ArrayList的循环中删除元素,会不会出现问题?》文章中:
![image-20200101124822998](/Users/ruiwendaier/Library/Application Support/typora-user-images/image-20200101124822998.png)
先调用iterator.next()获取元素,与elem进行比较,如果相等,再调用list.remove(iterator.next());来移除元素,这个时候的iterator.next()其实已经不是与elem相等的元素了,而是后一个元素了,我们可以写个demo来测试一下
ArrayList<Integer> arrayList = new ArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
arrayList.add(5);
arrayList.add(6);
arrayList.add(7);
Integer elem = 3;
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(arrayList);
if(iterator.next().equals(elem)) {
arrayList.remove(iterator.next());
}
}
输出结果如下:
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 5, 6, 7]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.test.ArrayListTest1.main(ArrayListTest1.java:29)
可以看到移除的元素其实不是3,而是3之后的元素,因为调用了两次next()方法,导致游标多移动了。所以应该使用Integer value = iterator.next();将元素取出进行判断。
第5种方法 - Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)
Iterator<Integer> iterator = arrayList.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
if (value.equals(3)) {//3是需要删除的元素
iterator.remove();
}
}
输出结果:
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 3, 4, 5]
当前arrayList是[1, 2, 3, 4, 5]
当前arrayList是[1, 2, 4, 5]
当前arrayList是[1, 2, 4, 5]
当前arrayList是[1, 2, 4, 5]
可以正确删除元素。
跟第3种和第4种方法的区别在于是使用iterator.remove();来移除元素,而在remove()方法中会对iterator的expectedModCount变量进行更新,所以在下次循环调用iterator.next()方法时,expectedModCount与modCount相等,不会抛出异常。
HashMap遍历时删除元素的几种姿势
首先结论如下:
第1种方法 - for-each遍历HashMap.entrySet,使用HashMap.remove()删除(结果:抛出异常)。
第2种方法-for-each遍历HashMap.keySet,使用HashMap.remove()删除(结果:抛出异常)。
第3种方法-使用HashMap.entrySet().iterator()遍历删除(结果:正确删除)。
下面让我们来详细探究一下原因吧!
HashMap的遍历删除方法与ArrayList的大同小异,只是api的调用方式不同。首先初始化一个HashMap,我们要删除key包含"3"字符串的键值对。
HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
hashMap.put("key1",1);
hashMap.put("key2",2);
hashMap.put("key3",3);
hashMap.put("key4",4);
hashMap.put("key5",5);
hashMap.put("key6",6);
第1种方法 - for-each遍历HashMap.entrySet,使用HashMap.remove()删除(结果:抛出异常)
for (Map.Entry<String,Integer> entry: hashMap.entrySet()) {
String key = entry.getKey();
if(key.contains("3")){
hashMap.remove(entry.getKey());
}
System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);
}
输出结果:
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key1=1
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key2=2
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key5=5
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前entry是key6=6
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是key3=3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at com.test.HashMapTest.removeWayOne(HashMapTest.java:29)
at com.test.HashMapTest.main(HashMapTest.java:22)
第2种方法-for-each遍历HashMap.keySet,使用HashMap.remove()删除(结果:抛出异常)
Set<String> keySet = hashMap.keySet();
for(String key : keySet){
if(key.contains("3")){
keySet.remove(key);
}
System.out.println("当前HashMap是"+hashMap+" 当前key是"+key);
}
输出结果如下:
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key1
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key2
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key5
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key3=3, key4=4} 当前key是key6
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前key是key3
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at com.test.HashMapTest.removeWayTwo(HashMapTest.java:40)
at com.test.HashMapTest.main(HashMapTest.java:23)
第3种方法-使用HashMap.entrySet().iterator()遍历删除(结果:正确删除)
Iterator<Map.Entry<String, Integer>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if(entry.getKey().contains("3")){
iterator.remove();
}
System.out.println("当前HashMap是"+hashMap+" 当前entry是"+entry);
}
输出结果:
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key1=1
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key2=2
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key5=5
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key6=6
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4, deletekey=3} 当前entry是key4=4
当前HashMap是{key1=1, key2=2, key5=5, key6=6, key4=4} 当前entry是deletekey=3
第1种方法和第2种方法抛出ConcurrentModificationException异常与上面ArrayList错误遍历-删除方法的原因一致,HashIterator也有一个expectedModCount,在遍历时获取下一个元素时,会调用next()方法,然后对
expectedModCount和modCount进行判断,不一致就抛出ConcurrentModificationException异常。
abstract class HashIterator {
Node<K,V> next; // next entry to return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
PS:ConcurrentModificationException是什么?
根据ConcurrentModificationException的文档介绍,一些对象不允许并发修改,当这些修改行为被检测到时,就会抛出这个异常。(例如一些集合不允许一个线程一边遍历时,另一个线程去修改这个集合)。
一些集合(例如Collection, Vector, ArrayList,LinkedList, HashSet, Hashtable, TreeMap, AbstractList, Serialized Form)的Iterator实现中,如果提供这种并发修改异常检测,那么这些Iterator可以称为是"fail-fast Iterator",意思是快速失败迭代器,就是检测到并发修改时,直接抛出异常,而不是继续执行,等到获取到一些错误值时在抛出异常。
异常检测主要是通过modCount和expectedModCount两个变量来实现的,
modCount
集合被修改的次数,一般是被集合(ArrayList之类的)持有,每次调用add(),remove()方法会导致modCount+1expectedModCount
期待的modCount,一般是被Iterator(ArrayList.iterator()方法返回的iterator对象)持有,一般在Iterator初始化时会赋初始值,在调用Iterator的remove()方法时会对expectedModCount进行更新。(可以看看上面的ArrayList.Itr源码)
然后在Iterator调用next()遍历元素时,会调用checkForComodification()方法比较modCount和expectedModCount,不一致就抛出ConcurrentModificationException。
单线程操作Iterator不当时也会抛出ConcurrentModificationException异常。(上面的例子就是)
WechatIMG4995.jpeg
总结
因为ArrayList和HashMap的Iterator都是上面所说的“fail-fast Iterator”,Iterator在获取下一个元素,删除元素时,都会比较expectedModCount和modCount,不一致就会抛出异常。
所以当使用Iterator遍历元素(for-each遍历底层实现也是Iterator)时,需要删除元素,一定需要使用 Iterator的remove()方法 来删除,而不是直接调用ArrayList或HashMap自身的remove()方法,否则会导致Iterator中的expectedModCount没有及时更新,之后获取下一个元素或者删除元素时,expectedModCount和modCount不一致,然后抛出ConcurrentModificationException异常。
【原理探究】女朋友问我ArrayList遍历时删除元素的正确姿势是什么?的更多相关文章
- 【Java】List遍历时删除元素的正确方式
当要删除ArrayList里面的某个元素,一不注意就容易出bug.今天就给大家说一下在ArrayList循环遍历并删除元素的问题.首先请看下面的例子: import java.util.ArrayLi ...
- java list集合遍历时删除元素
转: java list集合遍历时删除元素 大家可能都遇到过,在vector或arraylist的迭代遍历过程中同时进行修改,会抛出异常java.util.ConcurrentModification ...
- java 集合list遍历时删除元素
本文探讨集合在遍历时删除其中元素的一些注意事项,代码如下 import java.util.ArrayList; import java.util.Iterator; import java.util ...
- [转】Python--遍历列表时删除元素的正确做法
转自:https://blog.csdn.net/cckavin/article/details/83618306 一.问题描述 这是在工作中遇到的一段代码,原理大概和下面类似(判断某一个元素是否 ...
- 遍历list时删除元素的正确做法
我们往往会遇到需要删除list中满足条件的元素.举例: List<string> list_str =new List<string>() { "A",&q ...
- Java遍历时删除List、Set、Map中的元素(源码分析)
在对List.Set.Map执行遍历删除或添加等改变集合个数的操作时,不能使用普通的while.for循环或增强for.会抛出ConcurrentModificationException异常或者没有 ...
- 【转】ArrayList循环遍历并删除元素的常见陷阱
转自:https://my.oschina.net/u/2249714/blog/612753?p=1 在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出b ...
- ArrayList循环遍历并删除元素的常见陷阱
在工作和学习中,经常碰到删除ArrayList里面的某个元素,看似一个很简单的问题,却很容易出bug.不妨把这个问题当做一道面试题目,我想一定能难道不少的人.今天就给大家说一下在ArrayList循环 ...
- Java中ArrayList循环遍历并删除元素的陷阱
ava中的ArrayList循环遍历并且删除元素时经常不小心掉坑里,昨天又碰到了,感觉有必要单独写篇文章记一下. 先写个测试代码: import java.util.ArrayList; public ...
随机推荐
- 1. esc 安装 jenkins
$ yum install yum-fastestmirror -y #安装自动选择最快源的插件 #添加jenkins源: $ sudo wget -O /etc/yum.repos.d/jenkin ...
- Linux-监控与安全运维之zabbix
zabbix: Zabbix是一个开源分布式监控平台,包含诸多监控功能,用于构建一个符合企业级的监控解决方案.软件由开源社区提供开发和维护,遵循GPL协议,可以自由传播和使用,但开发团队提供收费的技术 ...
- SVM之不一样的视角
在上一篇学习SVM中 从最大间隔角度出发,详细学习了如何用拉格朗日乘数法求解约束问题,一步步构建SVM的目标函数,这次尝试从另一个角度学习SVM. 回顾监督学习要素 数据:(\(x_i,y_i\)) ...
- ansible的剧本play(四)
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAA68AAAETCAYAAADZDzDOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjw
- 2019-2020-1 20199329《Linux内核原理与分析》第十二周作业
<Linux内核原理与分析>第十二周作业 一.本周内容概述: 通过编程理解 Set-UID 的运行机制与安全问题 完成实验楼上的<SET-UID程序漏洞实验> 二.本周学习内容 ...
- SpringBoot应用操作Rabbitmq
记录RabbitMQ的简单应用 1.springboot项目中引入maven包,也是springboot官方的插件 <dependency> <groupId>org.spri ...
- 还在写CURD?试试这款基于mybatis-plus的springboot代码生成器
目录 ⚡Introduction ✔️Release Features Quick Start Examples 1.Controller模板代码示例 2.Service模板代码示例 3.Servic ...
- vuex vue-devtools 安装
vue-devtools是一款基于chrome游览器的插件,用于调试vue应用,这可以极大地提高我们的调试效率.接下来我们就介绍一下vue-devtools的安装 chrome商店直接安装 谷歌访问助 ...
- Vagrant (二) - 日常操作
立即上手 上一节中,我们介绍了怎样安装 Vagrant,安装本身并不困难.本章节中我们首先要快速上手,以便获得一个直观的概念: 建立一个工作目录 打开命令行工具,终端工具,或者iTerm2等,建立一个 ...
- 使用Node.js的http-serve搭建本地服务器
为什么要使用它? 首先,类似于vue-cli创建的项目,都能够实现浏览器中自动刷新,实时查看项目效果.其中的原理在于,webpack这样的工具启动了一个本地服务器,将本机当作一台服务器,这样在浏览器中 ...