Java常见问题总结三

news2024/11/24 2:53:19

一、ArrayList 和 LinkedList的区别 

1. 底层数据结构不同。ArrayList底层是基于数组实现的,LinkedList底层是基于链表文现的
2. 由于底层数缺结构不同,他们所适电的场景也不同,Araylist史适合随机查战,LinkedList史适合期余和添加,查询、添加、删余的时间复杂度不同
3. ArrayList和LinkedList都实现了list接口, 但是LinkedList还额外实现了Deque接口,还可以当做双端队列
4.  ArrayList需要考虑扩容(还不知道ArrayList扩容原理), LinkedList不需要

※:补充ArrayList 和hashMap

ArrayList 扩容机制:直接变成1.5倍。最大扩容到 2^31 次方的大小。

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        // 当长度等于最大值时就会扩容
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}
-----------------------------------------------------
private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    // 位运算右移一位相当于直接 /2 这行代码等价于 new = old + old/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity <= 0) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return minCapacity;
    }
    // 最后将新的容量和最大值做对比
    return (newCapacity - MAX_ARRAY_SIZE <= 0)? newCapacity : hugeCapacity(minCapacity);
}

hashMap的扩容:扩容成原来大小的两倍  当占75%的时候就会开始扩容

final HashMap.Node<K,V>[] resize() {
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 如果当前map需要扩容直接 容量和因子变成double
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; 
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               
        // 默认无参调用hashMap
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
    table = newTab;
}

hashMap的put操作,在1.8版本中引入了红黑树的概念,当长度大于8的时候会转换成红黑树,小于6的时候会转换成链表,其中链表的作用是为了防止hash冲突,将发生碰撞的key存储到连表中

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)   // 当前节点没有元素直接new一个
        tab[i] = newNode(hash, key, value, null);
    else {
        HashMap.Node<K,V> e; K k;
        // 存在一个相同node时候会去比较hash
        if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;  // 取出已存在的node
        else if (p instanceof HashMap.TreeNode)  // 判断是不是一个树节点
            e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                // 当长度大于8就变成红黑树
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { 当有已存在的节点时,新值会覆盖旧值,旧值会返回
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // 做完插入以后在进行扩容
    if (++size > threshold)
        resize();
    return null;
}

二、threadLocal使用

在每个线程Thread中都有threadLocalMap对象(是threadLocal 的一个内部类),key 是threadLocal对象是强引用、value就是缓存的值是弱引用。当线程结束完成以后threadLocal就会被GC清除,所以在线程中使用完成以后可以手动的remove掉,否则会造成内存泄露。

public class Node {
    // 初始化一个threadLocal, 通过类方法的形式
    private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    private Integer age;

    public Integer getAge(){
        return this.threadLocal.get();
    }

    public void setAge(Integer age){
        this.threadLocal.set(age);
    }
}
public static void main(String[] args) throws InterruptedException {
    Node node = new Node();
    
    new Thread(() -> {
        try{
            node.setAge(65);
            System.out.println("a线程===>"+node.getAge());
        }finally {
            threadLocal.remove();
        }
    }).start();

    new Thread(() -> {
        try{
            node.setAge(48);
            System.out.println("b线程===>"+node.getAge());
        }finally {
            threadLocal.remove();
        }
    }).start();
}

关于threadLocalMap的扩容机制,其机制和hashMap底层的扩容原理是不同的,hashMap采用的是链地址法(数组加链表的形式),threadLocalMap采用的是开放地址法(冲突的时候会寻找下一个位置是不是有元素,一直找到有空位的地方进行插入)。

三、fast-fail 和 fast-safe机制

fail-fast的字面意思是“快速失败”。当我们在遍历集合元素的时候,经常会使用迭代器,但在迭代器遍历元素的过程中,如果集合的结构被改变的话,就会抛出异常,防止继续遍历。这就是所谓的快速失败机制。它是java集合中的一种错误检测机制。

工作原理 :迭代器每次执行next方法都会去调用checkForComodification方法,当expectedModcount 和 modCount值不相等的时候就会抛出异常,当删除或者添加元素的时候都会使modCount++所以会出现不相等的情况。

@Override
public void forEachRemaining(Consumer<? super E> action) {
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i < size) {
        final Object[] es = elementData;
        if (i >= es.length)
            throw new ConcurrentModificationException();
        for (; i < size && modCount == expectedModCount; i++)
            action.accept(elementAt(es, i));
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }
}
// 这里是关键
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

fail-safe 当集合的结构被改变的时候,fail-safe机制会在复制原集合的一份数据出来,然后在复制的那份数据遍历。例如copyOnWriteArrayList,缺点是: 开销大。

4. switch 和 if

switch的速度要比 if 判断快。当switch的key是连续的要远比switch的key不连续的执行速度要快。因为连续的switch在字节码层面生成的是 tableswitch,不连续生成的是lookupswitch。

5. try..catch.finally

如果在 try 和 finally中都有return ,会走finally的return方法。

当只在try中return,但是在finally中还对return的变量有操作分为两种情况:

在try 里面进行基本数据类型值的返回,在finally里在对该值进行操作,最终返回的值还是try里面的值。
在try里ruturn一个引用类型的对象,在finally里对该对象进行属性修改,最终返回都对象是finally里修改过的对象。

6.布隆过滤器

其目的是为了防止缓存穿透,本质上是一个二进制的数组。

如何实现一个bit类型的数组,可以通过基本类型进行操作。具体流程是先进行一次hash函数,然后对bit数组长度进行取模操作。

public void test09(){
    // 先通过一个hash得到一个值  然后在%上一个bits的长度
    int a = 65;
    int[] bits = new int[4]; // 每个都是32bit 第一个位置是 0 ~ 31
    int index = a / 32;      // 确定在第几个桶
    int offset = a % 32;
    int state = ((bits[index] >> offset) & 1);   // 查看某个位置的状态

    bits[index] = bits[index] | (1 << offset);  // 变成某个位置变成1

    bits[index] = bits[index] & ~(1 << offset);  // 变成某个位置变成0
}

布隆过滤器的也是会存在误判的情况,误判取决于bit数组的长度和hash函数的次数。具体关系如下:

n是样本量,m是位图的长度,p是错误率,v是选取位图的长度,k是哈希函数的次数。

在n确定的情况下,m和p是一个反比例函数图像的关系,m和p的关系如下,算出来的m除8才是bit使用的空间:

m = -\frac{n*lnP}{ (ln2)^{2}}

 在m确定的情况下,p和n是一个二次函数的图像 应选取最低点  ,当k越大就会消耗掉的m越多  则p一定会上升,k和n的关系如下:

k = \frac{m}{n} * ln2

 真实的错误率为(k和m都是真实的):

p = (1-e^{-\frac{n*k}{m}})^{k}

 当查询参数 word到来,会经过多个hash函数从而得到多个hash结果值,从而将计算结果所对应的下标值置为1,判断是不是同一个word是需要,所有hash结果相同才会判定为相同。可以通过增加hash函数的数量 和 二进制数组的容量来减少布隆过滤器的误判率。

业务场景中  可以先判断这个数据在不在布隆过滤器(相当于一个白名单或者一个黑名单)中,然后再判断redis缓存,如果没有再判断mysql,然后将查出来的数据更新到redis中。

7. 分布式锁

zk实现分布式锁:用的是临时有序节点。每个客户端连接的时候都会创建一个临时节点,如果自己是最小的就去执行任务,如果不是则监听比自己小1的节点,等待节点的消失。

redis分布式锁:

是通过 Lua 脚本去保证 redis操作的原子性。

加锁核心:

// 如果key不存在直接进行set,会将当前线程号,过期时间等全部set进去。
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +

// 如果key存在,并且是当前线程,则进行重入次数+1
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +

// 如果key存在value(value就是线程号)不匹配,直接通过pttl命令 返回剩余时间
"return redis.call('pttl', KEYS[1]);",

解锁核心:

// 判断当前线程号不一致则忽略,线程号就是value
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +

// 是当前线程就进行减一的操作,因为有可重入的情况,如果大于0,就需要重新设置时间这是重入锁,
// 小于等于0直接删除key进行释放
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +
"return nil;",

对于提高容错可以使用redlock。

8、原子类LongAdder 和 LongAccumulator

原子类型的累加器,在高争用的情况下比atomicLong吞吐量要大很多,代价是消耗更大的空间,减少了CAS的重试次数;在低争用的情况下两者相差无几。

1. LongAdder 初值为0的累加器, 是striped64的子类

LongAdder longAdder = new LongAdder();
longAdder.add(2);   // +2
longAdder.increment();   // +1
longAdder.sum();  // 多线程不保证准确度
longAdder.sumThenReset();  // 求和并重置为0
longAdder.reset();  // 重置为0

2. LongAccumulator  自定义初值和计算方式的累加器,接收两个参数 [计算方式, 起始值]

LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
accumulator.accumulate(1);  // +1
accumulator.accumulate(2);  // +2
System.out.println(accumulator.get());

LongAdder底层的原理是热点分散,类似开多个窗口进行处理,将value值分散到多个数组里,不同线程会命中不同的槽位,各个槽位进行CAS的操作,获取result就是所有槽位值相加 + base。

 LongAdder的add时候代码解析:

变量说明:
base:基础值,在没有竞争的情况下直接累加到base,cells扩容后也需要部分累加到base上。
collide:是否还能扩容,false不扩容  true可以扩容。
cellsBusy:初始化/扩容的时候需要获取锁 0 标识无锁,1 标识其他线程已经持有锁了。
getProbe():得到当前线程的hash值。
CASCellsBusy(): 通过CAS修改 cellsBusy的值。true表示抢锁成功。

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    // 只有一个线程的时第一次进来cells为null,casBase做的是自旋锁的比较,如果竞争失败了进行扩容
    // base 是基础值, x 是更新值。
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;  // 有没有竞争,false代表有竞争产生

        // 这里有四个层层递进的判断,当cs 为空,会进行初始化
        // 当cs进行了初始化并赋初值后,某个槽位如果为空,则进行初始化一个cell
        // 当cs进行了初始化并赋初值,对某个槽位的值CAS竞争失败,会进行扩容 
        if (cs == null || (m = cs.length - 1) < 0 ||
                (c = cs[getProbe() & m]) == null ||
                !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

 在对Cell数组初始化可以分为以下三种情况:

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {
    int h;  // 值为0需要对线程进行强制初始化
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // 强制初始化
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;           
    done: for (;;) {
        Cell[] cs; Cell c; int n; long v;
        // cells已经被初始化了,进行某个槽位的争抢
        if ((cs = cells) != null && (n = cs.length) > 0) {
            // (n - 1) & h 得到了某个坑位值,如果值为null代表cell没有进行初始化
            if ((c = cs[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {       // 如果抢占失败,将collide = false
                    Cell r = new Cell(x);   // 初始化一个cell并赋初值。
                    if (cellsBusy == 0 && casCellsBusy()) {  // 双端校验 高并发就需要这样
                        try {               
                            Cell[] rs; int m, j;
                            // cell数组不为空且长度大于0 某个坑位为null 就将当前坑位赋值过去
                            if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                break done;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // 跳出来之后在重新获取hash值进行循环
            // 某个坑位c再次进行CAS抢锁  如果为成功直接break  
            else if (c.cas(v = c.value, (fn == null) ? v + x : fn.applyAsLong(v, x)))
                break;
            else if (n >= NCPU || cells != cs)
                collide = false;            // 已经到达CPU的上限不会进行扩容
            else if (!collide)
                collide = true;             // 值是false则允许扩容
            else if (cellsBusy == 0 && casCellsBusy()) {  // 进行扩容的逻辑
                try {
                    if (cells == cs)        //   左移相当*2
                        cells = Arrays.copyOf(cs, n << 1);
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            h = advanceProbe(h);
        }
        // cells没有加锁也没有初始化了,尝试加锁并初始化
        // cells == cs cs一进来就是null 可以看成 cells==null
        else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
            try {
                // 进行初始化                           
                if (cells == cs) {
                    Striped64.Cell[] rs = new Striped64.Cell[2];  // 永远都是二的次幂
                    rs[h & 1] = new Striped64.Cell(x);  // 给每个数组进行赋值 x是1
                    cells = rs;
                    break done;
                }
            } finally {
                cellsBusy = 0;
            }
        }
        // 兜底的方法。其他线程正在初始化,多个线程更新base的时候直接break。
        // fn是 LongAccumulator 初始化定义的自定义计算方法。
        else if (casBase(v = base,(fn == null) ? v + x : fn.applyAsLong(v, x)))
            break done;
    }
}

当sum的代码解析:一句话就是  result = base +\sum cells.

public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Striped64.Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

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

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

相关文章

自动化测试工程师的发展前景怎么样?

根据各大网络招聘平台的数据显示&#xff0c;越来越多的企业在招聘测试工程师的时候&#xff0c;都开始重视自动化测试这一重要技能。早在四年前&#xff0c;自动化测试的人才需求和薪资待遇就开始一路上涨。如果你问&#xff1a;自动化测试工程师的发展前景怎么样&#xff1f;…

基于redis实现分布式锁

前言 我们的系统都是分布式部署的&#xff0c;日常开发中&#xff0c;秒杀下单、抢购商品等等业务场景&#xff0c;为了防⽌库存超卖&#xff0c;都需要用到分布式锁。 分布式锁其实就是&#xff0c;控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或…

ubuntu重启、关机命令

// // // //之前用linux系统&#xff0c; 一键解决也是可以的&#xff0c;反正我每次用命令&#xff08;泪目…&#xff09;&#xff0c;中间崩了好几次&#xff0c;换回win&#xff0c;此篇也做记录 // // // 重启命令 以下所有命令在root根目录下输入&#xff08;普通用户&…

SQL Server 数据批量导出处理

在实际项目环境中&#xff0c;有时会遇到需要将大量数据&#xff08;这里所指百万级别以上的数据量&#xff09;从一台服务器迁移到另外一台数据库服务器的情况。SQL Server有很多方式可以进行数据迁移&#xff1a;备份还原、导入/导出数据、生成脚本&#xff08;包含数据&…

解决console.log打印不出深度链表问题

解决console.log打印不出深度链表问题相信大家在写算法题的时候会遇到这样一个问题&#xff0c;写了一个链表的数据结构&#xff0c;在append几个数据之后&#xff0c;想console.log打印以下看看append正不正确&#xff0c;但是&#xff0c;consolo.log出来成这个样子&#xff…

【算法】差分

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;合理规划时间与精力&#x1f43e; 1.什么是差分&#xff1f; 与前缀和是反函数 原数组a a1 , a2 , a3 , a4 , a5 , a6 , a7 构造数组b a1b1; a2b1b2; a3b1b2b3; … aib1b2b3…bi; 构造一个b数组使得&#…

【Linux】信号量

&#x1f387;Linux&#xff1a; 博客主页&#xff1a;一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 看似不起波澜的日复一日&#xff0c;一定会在某一天让你看见坚持…

Java基础:面向对象进阶

1.static 1.static概念 工具类 2.static内存图 静态变量是随着类的加载而加载的,优于对象出现 3.static的注意事项 1.静态方法中,只能访问静态 : 因为非静态方法一般会传入调用方法的对象的地址this(一般是虚拟机自动调用,不需要手动传入, 如student.study()).但是由于静态方…

MapStruct使用指北

mapstruct官方文档链接&#xff0c;点击跳转 mapstruct是什么&#xff1f; MapStruct 是一个代码生成器&#xff0c;它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。 生成的映射代码使用简单的方法调用&#xff0c;因此速度快、类型安全且易于理解。 为…

介绍一款HCIA、HCIP、HCIE的刷题软件

华为认证考试分为三个等级&#xff0c;分别为工程师HCIA、高级工程师HCIP、专家HCIE&#xff0c;等级越高&#xff0c;考试难度越大。 本篇带大家详细了解华为数通题库刷题工具的详细操作步骤。 操作须知&#xff1a;本款刷题工具为一款刷题小程序&#xff0c;无需安装即可在线…

vue2源码之生命周期篇

vue2源码之生命周期篇vue2源码之生命周期篇生命周期流程图初始化阶段&#xff08;new Vue&#xff09;vue2源码之生命周期篇 生命周期流程图 从图中可以看到&#xff0c;Vue实例的生命周期大致可分为4个阶段&#xff1a; 初始化阶段&#xff1a;为Vue实例上初始化一些属性&am…

YOLOv8 目标检测 | 自定义数据集

本文介绍了使用用于目标检测的自定义数据训练 YOLOv8 模型。我正在使用来自 kaggle 的 yolo 格式的“Face Mask Dataset”&#xff0c;数据集链接如下&#xff1a;https://www.kaggle.com/datasets/maalialharbi/face-mask-dataset?resourcedownloadYOLOv8 是目前最先进的 YOL…

一【 mybatis的工作流程】

目录一.mybatis执行流程二.使用工具类简化项目&#xff08;反射的体现&#xff09;2.1 Sqlsession工厂对像2.2 工具类&#xff08;可直接放在工具类使用&#xff09;一.mybatis执行流程 1.1 读取主配置文件mybatis-config.xml&#xff0c;获得运行环境和数据库连接。 1.2 加载映…

35.网络结构与模型压缩、加速-2

35.1 Depthwise separable convolution Depthwise separable convolution是由depthwise conv和pointwise conv构成depthwise conv(DW)有效减少参数数量并提升运算速度 但是由于每个feature map只被一个卷积核卷积,因此经过DW输出的feature map不能只包含输入特征图的全部信息,…

【C/C++基础练习题】复习题三

C复习题知识点记录&#xff1a; 在定义结构体类型时&#xff0c;不可以为成员设置默认值。 在公用一个共用体变量时。系统为其分配存储空间的原则是按成员中占内存空间最大者分配 a ,La, "a", L"a" 字符 长字符 字符串 长字符串 布尔类型只有两个值 fal…

蓝桥杯C/C++VIP试题每日一练之2n皇后问题

💛作者主页:静Yu 🧡简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者 💛社区地址:前端知识交流社区 🧡博主的个人博客:静Yu的个人博客 🧡博主的个人笔记本:前端面试题 个人笔记本只记录前端领域的面试题目,项目总结,面试技…

C语言(文件,流,键盘输入和输出以及文件结尾)

目录 一.文件 二.流 三.输入和输出 二.文件结尾 一.检测方法 二.不同的操作系统&#xff0c;文件结束方式 3.使用scanf检测EOF 一.文件 文件&#xff08;file&#xff09;是存储器中存储信息的区域。通常&#xff0c;文件都保存在某种永久存储器中。文件对于计算机系统相当重要…

【C++之容器适配器】栈和队列模拟实现

目录前言一、栈(stack)1. 基本代码结构2. 简介3. 成员类型4. 成员函数1. 构造函数2. empty()3. size()4. top()5. push()6.pop()7. 综合测试实现的stack的所有函数接口二、队列(queue)1. 基本代码结构2. 队列的简介3. 成员类型4. 成员函数1. 构造函数2. empty()3. size()4. fro…

leaflet 上传KML文件,导出geojson文件(065)

第065个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中本地上传kml文件,利用解析此kml文件,在地图上显示图形。点击导出geojson,将下载为geojson文件。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代…

ChatGPT 怎么用最新详细教程-新手小白一看就会

ChatGPT 以其强大的信息整合和对话能力惊艳了全球&#xff0c;在自然语言处理上面表现出了惊人的能力。这么强大的工具我们都想体验一下&#xff0c;那么 ChatGPT 怎么用呢&#xff1f;本文将给你逐步详细介绍。使用 ChatGPT 主要有4步&#xff1a;注册 ChatGPT 账号通过短信接…