Java 的集合

一、Collection 

1、ArrayList 

底层采用数组实现,操作大多基于对数组的操作。
在添加和删除时需要做 System.arraycopy(native层方法) 拷贝工作。
添加元素时可能会扩容,这要大量的拷贝工作,删除元素时,会把后面的元素向前拷贝。
所以增、删时效率不高。但set()、get()效率高。

1.ArrayList 的增、删方法

//这是添加元素的方法,size默认为0
public boolean add(E e) {ensureCapacityInternal(size + 1);//元素添加到集合 技术点:size++表示选赋值后加1,++size表示先加1后赋值。elementData[size++] = e;return true;
}private void ensureCapacityInternal(int minCapacity) {//如果是使用无参构造会进入到这里,那么minCapacity 值为10,第一次进来 minCapacity 是1,最大值就是 DEFAULT_CAPACITY 了。if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);
}//最核心的grow方法
private void ensureExplicitCapacity(int minCapacity) {modCount++;//如果第一次进入,minCapacity 为10,条件成立,进行扩容。当集合数据存满后,继续扩容。if (minCapacity - elementData.length > 0)grow(minCapacity);
}
//扩容核心方法 
private void grow(int minCapacity) {//默认为零int oldCapacity = elementData.length;//oldCapacity >> 1 是位运算右移一位,相当于是除以2。所以从这里可以看出扩容后newCapacity 是原来的1.5倍。//如果集合数据存满后,再次扩容newCapacity=10+5int newCapacity = oldCapacity + (oldCapacity >> 1);//如果是无参构造 第一次进入 newCapacity为0,minCapacity为10,条件成立if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);//扩容开始 拷贝elementData数组为新数组,长度为newCapacityelementData = Arrays.copyOf(elementData, newCapacity);
}//根据索引删除
public E remove(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));modCount++;E oldValue = (E) elementData[index];int numMoved = size - index - 1;//拷贝数组if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved);elementData[--size] = null; return oldValue;
}//根据对象删除
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;
}

2.ArrayList 的改、查方法

// set() 方法
public E set(int index, E element) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));E oldValue = (E) elementData[index];elementData[index] = element;return oldValue;
}// get() 方法
public E get(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));return (E) elementData[index];
}

2、ArrayDeque 

3、LinkedList 

双向链表结构,Node中保存了数据、前指针、后指针。
增删数据时,只更换修改节点的前后指针,无需拷贝,速度较快。
查询时需要遍历,速度较慢。
链表不存在容量不足的问题,没有扩容机制,更适合删除和添加。

//头节点指针
transient Node<E> first;//尾节点指针
transient Node<E> last;public LinkedList() {
}
//Node实例,next:上一个元素的指针;prev:下一个元素的指针。
private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
}

1.LinkedList的增、删方法

add: 尾部时(默认新元素插入尾部)
void linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);last = newNode;if (l == null)first = newNode;elsel.next = newNode;size++;modCount++;
}
add:插入到链表头部
private void linkFirst(E e) {final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);first = newNode;if (f == null)last = newNode;elsef.prev = newNode;size++;modCount++;
}remove:删除头节点 
private E unlinkFirst(Node<E> f) {// assert f == first && f != null;final E element = f.item;final Node<E> next = f.next;f.item = null;f.next = null; // help GCfirst = next;if (next == null)last = null;elsenext.prev = null;size--;modCount++;return element;
}remove:尾结点
private E unlinkLast(Node<E> l) {// assert l == last && l != null;final E element = l.item;final Node<E> prev = l.prev;l.item = null;l.prev = null; // help GClast = prev;if (prev == null)first = null;elseprev.next = null;size--;modCount++;return element;
}remove:按对象删除,需要遍历节点
public boolean remove(Object o) {if (o == null) {for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {if (o.equals(x.item)) {unlink(x);return true;}}}return false;
}

2.LinkedList的set、get方法

set:根据index修改元素,需要遍历元素
public E set(int index, E element) {checkElementIndex(index);Node<E> x = node(index);E oldVal = x.item;x.item = element;return oldVal;
}get:查询采用二分查找
//node方法用于查找当前节点
//判断index值是不是小于整个链表长度的一半,整个if/else逻辑是在判断查找的位置是距离链表头近还是链表尾近
public E get(int key, E valueIfKeyNotFound) {int i = ContainerHelpers.binarySearch(mKeys, mSize, key);if (i < 0 || mValues[i] == DELETED) {return valueIfKeyNotFound;} else {return (E) mValues[i];}
}

4、TreeSet 

二、Map 

1、TreeMap 

2、HashMap 

默认长度16,扩容因子0.75。无序,线程不安全,key、value 都可以为 null,key是包装类型。
jdk1.7用头插法,由 数组 + 链表 组成,链表是为了解决哈希冲突。
jdk1.8用尾插法,由 数组+链表+红黑树(红黑树条件:链表长度大于8,且数组长度大于64)。

1.核心代码 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {// 声明了一个局部变量 tab,局部变量 Node 类型的数据 p,int 类型 n,iNode<K,V>[] tab; Node<K,V> p; int n, i;// 首先将当前 hashmap 中的 table(哈希表)赋值给当前的局部变量 tab,然后判断tab 是不是空或者长度是不是 0,实际上就是判断当前 hashmap 中的哈希表是不是空或者长度等于 0if ((tab = table) == null || (n = tab.length) == 0)// 如果是空的或者长度等于0,代表现在还没哈希表,所以需要创建新的哈希表,默认就是创建了一个长度为 16 的哈希表n = (tab = resize()).length;// 将当前哈希表中与要插入的数据位置对应的数据取出来,(n - 1) & hash])就是找当前要插入的数据应该在哈希表中的位置,如果没找到,代表哈希表中当前的位置是空的,否则就代表找到数据了, 并赋值给变量 pif ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//创建一个新的数据,这个数据没有下一条,并将数据放到当前这个位置else { //代表要插入的数据所在的位置是有内容的// 声明了一个节点 e, 一个 key kNode<K,V> e; K k;if (p.hash == hash && //如果当前位置上的那个数据的 hash 和我们要插入的 hash 是一样,代表没有放错位置// 如果当前这个数据的 key 和我们要放的 key 是一样的,实际操作应该是就替换值((k = p.key) == key || (key != null && key.equals(k))))// 将当前的节点赋值给局部变量 ee = p;else if (p instanceof TreeNode)//如果当前节点的 key 和要插入的 key 不一样,然后要判断当前节点是不是一个红黑色类型的节点e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果是就创建一个新的树节点,并把数据放进去else {// 如果不是树节点,代表当前是一个链表,那么就遍历链表for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {//如果当前节点的下一个是空的,就代表没有后面的数据了p.next = newNode(hash, key, value, null);//创建一个新的节点数据并放到当前遍历的节点的后面if (binCount >= TREEIFY_THRESHOLD - 1) // 重新计算当前链表的长度是不是超出了限制treeifyBin(tab, hash);//超出了之后就将当前链表转换为树,注意转换树的时候,如果当前数组的长度小于MIN_TREEIFY_CAPACITY(默认 64),会触发扩容,我个人感觉可能是因为觉得一个节点下面的数据都超过8 了,说明 hash寻址重复的厉害(比如数组长度为 16 ,hash 值刚好是 0或者 16 的倍数,导致都去同一个位置),需要重新扩容重新 hashbreak;}// 如果当前遍历到的数据和要插入的数据的 key 是一样,和上面之前的一样,赋值给变量 e,下面替换内容if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // 如果当前的节点不等于空,V oldValue = e.value;// 将当前节点的值赋值给 oldvalueif (!onlyIfAbsent || oldValue == null)e.value = value; // 将当前要插入的 value 替换当前的节点里面值afterNodeAccess(e);return oldValue;}}++modCount;// 增加长度if (++size > threshold)resize();// 如果当前的 hash表的长度已经超过了当前 hash 需要扩容的长度, 重新扩容,条件是 haspmap 中存放的数据超过了临界值(经过测试),而不是数组中被使用的下标afterNodeInsertion(evict);return null;
}

2.扩容的方法 

final Node<K,V>[] resize() {// 创建一个临时变量,用来存储当前的tableNode<K,V>[] oldTab = table;// 获取原来的table的长度(大小),判断当前的table是否为空,如果为空,则把0赋值给新定义的oldCap,否则以table的长度作为oldCap的大小int oldCap = (oldTab == null) ? 0 : oldTab.length;// 创建临时变量用来存储旧的阈值,把旧table的阈值赋值给oldThr变量int oldThr = threshold;// 定义变量newCap和newThr来存放新的table的容量和阈值,默认都是0int newCap, newThr = 0;// 判断旧容量是否大于0if (oldCap > 0) {// 判断旧容量是否大于等于 允许的最大值,2^30if (oldCap >= MAXIMUM_CAPACITY) {// 以int的最大值作为原来HashMap的阈值,这样永远达不到阈值就不会扩容了threshold = Integer.MAX_VALUE;// 因为旧容量已经达到了最大的HashMap容量,不可以再扩容了,将阈值变成最大值之后,将原table返回return oldTab;}// 如果原table容量不超过HashMap的最大容量,将原容量*2 赋值给变量newCap,如果newCap不大于HashMap的最大容量,并且原容量大于HashMap的默认容量else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)// 将newThr的值设置为原HashMap的阈值*2newThr = oldThr << 1; // double threshold}// 如果原容量不大于0,即原table为null,则判断旧阈值是否大于0else if (oldThr > 0) // 如果原table为Null且原阈值大于0,说明当前是使用了构造方法指定了容量大小,只是声明了HashMap但是还没有真正的初始化HashMap(创建table数组),只有在向里面插入数据才会触发扩容操作进而进行初始化// 将原阈值作为容量赋值给newCap当做newCap的值。由之前的源码分析可知,此时原阈值存储的大小就是调用构造函数时指定的容量大小,所以直接将原阈值赋值给新容量newCap = oldThr;// 如果原容量不大于0,并且原阈值也不大于0。这种情况说明调用的是无参构造方法,还没有真正初始化HashMap,只有put()数据的时候才会触发扩容操作进而进行初始化else {               // zero initial threshold signifies using defaults// 则以默认容量作为newCap的值newCap = DEFAULT_INITIAL_CAPACITY;// 以初始容量*默认负载因子的结果作为newThr值newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}// 经过上面的处理过程,如果newThr值为0,说明上面是进入到了原容量不大于0,旧阈值大于0的判断分支。需要单独给newThr进行赋值if (newThr == 0) {// 临时阈值 = 新容量 * 负载因子float ft = (float)newCap * loadFactor;// 设置新的阈值 保证新容量小于最大总量   阈值要小于最大容量,否则阈值就设置为int最大值newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}// 将新的阈值newThr赋值给threshold,为新初始化的HashMap来使用threshold = newThr;// 初始化一个新的容量大小为newCap的Node数组@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];// 将新创建的数组赋值给table,完成扩容后的新数组创建table = newTab;// 如果旧table不为null,说明旧HashMap中有值if (oldTab != null) {// 如果原来的HashMap中有值,则遍历oldTab,取出每一个键值对,存入到新tablefor (int j = 0; j < oldCap; ++j) {// 创建一个临时变量e用来指向oldTab中的第j个键值对,Node<K,V> e;// 将oldTab[j]赋值给e并且判断原来table数组中第j个位置是否不为空if ((e = oldTab[j]) != null) {// 如果不为空,则将oldTab[j]置为null,释放内存,方便gcoldTab[j] = null;// 如果e.next = null,说明该位置的数组桶上没有连着额外的数组if (e.next == null)// 此时以e.hash&(newCap-1)的结果作为e在newTab中的位置,将e直接放置在新数组的新位置即可newTab[e.hash & (newCap - 1)] = e;// 否则说明e的后面连接着链表或者红黑树,判断e的类型是TreeNode还是Node,即链表和红黑树判断else if (e instanceof TreeNode)// 如果是红黑树,则进行红黑树的处理。将Node类型的e强制转为TreeNode,之所以能转换是因为TreeNode 是Node的子类// 拆分树,具体源码解析会在后面的TreeNode章节中讲解((TreeNode<K,V>)e).split(this, newTab, j, oldCap);// 当前节不是红黑树,不是null,并且还有下一个元素。那么此时为链表else { // preserve order/*这里定义了五个Node变量,其中lo和hi是,lower和higher的缩写,也就是高位和低位,因为我们知道HashMap扩容时,容量会扩到原容量的2倍,也就是放在链表中的Node的位置可能保持不变或位置变成 原位置+oldCap,在原位置基础上又加了一个数,位置变高了,这里的高低位就是这个意思,低位指向的是保持原位置不变的节点,高位指向的是需要更新位置的节点*/// Head指向的是链表的头节点,Tail指向的是链表的尾节点Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;// 指向当前遍历到的节点的下一个节点Node<K,V> next;// 循环遍历链表中的Nodedo {next = e.next;/*如果e.hash & oldCap == 0,注意这里是oldCap,而不是oldCap-1。我们知道oldCap是2的次幂,也就是1、2、4、8、16...转化为二进制之后,都是最高位为1,其它位为0。所以oldCap & e.hash 也是只有e.hash值在oldCap二进制不为0的位对应的位也不为0时,才会得到一个不为0的结果。举个例子,我们知道10010 和00010 与1111的&运算结果都是 0010  ,但是110010和010010与10000的运算结果是不一样的,所以HashMap就是利用这一点,来判断当前在链表中的数据,在扩容时位置是保持不变还是位置移动oldCap。*/// 如果结果为0,即位置保持不变if ((e.hash & oldCap) == 0) {// 如果是第一次遍历if (loTail == null)// 让loHead = e,设置头节点loHead = e;else// 否则,让loTail的next = eloTail.next = e;// 最后让loTail = eloTail = e;}/*其实if 和else 中做的事情是一样的,本质上就是将不需要更新位置的节点加入到loHead为头节点的低位链表中,将需要更新位置的节点加入到hiHead为头结点的高位链表中。我们看到有loHead和loTail两个Node,loHead为头节点,然后loTail是尾节点,在遍历的时候用来维护loHead,即每次循环,更新loHead的next。我们来举个例子,比如原来的链表是A->B->C->D->E。我们这里把->假设成next关系,这五个Node中,只有C的hash & oldCap != 0 ,然后这个代码执行过程就是:第一次循环: 先拿到A,把A赋给loHead,然后loTail也是A第二次循环: 此时e的为B,而且loTail != null,也就是进入上面的else分支,把loTail.next =B,此时loTail中即A->B,同样反应在loHead中也是A->B,然后把loTail = B第三次循环: 此时e = C,由于C不满足 (e.hash & oldCap) == 0,进入到了我们下面的else分支,其实做的事情和当前分支的意思一样,只不过维护的是hiHead和hiTail。第四次循环: 此时e的为D,loTail != null,进入上面的else分支,把loTail.next =D,此时loTail中即B->D,同样反应在loHead中也是A->B->D,然后把loTail = D*/else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);// 遍历结束,即把table[j]中所有的Node处理完// 如果loTail不为空,也保证了loHead不为空if (loTail != null) {// 此时把loTail的next置空,将低位链表构造完成loTail.next = null;// 把loHead放在newTab数组的第j个位置上,也就是这些节点保持在数组中的原位置不变newTab[j] = loHead;}// 同理,只不过hiHead中节点放的位置是j+oldCapif (hiTail != null) {hiTail.next = null;// hiHead链表中的节点都是需要更新位置的节点newTab[j + oldCap] = hiHead;}}}}}// 最后返回newTabreturn newTab;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis+IDEA极速了解和实现单机锁和分布式锁

单机下&#xff1a; 只适用于单机环境下&#xff08;单个JVM&#xff09;&#xff0c;多个客户端访问同一个服务器 1.synchronized package com.cloud.SR.controller;import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.cor…

wps插入图片显示不全、混乱

问题如下&#xff1a; 原因&#xff1a; 格式混乱 解决办法&#xff1a; 1、统一格式&#xff0c;使用格式刷统一文档的格式 2、Ctrl A 全选&#xff0c;重新选择行距 3、重新粘贴图片&#xff08;选择嵌入型&#xff09;

【Hello mysql】 mysql的内置函数

Mysql专栏&#xff1a;Mysql 本篇博客简介&#xff1a;介绍mysql的基内置函数 mysql的内置函数 日期函数获取年月日获取时分秒获取时间戳在日期的基础上加上日期在日期的基础上减去日期计算两个日期之差创建一张表 记录生日创建一个留言表 字符串函数获取emp表的ename列的字符集…

Dubbo分布式服务框架,springboot+dubbo+zookeeper

一Dubbo的简易介绍 1.Dubbo是什么&#xff1f; Dubbo是一个分布式服务框架&#xff0c;致力于提供高性能和透明化的RPC远程服务调用方案&#xff0c;以及SOA服务治理方案。 简单的说&#xff0c;dubbo就是个服务框架&#xff0c;如果没有分布式的需求&#xff0c;其实是不需…

idea编译时遇到的bug

1、对象重复定义 问题描述&#xff1a; D:\workspace\spark\src\main\Scala\WordCount.scala:3:8 WordCount is already defined as object WordCount object WordCount { 解决参考博客&#xff1a;Error:(21, 8) FlumePushWordCount is already defined as object FlumePush…

rust abc(5): 常量

文章目录 1. 目的2. 基本用法2.1 说明2.2 运行结果 3. 不推荐或不正确用法3.1 不推荐用小写字母作为常量名字3.2 常量名称中含有小写字母就会报warning3.3 定义常量时&#xff0c;不指定数据类型会编译报错 4. const 和 immutable 的区别4.1 const 可以在函数外声明&#xff0c…

基于深度学习的高精度安全帽及背心检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度安全帽及背心检测识别系统可用于日常生活中或野外来检测与定位安全帽及背心目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的安全帽及背心目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系…

WebDAV之π-Disk派盘 + Solid Explorer

Solid Explorer 支持WebDAV方式连接π-Disk派盘。 Solid Explorer 是一款非常优秀的 Android 文件管理器&#xff0c;Material Design 设计风格&#xff0c;双栏布局&#xff0c;可拖拽操作、支持 ROOT 权限、多媒体浏览器、压缩包支持&#xff0c;Chromecast 流支持等众多功…

微信为什么使用 SQLite 保存聊天记录?

概要 SQLite 是一个被大家低估的数据库&#xff0c;但有些人认为它是一个不适合生产环境使用的玩具数据库。事实上&#xff0c;SQLite 是一个非常可靠的数据库&#xff0c;它可以处理 TB 级的数据&#xff0c;但它没有网络层。接下来&#xff0c;本文将与大家共同探讨 SQLite 在…

基于Tensorflow来重现GPT v1模型

OpenAI推出的ChatGPT模型让我们看到了通用人工智能的发展潜力&#xff0c;我也找了GPT的相关论文来进行研究。OpenAI在2017年的论文Improving Language Understanding by Generative Pre-Training提出了GPT的第一个版本&#xff0c;我也基于这个论文来用Tensorflow进行了复现。…

Keepalived 安装与配置

安装 Keepalived apt -y install keepalived 里边有一个杠y&#xff0c;就是我安装的时候里面有yes&#xff0c;就直接是yes 添加 Keepalived 配置 安装好之后, 下一步就开始去来写这个配置文件了&#xff0c;就在这里面去建一个 etc 当中&#xff0c;就是在这个 etc 当中建一个…

认识企业级定时任务Quartz

文章目录 前言一、实现一个Quartz的小案例1.创建一个maven项目2.添加Quartz依赖3.创建一个配置文件配置Quartz信息4.创建一个Job类继承Job接口5.编写主方法逻辑进行测试6.测试运行结果 二、Job和JobDetail总结 前言 目前仍有大部分企业仍在使用Quartz这种定时任务框架&#xf…

45. 跳跃游戏 II (贪心)

题目链接&#xff1a;力扣 解题思路&#xff1a;贪心&#xff0c;尽可能地找到下一跳能够跳到的最远距离&#xff0c;这样到达终点时&#xff0c;所需跳跃次数最少 以nums [2,3,1,1,4,2]为例&#xff1a; 以当前位置begin作为起跳点&#xff0c;能够跳跃的最远距离为m&#…

MySQL每日一练:多表查询——连接查询、子查询

目录 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; emp表&#xff1a; 2、插入数据&#xff1a; dept表&#xff1a; emp表&#xff1a; 3、 按条件查找 1、首先创建员工表emp和部门表dept&#xff1a; dept表&#xff1a; create table dept (…

以太网(Ethernet)入门了解

以太网&#xff08;Ethernet&#xff09;是一种常见的局域网&#xff08;LAN&#xff09;通信协议&#xff0c;它是由Xerox公司于1970年代中期开发的。以太网是一种基于广播技术的开放式网络协议&#xff0c;它允许设备在共享通信介质上进行通信。以下是关于以太网的基本概念、…

MySQL 多表查询练习

1.创建student和score表 CREATE TABLE student ( id INT(10) NOT NULL UNIQUE PRIMARY KEY , name VARCHAR(20) NOT NULL , sex VARCHAR(4) , birth YEAR, department VARCHAR(20) , address VARCHAR(50) );创建score表。SQL代码如下&#xff1a; CREATE TABLE s…

OpenCV 入门教程:Laplacian算子和Canny边缘检测

OpenCV 入门教程&#xff1a; Laplacian 算子和 Canny 边缘检测 导语一、Laplacian 算子二、Canny 边缘检测三、示例应用3.1 图像边缘检测3.2 边缘增强 总结 导语 边缘检测在图像处理和计算机视觉领域中起着重要的作用。 Laplacian 算子和 Canny 边缘检测是两种常用的边缘检测…

CAT1模块 EC800M HTTP使用总结记录

分享记录一下 CAT1 模块EC800 HTTP 协议使用流程 ...... by 矜辰所致目录 前言一、基础说明1.1 CAT1 与 4G1.2 EC800M 模块1.3 HTTP 二、开始使用2.1 硬件设计部分2.2 模块上电流程2.3 PDP 上下文2.3.1 什么是 SGSN 和 GGSN &#xff1f; 三、 HTTP 流程3.1 客户端3.1.1 PDP 上…

Ubuntu18.04 系统安装 Docker

1、首先更新软件源&#xff1a; sudo apt-get updatesudo apt-get upgrade 2、安装Docker&#xff1a; sudo apt install docker -y 3、查看安装的Docker apt list docker 4、查看docker 进程 ps -ef|grep docker 5、查看docker 版本有问题 6、开启Docker服务 systemctl…

10_SPI_Flash 连续写实验

10_SPI_Flash 连续写实验 1. 实验目标2. 连续写方法3. 操作时序4. 流程框图4.1 顶层模块4.2 连续写模块 5. 波形图6. RTL6.1 flash_seq_wr_ctrl6.2 spi_flash_seq_wr 7. Testbench 1. 实验目标 使用页写指令&#xff0c;将串口发送过来的连续不定量数据写入 Flash。本实验中&a…