• javaSE复习


    java
    基础
    基础
    特性
    简单,面向对象,可移植,解释执行(先编译为class文件,然后jvm解释执行)
    修饰符
    作用域
    private(同类)、protected(同包和子类)、default(同包)、public(所有)
    static
    static修饰的变量内存只有一份,static修饰的变量和方法可以直接通过类名访问。static修饰的代码块只会执行一次
    final
    final修饰的变量成为常量,final修饰的方法不能被覆盖,final修饰的类不能被继承
    super
    可以调用父类的方法和属性
    数据类型
    基本数据类型
    整数型(byte,short,int,long)
    浮点数型(float,double)单双精度
    字符型(char 2)
    布尔型(boolean 1)
    引用数据类型
    类和接口、数组
    变量类型
    常量(final 修饰,无默认值,存在常量池)
    类变量/静态变量(static修饰,有默认值,存在方法区)
    成员变量/实例变量(方法外,有默认值,存在堆中)
    局部变量(方法内,无默认值,基本数据类型存在栈中,引用数据类型的变量在栈,但指向的对象在堆中)
    面向对象
    定义
    面向过程注重事情的每个步骤及顺序,面向对象注重参与事情的对象有哪些,以及他们各自需要做什么
    三大特性
    封装
    将类的某些信息隐藏在内部(private),对外通过方法调用,不用关心具体实现。减少耦合
    继承
    从已有的类中派生出新的类,能继承并扩充已有类的属性和方法(父类private不能继承)
    多态
    父类或接口定义的引用变量可以指向不同子类的实例对象,实现对引用变量的同一个方法调用,执行不同的逻辑。让代码更好的扩展和维护。
    特殊类
    抽象类(abstract)
    “是不是”的概念,1.不能创建对象,只能被继承;2.可以定义抽象方法。
    接口(interface)
    “有不有”的概念,1. 方法都是抽象的,变量都是final修饰的常量。2. 不能创建对象,只能被类实现或者被接口继承。3. 1.8后新增default方法,不是抽象方法,不用实现。
    内部类
    作用
    1. 内部类可以更方便访问外部类成员(否则只能通过外部类对象访问),2. 每个内部类都能独立的继承一个接口的实现,使多继承变得更加完善.
    注意
    1. 内部类和外部类可相互访问private属性。2. 非静态的内部类不能定义静态方法和变量。
    调用内部类变量或方法
    1. 间接调用(通过外部类方法调用)。2. 直接调用(定义内部类对象调用)Outer.Inter inter=new Outer.new Inter();
    匿名内部类
    new对象时,直接接创建类new X{public void f(){…}}
    高阶
    泛型
    含义
    类定义时不设置属性或方法具体类型,实例化的时候再指定具体类型。优点:代码重用、保护类型的安全以及提高性能。
    泛型通配符
    class A{}:可以定义任意类型
    class A{}:只能定义B或B的子类
    class A{}:只能定义B或B的父类
    反射
    具体:反射就是先得到Class类对象,再通过该对象获得它的成员变量/构造方法/成员方法,最后分别通过成员变量/构造方法/成员方法的对象调用对应的方法
    作用:可以动态获取类的信息, 提高代码灵活度,框架中比较常见
    具体流程:
    准备阶段:编译期将每个类的元信息保存在Class类对象中
    获取Class类对象:三种方法x.class/x.getClass()/Class.forName()
    实际反射操作:根据获取的Class对象,来获取属性,方法,构造方法(Filed/Method/Constructor)
    动态代理
    作用:类似Spring的aop,可以动态的,不修改源代码的情况下为某个类增加功能,如在一个方法的前后添加一下功能。
    异常
    Throwable
    Error(错误):程序无法处理
    栈/内存溢出
    虚拟机运行错误
    Exception(异常):程序可以处理
    RuntimeException及其子类:如下标越界
    非RuntimeException异常:可查异常,需要try catch 或throws。如文件上传
    IO
    字节流:以字节为单位,可处理任意类型数据
    字符流:以字符为单位,一次性可读多个字节,处理字符类型数据。
    集合
    collection(单列集合)
    list(可重复,有序:元素存取顺序一样)
    查快:ArrayList
    底层数组,查快,增删慢,线程不安全,效率高
    ArrayList的扩容:使用无参构造方法时,初始大小是0,当有数据插入时,扩展到10。每当容量到达最大量就会自动扩容原来容量的1.5倍(会把老数组元素重新拷贝一份到新数组,代价较高,所以知道初始容量,可以初始化时指定一个初始容量)。
    线程安全
    Vector:线程安全,结构跟ArrayList类似。内部实现直接使用synchronized 关键字对 每个方法加锁。性能很慢。现在淘汰(synchronizedList跟Vector类似,区别是使用synchronized 关键字对 每个方法内部加锁)
    CopyOnWriteArrayList:线程安全,在写的时候加锁(ReentrantLock锁),读的时候不加锁,写操作是在副本进行,所以写入也不会阻塞读取操作,大大提升了读的速度。缺点:写操作是在副本上进行的,数组的复制比较耗时,且有一定的延迟。
    删快:LinkedList
    底层双向链表(所以可以作为栈和队列),查慢,增删快,线程不安全,效率高
    set(唯一,无序:元素存取顺序不同)
    未排序:HashSet
    底层hashMap(对应value为object常量对象),先判断对象hashcode是否重复,如果不重复那肯定就没添加。如果重复再通过equals比较。
    有一个子类LinkedHashSet,底层为链表和哈希表,依赖链表保证有序
    排好序:TreeSet
    底层红黑树(Compareable保证唯一)
    map(双列集合,无序)
    未排序:HashMap
    线程不安全,底层数组+链表+红黑树(1.8之前为数组+链表)。(扩展:1.8后链表插入从头插法改为了尾插法,因为头插法在多线程中可能导致死循环:在扩容时,头插法打乱了链表的顺序,第一个线程扩容后,顺序相反。此时第二个线程再进行扩容时,就会出现死循环,本来a节点的next时b,单第一个线程扩容后b的next节点是a)
    hashMap添加和扩容
    put添加过程
    1. 根据key获得哈希码,计算数组下标位置。
    key通过hashcode获得hash码
    位置计算:hash码&(length-1),比  hash值%数组长度  更快
    2. 如果对应位置没有元素  就直接把封装好的对象放到该位置。如果对应位置是红黑树节点,就把新节点放到红黑树节点上(期间会判断是否存在key,存在就更新)。如果是链表节点,就通过尾插法插入链表节点,然后判断节点数大于等于8就转为红黑树。
    3. 最后判断是可否需要扩容
    扩容机制(默认大小16)
    先生成新数组(默认原两倍),然后遍历老数组每个元素,计算对应新数组位置。如过某个位置元素大于等于8就转红黑树。
    加载因子
    默认0.75(hashmap默认大小16,乘以加载因子为12,所以达到12就进行扩容。)加载因子越大,空间利用率越高,但冲突机会增加。
    线程安全的
    线程安全的HashTable,底层数组+链表,通过Synchronized对整张表加锁保证安全,现已淘汰
    线程安全的concurrentHashMap 
    1.7是segment数组+hashEntry实现。一个segment中包含一个hashEntry数组,hashEntr是链表结构。线程安全是通过ReentrantLock对Segment数组加锁实现,
    1.8后是数组+链表+红黑树实现。线程安全是通过synchronized对头节点加锁保证线程安全,相对1.7减少了加锁的粒度。(扩展:1. 读不需要加锁因为使用volatile对元素进行修饰,其他线程修改时是可见。2. 初始化和添加元素通过cas和volatile保证线程安全)
    根据key排好序:TreeMap
    底层红黑树,各操作O(logn)
    线程
    状态
    新建状态,new一个线程后
    就绪状态,调用start后
    运行状态,执行run方法
    阻塞状态,如调用sleep,wait等方法后
    终止状态,如线程正常结束,或者调用stop等
    开启线程三种方式
    继承Thread类,重写run方法。(代码:Thread t=new MyThread()😉
    实现Runnable接口,在类中实现run()方法(代码:Thread t=new Thread(new MyThreadt())😉
    实现Callable接口,实现call方法,再结合FutureTask 创建线程(可获取线程结束后的返回值)(代码:Thread t = new Thread(new FutureTask<>(new MyCallable));)
    比较:接口实现优势:1.避免java中的单继承的限制,2. 使用接口的方式能放入线程池。(Callable较Runnable而言,线程可以有返回值)
    线程相关方法
    Thread.sleep(long millis) :线程睡眠,线程转到阻塞状态(不释放锁)
    Obj.wait() :线程等待,线程转到阻塞状态(释放对象锁,需要notify唤醒)
    Obj.notify(): 线程唤醒,唤醒等待中的线程(注:它不是立即唤醒,notify()是在synchronized语句块内,在synchronized(){}语句块执行结束后当前线程才会释放对象锁,唤醒其他线程)
    Thread.yield() :线程让步,让相同优先级的线程之间能适当的轮转执行
    join(): 线程加入,在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
    interrupt():线程中断,向其他线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出异常。
    注:线程优先级范围1-10,默认为5。main()本身也是一个线程,当main结束后,子线程仍会继续执行到结束。每个程序至少有两个线程,main和垃圾回收线程(每个程序启动都会启动一个jvm)
    线程池
    定义
    可以容纳多个线程的容器,其中线程可以反复使用,避免反复创建线程的开销(同时新来任务也不用去创建线程比较快)。
    创建线程池步骤
    创建线程池:ExecutorService service = newFixedThreadPool(5);//(读音 e g z ki ter)   另一种创建方式: ExecutorService service = Executors.newFixedThreadPool(5);//
    加入线程:service.submit(new MyCallable());//或者service.submit(new MyRunnable ());加入后会自动调用run方法
    关闭线程池:service.shutdown();
    线程池的七大参数
    常驻核心线程数(corePoolSize):线程中长驻的核心线程
    最大线程数(maximumPoolSize):大于等于1
    多余的空闲线程存活时间(keepAliveTime):线程数超过常驻核心线程数时,多余线程的空闲时间达到keepAliveTime时就会销毁
    unit:keepAliveTime的单位
    任务队列(workQueue):被提交但是尚未被执行的任务。当任务达到核心线程数过后,就会放到任务队列中,任务调度时候再取出
    线程工厂(threadFactory):用于创建线程
    拒绝策略(handler):当工作线程大于等于最大线程数且任务队列也满了时,就会触发拒绝策略(4种:1. 异常策略(AbortPolicy):直接抛出异常。2. 丢弃策略DiscardPolicy:新来任务被提交后直接丢弃。3. 淘汰最久任务策略DiscardOldestPolicy:丢弃存活时间最长的任务。4. 执行策略(CallerRunsPolicy):让提交任务的线程自身去执行该任务)。
    线程池执行流程
    提交任务
    判断核心线程是否满,没满创建线程执行提交任务(就算有空闲线程也会创建,保证创建满核心线程数)
    核心线程满判断任务队列是否满,没满放任务队列,等核心线程空闲再去执行。
    任务队列也满,判断最大线程数是否满,未满创建线程,满就触发拒绝策略
    ThreadLocal
    介绍:线程本地存储机制,它可以将数据缓存到某个线程内部,因为共享区的数据为了安全会使用加锁等机制,从而影响效率。
    底层:每个线程都有一个ThreadLoacalMap:数据是以key为ThreadLocal对像,值为数据缓存到ThreadLocalMap中的,
    场景:比如把用户信息存入Token中,当用户调用接口时,拦截器中解析Token(header中携带 Token),把用户信息存在ThreadLocal中,然后就可以在controller,service等多层方便的共享用户数据,并且能保证对每个请求,都只能访问当前请求的用户信息
    注意:因为线程池中的线程不会销毁,所以ThreadLocal对应的值也不会被回收会导致内存泄漏,所以建议在使用完后remove掉。
    和加锁对比:加锁用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
    锁(保证线程安全)
    Synchronized和ReentranLock
    不同点
    Synchronized是一个关键词(jvm层面的锁),ReentranLock是一个类(API层面的锁)
    Synchronized可以修饰方法和代码块(在方法前加static 可锁住类的所有对象),Jvm自动释放锁。ReentranLock只能修饰代码块。需要手动释放锁
    Synchronized锁的是对象,锁信息保存在对象头中的Markword中(记录四个锁状态),ReentrantLock锁的是线程,锁的信息保存在AQS 中
    相同点:Synchronized和ReentranLock都是可重入锁
    就是在一个线程中允许你反复获得同一把锁,若多次加锁记需要多次解锁才能释放(实现:每个锁关联一个请求计数器和一个获得该锁的线程,加一次锁计数器加1)
    为什么需要可重入锁:最大程度避免死锁(对象中加锁的A方法调用加锁的B方法,就会导致死锁,可重入锁允许一个线程反复或的一把锁,就不会导致死锁)
    ReentrantLock 相对 synchronized 多了三种功能
    1.等待可中断(正在等待的线程可以选择放弃等待,改为处理其他事情。)
    2.可实现公平锁(默认非公平)
    公平锁:在获取锁时,会先检查AQS同步队列是否有线程在排队,有就进行排队(AQS先进先出的双向队列)
    非公平锁:获取锁时不会检查是否有线程在排队,而是直接竞争锁(如果没竞争到锁,后面就跟公平锁一样也会去排队,当锁释放只会唤醒AQS队列的首个线程,非公平只体现在加锁阶段,不体现在唤醒阶段)
    3.可绑定多个条件,实现有选择的唤醒等待(synchronized结合notifyAll()会唤醒所有等待的线程。Reentrantlock中一个锁对象可以创建多个对象监视器,线程对象可以注册在指定的对象监视器中,从而可以有选择性的进行线程通知)
    Synchronized相关
    为什么说Synchronized是一个重量级锁
    Synchronized 底层实现依赖于操作系统的互斥锁。而操作系统实现线程之间的切换需要从用户态转换到核心态,转换耗时,成本非常高。这种依赖于操作系统互斥锁的称为重量级锁。
    锁机制如何升级
    1.6之前就直接是重量级锁,但效率比较低,所以后面引入锁升级机制,用于平衡安全和效率的问题 。
    偏向锁:在对象头中记录当前获得该锁的线程id,下次该线程就可以通过偏向锁直接获取到资源。(因为大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁)
    轻量级锁:每个竞争线程通过自旋不停看被锁住资源是否释放。避免用户态到内核态的切换。
    重量级锁:由操作系统来判断锁住资源是否释放,释放再通知其他线程。
    过程
    先尝试用偏向锁的方式去竞争资源。
    如果失败就表示其他线程已经占用了偏向锁,此时升级成轻量级锁,通过自旋的方式尝试去加锁。
    如过多次竞争失败就会升级成重量级锁(因为太多线程不断自旋竞争对效率影响也比较大),此时没竞争到锁的线程会被阻塞,
    双重检查锁DCL(Double Check Lock)
    作用:尽可能减少加锁的范围(锁如果加在第一重,初始化除了创建对象可能还涉及其他赋值操作,所以加锁范围太大。在初始化中创建 对象为什么还要二重检查?因为第一重检查未加锁,所以可能导致多个线程都判断对象为空,从而进入初始化方法,依次创建多个对象)
    volatile
    作用:保证可见性(强制将修改的值立即写入主存)、有序性(禁止进行指令重排序)。
    扩展:普通共享变量修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
    指令重排:指令重排序是编译器处于性能考虑,源码顺序和程序顺序可能不一样。例子:比如new一个对象,底层是三步(1.分配内存空间。2. 执行构造方法,初始化对象。3. 把这个对象指向空间。)所以可能导致A线程执行了1,3。b线程判断此时对象为空,然后又执行初始化操作。
    voalate和synchronized区别:
    并发编程三个重要的特性是原子性、有序性、可见性。
    原子性:synchronized通过互斥锁。voalate不保证原子性(可以和cas操作搭配保证原子性)
    有序性:synchronized通过程序串行化执行,volatile通过禁止指令重排。
    可见性:volatile通过强制将修改的值立即写入主存,synchronized通过jvm指令monitorexit把共享资源都刷新到内存保证
    CAS操作:非阻塞原子性操作(通过硬件保证原子性),同一时刻只有一个线程可以修改,其他线程并不会阻塞而是重新尝试
    ReentrantLock相关
    使用:先创建一个ReentranLock对象,然后通过lock()和unLock()方法加锁和释放锁。还有tryLock()尝试加锁,可以用于自旋。
    加锁过程
    ReentrantLock中包含一个AQS对象,这个对象中又三个核心变量:1. 加锁状态state(0表示未加锁)2. 加锁线程。3. 等待队列(Node双向链表)
    加锁过程:线程尝试通过CAS操作将state值从0变为1,如果修改成功就把加锁线程设置为自己。修改失败就看看加锁线程是否为自己,不是就进入等待队列。当持有锁的线程释放后会唤醒队列首个线程
    AQS
    定义:AQS是java并发包的基础类,java并发包下很多API都是基于AQS来实现的加锁和释放锁等功能的,如ReentrantLock。
    JUC线程并发库
    并发容器类:ConcurrentHashMap(线程安全)
    锁相关类:ReentrantLock
    线程池相关的类:Callable,Executor(创建线程池的)
    面试题
    比较
    深拷贝和浅拷贝
    浅拷贝:只复制一个对象的引用
    深拷贝:把对象完全复制一份
    重写和重载
    重写是子类重写覆盖父类的方法(注意:重写方法的 修饰符范围不能小于父类,且private方法不能重写)
    重载是同一个类中方法名相同,参数不同
    序列化和反序列化
    序列化:把对象拆解成字节碎片(可用于对象的持久化)
    java实现:先把需要序列化的类实现Serializable接口(标志作用),通过对象输出流ObjectOutputStream把对象转换成字节碎片,然后通过文件输出流FileOutputStream把字节碎片写入文件。(还可以指定一个序列化id号,防止进出版本不一致)
    jdk、jre和jvm
    jdk:它是java开发工具包。包含:java编译器,Java运行环境jre,java常用类库
    jre:它是Java运行环境,包括jvm和jvm所需类库
    jvm:java虚拟机,用于运行字节码文件(会将字节码文件解释为机器指令,不同操作系统机器指令不同,所以不同操作系统的jvm也不同,但都运行字节码文件)
    、equals()和hashCode()
    :如果是基本类型,则比较的是值,引用类型比较的是引用地址。
    equals:具体比较什么是看类中equals方法重写逻辑(默认是Object中的equals跟
    是一样的)
    hashcode:hashcode是获取一个对象的hash码,可以根据hash码找到对象在堆中的位置(堆中有一个hash表与之对应)。
    hashcode扩展
    两个对象相等,hashcode一定相等。hashcode相等,对象不一定相等。
    为什么重写equals时也要重写hashcode方法:因为java中一些集合类比如hashset判断两个对象是否相等会先判断hashcode,所以如果不重写hashcode,就会导致该类与其他集合类一起工作时出问题。
    hashcode意义:HashSet集合添加元素,需要判断是否添加重复元素。如果用equals一个个比较太慢了,所以可以先判断对象hashcode是否重复,如果不重复那肯定就没添加。如果重复再通过equals比较。
    String、StringBuffer和StringBuilder区别
    String:底层时final修饰的byte数组(所以不可被继承)。若频繁修改字符串时,会产生很多无用的中间对象,效率很低。
    StringBuilder :底层也是一个byte数组,但不是常量,当容量不足时会进行扩容。线程不安全。
    StringBuffer:在StringBuilder基础上考虑了线程安全。通过synchronized为每个方法加锁保证线程安全。效率较低
    int和Integer
    区别
    Integer:Integer是int提供的封装类;Integer对象需要实例化,默认值为null。
    int:int是基本数据类型,直接存储数值,默认值是0;
    数值比较
    Integer和int比较:Integer是int的封装类,int与Integer比较时,Integer会自动拆箱,无论怎么比,int与Integer都相等
    Integer和Integer比较:通过equals比较,但对于数值在-128与127之间的Integer对象,会缓存在内存中。所以会直接从内存取,不会创建新的对象,所以也可以通过
    比较。
    拆箱装箱原理:装箱是编译的时候自动调用vulueOf(),拆箱是调用intValue()方法。
    run()和start()
    run方法就是一个普通的方法,而start方法会创建一个新线程去执行run()的代码。
    概念
    new对象初始化顺序
    父类静态代码块,子类静态代码块,父类构造方法,子类构造方法
    构造方法有哪些特性
    名字与类名相同,没有返回值,但不能用 void 声明构造函数,创建对象时自动执行
    一致性hash算法
    优化分布式环境下,主机增加或减少情况下hash映射的问题。普通hash会全部重新隐射。一致性hash通过hash环的数据结构来优化。把主机iphash后放到hash环上,然后对key hash后,放到顺时针方向离自己最近的主机上。所以主机增加或减少时只需少部分数据需要重新映射
    具体:环范围0-2^(32-1),主机ip hash后放到环上,节点key hash后放到顺时针最近最近上。(数据倾斜可以通过虚拟节点解决——每个主机创建多个虚拟节点,放到环上)
    java8新特性
    lambda表达式
    定义:语法更简单,本质就是创建一个接口实现类的具体对象。(对应接口只能有一个抽象方法,可以在接口加一个注解,限制其只能有一个抽象方法)
    例子:X x = ()->System.out.print(“1”);//()对应的就为接口中无参方法,箭头后就为实现的内容。
    作用:简化代码,适用于代码不复用的场景
    函数式接口
    可以在接口加一个注解,对应接口只能有一个抽象方法,与lambda连用
    方法引用
    lambda深层引用,如果在lambda表达式中,函数式接口的参数和返回值  和  方法体实现中的方法的的参数和返回值一样就可以使用
    三种使用情况:
    对象::实例方法名(非静态方法)例子:X x = y::方法名; x.方法名(参数)(y为方法体中的方法对应的对象)
    类::静态方法名
    类::实例方法名(还要满足条件:两个参数,第一个参数为方法调用者,第二个参数为方法的实际参数)
    接口新增default方法
    不是抽象方法,不用重写
    修改一些底层实现
    如hashmap,底层新增红黑树结构
    新增一些api(日期相关)
    为什么数组具有快速查找的能力
    数组是连续的内存地址,且元素都是同一类型,占用空间大小一样,所以通过下标就可以计算出元素的位置。
    浮点数
    IEEE754标准
      float占4字节,32位(1个符号位,8个指数位,23个尾数位)指数对应表示大小范围:8位指数大小范围是[-127,128]),所以表示数值范围是-2128到2128。尾数对应精度:23位尾数,能表示最大十进制为2的23次方为7位数,所以完整精确表示的为6位。(省略位为1,所以不算进去)
    double占8字节,64位(1个符号位,11个指数位,52个尾数位)表示10进制精度位15位。表示数值范围为-21024到21024
    十进制数值存储过程(以float为例)
    先把10进制转换为2进制,然后规范化(科学计数法)。对于符号位:正数存0,负数存1。对于指数位:因为指数位8位范围是[-127,128],转二进制存计算机时,为了避免负数,加上一个固定偏移量127,然后转二进制存入8位指数位。对于尾数:由于规范化后首位都为1,所以省略首尾,将小数点后的位数放入尾数部分,不足补0。
    例子:10.75转为二进制为1010.11,规范化为1.0101110^3。 正数符号位为0。尾数为省略首位后为01011。指数为3+127=130,换算为二进制为10000010。所以最终10.75存在计算机就为:0   10000010   01011000000000000000000
    扩展:十进制转二进制
    正数:除2取余,倒叙排列。如2:2/2得1取0;1/2得0余1取1。最后得10
    小数:乘2取整,正序排列。如0.25:0.25
    2=0.5取0,0.5*2=1.0取1。最后得到01
    注意事项
    1. 比较两个浮点不要用==,而是做差是否在一定范围(一般差小于10的-6次方)。2. 尽量使用double而不是float(因为float精度太低 )。3.金融场景一定要使用BigDecimal(无精度损失)
    扩展
    int占4字节,32位,除去符号位还剩31位。范围为[-231,231-1]。(因为31可以表示231个正负数。分别为[0,231-1]和[-231+1,-0]。因为只需要一个0,而且-0的原码加上符号位,恰好等于-0的补码,所以负数要多一个,最后范围为[-231,2^31-1])
    正数:原码=反码=补码。负数:反码为原码符号位不变,其他位相反;补码为反码加1。(计算机都存的补码)

  • 相关阅读:
    d共享左值
    Aspose.Slides for Python演示文稿指定幻灯片放映设置
    深度学习入门(5) - RNN
    Python爬虫实战-批量爬取美女图片网下载图片
    什么时候一个变量应该以state的形式出现?
    奥巴马竞选演讲及相关视频下载(用迅雷可以下)
    unity学习 -- 游戏物体(对象)以及相关操作工具
    定时任务管理系统详细设计说明书
    壳聚糖-紫杉醇|Chitosan-Paclitaxel|紫杉醇-PEG-壳聚糖
    wifi感知技术
  • 原文地址:https://blog.csdn.net/Y734493585/article/details/126550882