前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ArrayList 源码解析

ArrayList 源码解析

作者头像
飞翔的竹蜻蜓
发布于 2020-07-08 06:11:34
发布于 2020-07-08 06:11:34
34600
代码可运行
举报
运行总次数:0
代码可运行

今天就来说说我们开发中常用的ArrayList源码

ArrayList几个重要的成员变量

String源码解析开篇一样,我们还是先来看看ArrayList类的基本结构。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 数组第一个 add 时候的长度,默认是 10,;
    private static final int DEFAULT_CAPACITY = 10;
  
    //表示当前数组的长度;
 		private int size;
  
  	// 保存集合数据的数组
    transient Object[] elementData; // non-private to simplify nested class access
  
    //  这个集合修改结构的次数
    protected transient int modCount = 0;
}

这里来说说这几个成员变量具体代表什么意思。

DEFAULT_CAPACITY: 数组的初始大小

size: 当前数组的大小(实时),类型 int,没有使用 volatile 修饰,非线程安全的

elementData: 用来保存集合数据的数组,从这个成员变量我们就可以看出来ArrayList的本质就是一个数组。

modCount: 数组结构发生改变的次数。比如说:扩容

如果ArrayList的本质是一个数组,数组结果一旦被创建之后就不能被改变了,那么ArrayList是怎么实现动态扩容的

从上文的分析可以得到ArrayList的本质就是一个数组,初始为空数组,当第一次add 的时候会变成一个长度为10 的数组,那么我们可以得到下图的ArrayList结构图示。

ArrayList的类注释

要看一个类的源码,就必须的先看看这个类的类注释。由于ArrayList的类注释很长,且都是英文的,为了节省大家的时间,我将其翻译成英文并提取出重要信息如下。

  • ArrayList继承自List接口,且可以存储null值,可自动扩容;
  • size、isEmpty、get、set、iterator和listIterator几个方法的时间复杂度都是O(1);
  • ArrayList不是线程安全的,推荐使用线程安全类:Collections#synchronizedList;
  • 增强 for 循环,或者使用迭代器迭代过程中,如果数组大小被改变,会快速失败,抛出异常。

ArrayList类注释大概的意思就是上述的几条,后续涉及的我们在后面详细讲解。

初始化 ArrayList

ArrayList 类有三个重载构造函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 默认构造器, 直接初始化, 默认大小为空
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 使用已存在的集合初始化ArrayList
public ArrayList(Collection<? extends E> c) {
  // elementData 是保存数组的容器,默认为 null
  elementData = c.toArray();
  // 给定的集合 c 有值
  if ((size = elementData.length) != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    // 如果给定的集合不是 Object 类型, 我们会转成 Object 
    if (elementData.getClass() != Object[].class)
      elementData = Arrays.copyOf(elementData, size, Object[].class);
  } else {
    // 给定集合无值,则默认空数组
    this.elementData = EMPTY_ELEMENTDATA;
  }
}

// 指定 ArrayList 初始长度
public ArrayList(int initialCapacity) {
  if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
  } else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
  } else {
    throw new IllegalArgumentException("Illegal Capacity: "+
                                       initialCapacity);
  }
}

构造函数说明

  1. 默认构造函数初始化的时候,会指定 ArrayList 的长度为0, 也就是说初始时候的数组是空数组,而不是10, 10 是第一次 add 的时候数组的长度。
  2. 第 12 行的时候,有一行注释。这行注释表示这里有一个bug,大致的意思就是。当给定集合内的元素不是 Object 类型时,我们会转化成 Object 的类型。一般情况下都不会触发此 bug,只有在下列场景下才会触发:ArrayList 初始化之后(ArrayList 元素非 Object 类型),再次调用 toArray 方法,得到 Object 数组,并且往 Object 数组赋值时,才会触发此 bug,代码和原因如图:

15行的地方打印出了String[], 说明这个 arr的泛型是 String

官方查看文档地址:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652,问题在 Java 9 中被解决。

ArrayList 的 add 方法和 扩容

新增有两步:

  1. 确保数组的长度是否足够,如果不够,则需要扩容至原先长度的1.5倍
  2. 新增数据到数组

这两步我们可以直接从下面源码中体现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean add(E e) {
  // 确保数组长度足够, size 是数组的长度
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  // 数组新增数据
  elementData[size++] = e;
  return true;
}

注重来看第一步,这一步主要是一个方法 ensureCapacityInternal(size),我们现在来看看这个方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void ensureCapacityInternal(int minCapacity) {
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

看到这里主要使用了两个方法calculateCapacityensureExplicitCapacitycalculateCapacity方法是获当前数组的容量;ensureExplicitCapacity方法是确保数组的容量足够。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  return minCapacity;
}

calculateCapacity方法比较简单,如果是空集合的时候,我们会将容量设定为DEFAULT_CAPACITY也就是10。构造就直接返回minCapacity, 也就是当前数组大小 size + 1。minCapacity 的意思是 当前数组的最小容量。

下面来看 ensureExplicitCapacity 这个方法,这个方法的作用是确保数组容量足够。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 如果期望的最小容量小于目前数组的长度,就需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

由于是对数组的容量做修改,这里modCount会加1,modCount 的作用主要是记录数组被修改,在类的其他方法中也会有用处。

接下来的操作就是grow(minCapacity)方法,这个方法的作用就是扩容,我们接下来来看看 grow方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 扩容数组,扩容至原先容量的 1.5 倍
private void grow(int minCapacity) {
  // 原先的容量
  int oldCapacity = elementData.length;
  // 新容量
  int newCapacity = oldCapacity + (oldCapacity >> 1);
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
  elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) 
    throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
  MAX_ARRAY_SIZE;
}

˙这个方法还是很简单的,主要分为下面几步:

  1. 得到当前数组的容量
  2. 计算出新的容量, oldCapacity + (oldCapacity >> 1)这句话的意思是扩容至当前容量的 1.5倍。oldCapacity >> 1表示右移一位,也就是除以2的意思。
  3. 如果扩容后的容量 < 我们期望的最小的容量,那么扩容后的值就是期望的最小容量
  4. 如果扩容后的容量 > jvm 所能分配的数组的最大值,就使用 Integer 的最大值。
  5. 最后我们将原数组的值 copy 到新数组。

虽然代码很简单,但是这里有几点需要注意的。

  • 扩容后的容量是原先容量的 1.5倍,也就是 原先容量大小 + 原先容量大小的一半
  • 扩容后的数组数据其实是复制了原先数组数据,也就是说前后的数组并不是同一个数组。也可以说Arrays.copyOf就是扩容的本质。
  • ArrayList 允许 null 值
  • ArrayList 最大容量是 Integer.MAX_VALUE
  • add 方法并不是线程安全的。

ArrayList 的 remove 方法

ArrayList 的删除方法实现有两个重载方法,一个根据索引删除(remove(index)),一个根据值删除(remove(Object))。我们只讲其中一个方法的源码来分析,我们讲解根据值删除方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
}

这里的 remove 方法主要分为两块,一个是值为 null 的时候,和值不为 null 的时候。

o == null 的时候

  • 遍历整个数组,找到第一个值为null 的下标
  • 调用 fastRemove 方法删除 下标值,然后返回 true
  • 找不到就返回 false

o != null 的时候

  • 遍历整个数组,找到满足o.equals(elementData[index])的下标
  • 调用 fastRemove 方法删除 下标值,然后返回 true
  • 找不到就返回 false

fastRemove方法

上面两种情况都调用了 fastRemove 方法,下面我就来看看这个方法.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
}
  • 第一步呢,这里还是 modCount++, 说明结构发生了改变
  • numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去。减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起。
  • 调用 System.arraycopy从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
  • 最后一步,数组最后一个位置赋值 null,帮助 GC

以上的步骤可以总结为下面的动态图,

简单总结一下,ArrayList有三个构造函数,通过这三个构造函数,使用者可以灵活初始化一个数组容器。在使用默认构造器初始化的数组的时候,数组是一个空数组。只有在第一次add的时候,才会见数组容量变为10。add 方法的本质是调用了 Arrays.copyOf() 方法。不管是 add 还是 remove 每次改变数组结构之前,都有一个变量 modCount 记录改变的次数。ArrayList可以运行null值且最大长度为 Integer.MAX_VALUE

迭代器

如果要自己实现迭代器,实现 java.util.Iterator 类就好了,ArrayList 也是这样做的,我们来看下迭代器的几个总要的参数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int cursor;// 迭代过程中,下一个元素的位置,默认从 0 开始。
int lastRet = -1; // 新增场景:表示上一次迭代过程中,索引的位置;删除场景:为 -1。
int expectedModCount = modCount;// expectedModCount 表示迭代过程中,期望的版本号;modCount 表示数组实际的版本号。

迭代器一般来说有三个方法:

  • hasNext 还有没有值可以迭代
  • next 如果有值可以迭代,迭代的值是多少
  • remove 删除当前迭代的值

我们来分别看下三个方法的源码:

###hasNext

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public boolean hasNext() {
   return cursor != size;
 }

cursor 表示下一个元素的位置,size 表示实际大小,如果两者相等,说明已经没有元素可以迭代了,如果不等,说明还可以迭代

next

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public E next() {
   //迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
   checkForComodification();
   //本次迭代过程中,元素的索引位置
   int i = cursor;
   if (i >= size)
     throw new NoSuchElementException();
   Object[] elementData = ArrayList.this.elementData;
   if (i >= elementData.length)
     hrow new ConcurrentModificationException();
   // 下一次迭代时,元素的位置,为下一次迭代做准备
   cursor = i + 1;
   return (E) elementData[lastRet = i];
 }

// 版本号比较
final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

看到这里,我们更能理解 cursor的含义了,就是一个计数器,指向下一个元素的位置。可以通过JVM 的程序计数器来类比,当然了,这里的含义更简单。

remove

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void remove() {
  	// 如果上一次操作时,数组的位置已经小于 0 了,说明数组已经被删除完了
    if (lastRet < 0)
        throw new IllegalStateException();
     //迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
      	// -1 表示元素已经被删除,这里也防止重复删除
        lastRet = -1;
      // 删除元素时 modCount 的值已经发生变化,在此赋值给 expectedModCount
    	// 这样下次迭代时,两者的值是一致的了
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

删除元素成功,数组当前 modCount 就会发生变化,这里会把 expectedModCount 重新赋值,下次迭代时两者的值就会一致了。

写到最后

ArrayList 并不是线程安全的,如果想要使用线程安全的 ArrayList ,可以使用开篇提到的 Collections#synchronizedList。我们来看看他的 add源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}

以上就是 ArrayList 的几个基础类和迭代器,由于篇幅问题,并没有深入说明 ArrayList 的使用。但我也相信大家已经能很熟练的使用 ArrayList 了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
作为头号大敌,三星为何还给苹果代工屏幕和处理器? | 拔刺
--- 拔出你心中最困惑的刺!--- 在这个用过即弃的时代,不要让你的求知欲过期。 今日拔刺: 1、作为头号大敌,三星为何还给苹果代工屏幕和处理器? 2、如何评价OPPO新机find x? 3、比特币
镁客网
2018/06/28
4870
征战高端10余载,OPPO少了昔日的锐气
早在2013年,OPPO就推出了Find系列,这标志着,从那个时候起,OPPO就正式进入高端智能手机市场。
刘旷
2025/04/29
900
探索无镜之境,藏在OPPO Find X背后的思考
6月29日在欧洲发布并引发轰动的OPPO Find X中国发布会在北京召开。第二次发布OPPO除了宣布国行价格外,也系统性地阐述了这款手机背后的思考和打磨它所遇到的挑战。第二次,我的朋友圈被这款手机刷屏,许多国内媒体在见到真机后都有话要说,关于这部手机本身,关于OPPO的创新能力,关于国产手机的创新,关于智能手机的未来,OPPO Find X都带来了太多话题。
罗超频道
2018/07/23
3560
探索无镜之境,藏在OPPO Find X背后的思考
升降摄像头、滑动屏……为实现全面屏,这是越走越偏了?
对于手机来说,近期炒得最火热的技术之一就是“全面屏”了。自从苹果带起了“刘海屏”的潮流,华为、小米、OPPO、vivo等好似都中了“刘海屏”的毒,纷纷开始采用“刘海屏”。然而,大众的需求总是越来越大,全面屏的呼声日渐高涨。
VRPinea
2018/09/28
1.7K0
智能手机的终场战事:小步快跑进入“智慧时代”
OPPO、荣耀、华为、三星等品牌陆续推出新品,经历过2021年的“小阳春”后,几乎所有的主流厂商都在加足马力。但有别于一味在性能、快充、屏幕等方面秀肌肉的做法,越来越多的品牌将焦点集中在了智慧化方面。
Alter聊科技
2023/01/13
3250
让苹果、三星、华为拼抢的可折叠手机,究竟有什么魔力
目前,可折叠手机并没有统一标准,各个手机厂商提供的可折叠技术方案并不一致。所谓的可折叠手机既可以是屏幕可折叠,也可以是元器件可折叠。
镁客网
2018/07/31
5190
让苹果、三星、华为拼抢的可折叠手机,究竟有什么魔力
与苹果的「高端市场」之争,国产手机只能指望「折叠屏」?
2022年半导体大会正式开启,点击图片立刻参与! 抢占市场,需要一个新的思路。 作者 | 来自镁客星球的家衡 4月11日晚间,vivo在自家新品发布会上公布了首款折叠屏产品。 就产品本身而言,这款名为“vivo X Fold”的手机并没有太多颠覆性的创新,但当8999元的起售价公布之后,弹幕还是纷纷刷起来“真香”:一款售价不到1万元的折叠屏手机,确实诱人。 时间回到2018年,折叠屏手机“出道即巅峰”。柔宇科技给智能手机带来了全新形态,随后三星与华为迅速跟进,推出相关产品。 但相较于折叠屏的惊艳,易碎的屏
镁客网
2022/04/12
2640
与苹果的「高端市场」之争,国产手机只能指望「折叠屏」?
国产手机:2022疑无路,2023又一村?
1月28日,Counterpoint Research公布的2022中国智能手机市场销量报告显示,中国智能手机销量2022年同比下降14%,达到十年来的最低水平。销量持续走下坡路,国内智能手机厂商们似乎已难改市场羸弱现状。
刘旷
2023/02/08
2120
为抛弃刘海屏,苹果预备在显示屏上打孔,但这似乎已经落伍了
随着人脸识别解锁、支付等应用的兴起,相关技术及元器件也成为了移动智能手机的标配。从iPhone X发布至今,其后几乎每一款品牌都有支持人脸识别的新机发布,但也始终没能从根本上解决iPhone“刘海屏”的问题。当然,苹果也一直在尝试研究出更好的替代方案。
镁客网
2018/11/23
5910
1万元的iPhone X太贵买不起?至少中国富人穷人都买得起
iPhone X发布已有2天,在中国收到的评论呈现出前所未有的两极分化。 好的给予了溢美之词: “苹果 iPhone 发布会超全记录:iPhone X 技术颠覆,价格贵哭”、“iPhone X发布!致敬乔布斯,没有比它更美好的方式了”; 不好的还是说苹果没创新: “史上最贵iPhoneX的尴尬:设计不及小米,技术晚于华为,买吗?”、“除了涨价 iPhone 8已经没有颠覆性的创新”。 排除连iPhone 8和iPhone X都不区分的“恶意差评”,我们来看看iPhone X最被差评的地方在哪里:全面屏、无
罗超频道
2018/04/16
3.4K0
1万元的iPhone X太贵买不起?至少中国富人穷人都买得起
折叠屏竞争又燃新战火,vivo下场晚了吗?
前些日子,有关智能手机又双叒叕卖不动了的消息再一次冲上话题榜前列,聚焦的是这届年轻人不怎么换手机了。时间往回倒几年,绝大多数年轻人不说一年一换,两年换一次手机也算是常态,如今他们平均换机周期已超31个月,媒体将这种现象称之为年轻人的“换机焦虑”。
用户2908108
2022/05/17
2380
折叠屏竞争又燃新战火,vivo下场晚了吗?
NEX让人们对vivo刮目相看,这个互联网巨头出了一份力
6月12日晚上,距离世界杯开幕只有两天时间,作为本届世界杯赞助商之一的vivo在上海发布重量级新机型vivoNEX。这款新机是今年2月底在MWC上展示的vivo APEX概念机的量产版,集成了不少黑科技——NEX对于vivo的价值有点像MIX对于小米的价值,大幅提升了手机品牌的科技感。
罗超频道
2018/07/23
5950
NEX让人们对vivo刮目相看,这个互联网巨头出了一份力
手机厂商的开年大考:卷AI,还是卷MR?
一个是OpenAI发布的一系列demo视频,仅需寥寥数字的提示词,文生视频模型Sora即可生成一段场景逼真、动作丝滑、细节丰富的视频。被“震惊”的不单单是影视从业者,包括周鸿祎在内的大佬坦言:通用人工智能的实现时间可能缩短至一两年。
Alter聊科技
2024/03/04
1430
手机厂商的开年大考:卷AI,还是卷MR?
荣耀9登陆德国比国内贵一大截,诠释中国手机出海的“荣耀之道”
北京时间6月27日晚,荣耀9正式登陆德国市场,起售价449欧元,折合人民币约3454元,比国内高1000多,沿袭了荣耀系列产品进军海外的定价惯例。事实上,更早之前荣耀9在芬兰开启预售时定价也比国内高出
罗超频道
2018/04/25
9660
荣耀9登陆德国比国内贵一大截,诠释中国手机出海的“荣耀之道”
“中国芯”引热议,智能手机要规避技术创新的三大陷阱
在近期爆发的中美贸易战中,美国商务部对中兴的7年禁令在国内引发热议,研发具有世界级水平的自主芯片,不再受制于人,已是燃眉之急。举一反三地来看,中国需要的不只是芯片,而是在各个领域都掌握核心技术,比如操作系统、云计算、AI、无人车等等。
罗超频道
2018/07/23
4220
“中国芯”引热议,智能手机要规避技术创新的三大陷阱
手机圈偏科生逆袭的思考,纵向的生存空间还有多少?
当下的手机市场用“日新月异”来形象再合适不过了,从“中华酷联”到小米、魅族、乐视的崛起再到VIVO、OPPO的逆袭;从大可乐、iuni等一批新晋手机厂商相继倒闭到三星“炸机门”、苹果“电流门”,再到锤子科技资产大幅缩水、准备上市的美图其95.1%的利润来源于美图手机,手机行业的角色转换故事每天都在上演。中国手机市场就如一个班级,成绩有好坏优劣,名次在前后更迭,有班长、尖子生、班花、班草等各类角色,更少不了一门成绩极其优秀的偏科生。那么在手机领域这个“大班级”中,谁是消费者眼中的偏科生,他们又是否还有逆袭成长,成为班级NO.1的机会?
曾响铃
2018/08/20
5610
手机圈偏科生逆袭的思考,纵向的生存空间还有多少?
MWC热词盘点,今年的手机都会是咋样的?
科技的迅速发展,无疑会反映在人们所熟悉的智能手机领域。在本届MWC上,众多厂商各显神通,带来的旗舰手机各具特色,持币以待的你是不是已经看花了眼?那么,就跟小编一起数数这届MWC的新潮流有哪些吧。 众厂
VRPinea
2018/05/15
1K0
镁客请讲 | 光鉴科技CEO朱力博士:人工智能+纳米光学,赋能机器视觉
我们的技术可以把结构光的成本降到现在的一半以内。这样将有利于安卓手机上3D结构光的普及量产。
镁客网
2018/12/24
6850
镁客请讲 | 光鉴科技CEO朱力博士:人工智能+纳米光学,赋能机器视觉
智能手机迎来三国时代,2016年下半年旗舰机展望
近日,国际数据公司(IDC)公布了《全球手机季度跟踪报告》,从数据看,2016年第一季度智能手机出货量前五强悄然发生改变。其中三星、苹果依旧占据前两位,华为、OPPO、vivo分别位列其后,尤其是OP
镁客网
2018/05/28
4690
五大层面详解智能手机创新天花板
苹果失去创新已不是什么新观点,前段时间新发布的iOS 14也有不少人戏谑是“掏空安卓”。事实上,真有点为难苹果了,近几年智能手机领域的跃进式创新几乎绝迹,更多的还是微创新,或者只是技术常规迭代。
刘旷
2020/07/03
8330
推荐阅读
相关推荐
作为头号大敌,三星为何还给苹果代工屏幕和处理器? | 拔刺
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档