JAVA 学习笔记

数据结构

树:只有一个根节点;子树1个前驱,0个多个后驱。子树之间不能有交集。

概念

  • 节点的度:一个节点含有子树的个数称为该节点的度
  • 叶节点:度为为0
  • 树的度:最大节点的度
  • 节点的层次:根为1层,往下2层,以此类推
  • 树的高度或者深度:最大层数
  • 兄弟节点:同根
  • 堂兄节点:同层不同跟

二叉树:不存在节点大于2的度;二叉树有左右之分,次序不能颠倒,因此是有序树。

对于任意二叉树都是由以下几种情况复合而成的。 

满二叉树:子节点都在一层 完全二叉树:前n-1层是满二叉树,最后一层子节点连续 平衡树:左根右递增,子树高度差小于等于1,追求绝对平衡,插入删除旋转次数未知 增删平均时间复杂度为logn 红黑树:左根右递增,根节点和叶子结点是黑色;红节点有两个黑节点;任意节点到每个叶子结点路径上的黑节点数量相同 增删查复杂度最坏为logn,最长路径不大于最短路径的两倍,追求大致平衡,插入删除最多三次旋转

树的表示

二叉树的性质

①若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2i-1个结点。\n②若规定根结点的层数为1,则深度为h的二叉树的最大结点数为2h-1个。\n③对任何一棵二叉树,如果度为0的叶结点个数为n0,度为2的分支结点个数为n2,则有n0 = n2+1。(常用这个性质解选择题)\n④若规定根结点的层数为1,则具有N个结点的满二叉树的深度h = log2(N+1)。\n⑤对于具有N个结点的完全二叉树,如果按照从上至下、从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点:\n1、若 i > 0,则该结点的父结点序号为:( i - 1) / 2;若 i = 0,则无父结点。\n2、若2i + 1 \u003C N,则该结点的左孩子序号为:2i + 1;若2i + 1 >= N,则无左孩子。\n3、若2i + 2 \u003C N,则该结点的右孩子序号为:2i + 2;若2i + 2 >= N,则无右孩子。

二叉树的存储结构

顺序存储一般用来存储完全二叉树,否则会造成空间浪费。现实只有堆会用数组存储。 链式存储,用链表表示二叉树。每个节点为左右指针域和数据域。 

 

二叉树的遍历

深度遍历

  • 前序 根左右
  • 中序 左根右
  • 后序 左右跟 广度遍历:层次遍历

排序算法,查找算法

  • 插入排序:从第二个元素开始,每选择一个元素,这个元素前面就是有序区间,后面就是无序区间。在有序区间选择合适位置插入。 O(n^2) 稳定

  • 希尔排序:将数据分成n组,进行排序,逐渐缩小n值 O(n^1.3~1.5) 不稳定

  • 选择排序:每次将最大或者最小放到最后,知道所有都排完 O(n^2)不稳定

  • 冒泡排序:在无序区间,通过相邻数的比较,将最大的数据放入一侧,持续整个过程,直到数组整体有序。 时间复杂度: 最坏情况 O(N^2) 最好情况 O(N) (一趟就有序了) 空间复杂度: O(1) 稳定性: 稳定

  • 快速排序(重要) 1.找一个基准值,存储 2.比较左边和右边,小于的放到右边,大于的放到左边 3.然后对左右区间按同样的方式处理 nlogn 最坏n^2

选择排序和冒泡排序的区别

  • 冒泡排序是左右两个数相比较,而选择排序是用后面的数和每一轮的第一个数相比较;
  • 冒泡排序每轮交换的次数比较多,而选择排序每轮只交换一次;
  • 冒泡排序是通过数去找位置,选择排序是给定位置去找数;
  • 当一个数组遇到相同的数时,冒泡排序相对而言是稳定的,而选择排序便不稳定;
  • 在时间效率上,选择排序优于冒泡排序。
  • 两种算法的最坏情况复杂度相同,即O(n^2),但最佳复杂度不同。冒泡排序使用n个时间顺序,而选择排序使用 n ^ 2个时间顺序

堆排序

将数组放入堆,调整每一颗子树,使之变成大根堆。 O(n * logN) O(1) 不稳定

 

java

代码解读

复制代码

package com.ln.mybatis.sort; import java.util.Comparator; import java.util.PriorityQueue; //求前k个最小的元素 public class sortTest { public static void main(String[] args) { int test[]={10,11,12,32,434,1,2,3,4,5,6,7,8,9,10,11,12,32,434,54645,6565,757,323,32}; int[] topk=topK(test,3); for (int i = 0; i < topk.length; i++) { System.out.println(topk[i]); } // System.out.println(topK(test,3)); } // 找出数组中最小的元素,建立大根堆,然后对根节点进行比较,大则不放,小则替换 public static int[] topK(int[] arr,int k){ // 创建一个大小为k的大根堆 PriorityQueue<Integer> maxHeap =new PriorityQueue<>(k, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return 02-01; } }); for (int i = 0; i < arr.length; i++) { if(i<k){ maxHeap.offer(arr[i]); }else { if(maxHeap.peek()>arr[i]){ maxHeap.poll(); maxHeap.offer(arr[i]); } } } int[] ret=new int[k]; for (int i = 0; i <k ; i++) { ret[i]=maxHeap.poll(); } return ret; } }

雪花算法

组成: 1.第一位为符号位默认0不使用 占用1bit,2.时间戳:41bit 3.机器id 10bit 4.序列号 12bit 同一毫秒同一个id可以生成4096个序列号

时间回拨生成重复id的问题 1.回拨时间短不生成id 2.回拨时间长使用扩展位

java基础

java1.8的特性

  1. lambda表达式(在stream和optional用方法引用)
  2. 函数式接口,如predicate,function和consumer(consumer统一处理外部调用的异常)
  3. streamAPI(集合处理)
  4. 接口可以包含默认方法
  5. Optional类,避免显示的null检查,处理可能为null的值
  6. 新日期和时间 API:java.time 包,替代了老旧的 java.util.Date 和 java.util.Calendar 类(线程安全,时区处理)

String、StringBuffer、StringBuilder的区别

JAVA的集合类型以及线程安全的集合

LIST SET HASH vector,hashtable CopyOnWriteArrayList,CopyOnWriteArraySet,ConcurrentHashMap 底层大都采用Lock锁 ConcurrentHashMap不用

Java是引用传递还是值传递

是值传递 值传递:传递的是参数的拷贝,操作参数不会影像实际参数 引用传递:传递的是参数的地址,操作参数会影响实际参数

lambda

lambda 可以访问外部变量 但是变量不可变,引用不可变。

c和java的区别

1.c面向过程,执行效率高 j面向对象,执行效率低

2.j跨平台,c,c++,c#都需要在特定的系统中执行

3.c有指针,没有垃圾回收机制,j没有指针,有垃圾回收机制

4.c可以调用系统指令,j不可以,因此j中只有线程没有进程的概念,c两者都有

5.文件组织方式不一样,j是类,c中把全局变量和方法的声明放在头文件中

jvm

概念 1.JVM是java虚拟机,用来执行字节码文件(二进制 class文件)的虚拟计算机。除了java,Scala,Groovy和Python等其他语言经过处理也可以转换成字节码文件。 2.JVM运行在操作系统上,和硬件没有任何关系。

跨平台原理:编译后的字节码文件和平台无关,在java虚拟机上运行。统一的class文件结构,就是jvm的基石。 JVM分类

  • 类加载器子系统
  • 运行时数据区
  • 执行引擎
    • JIT编译器(主要影响性能):编译执行
    • 解释器(负责响应时间):逐行解释字节码

程序执行方式有三种,静态编译执行,动态编译执行,动态解释执行。 在java中,程序的执行以动态解释为主,动态编译为辅。(静态编译如C,直接编译成可执行文件exe) 机器码和字节码的区别: 机器码是CPU直接读取,速度快;字节码需要直译器转译后才能变成机器码。

JDK包括了编译器等开发工具和JRE JRE包括了运行类库和JVM JVM有两种运行方式 client和server Client启动快,运行慢。Server启动慢,运行快。

JVM流程 .java文件编译器解释为class文件,交给jvm执行引擎执行,执行时会用空间存储数据,就是JVM内存。 JVM内存主要为:堆,栈,方法区,本地方法区,程序计数器。

  • 程序计数器 当前线程执行字节码的行数指示器,用来记录虚拟机字节指令地址,线程私有。执行本地方法时为空。也称为PC寄存器。 字节码解释器在工作时,通过改变计数器的值来选取下一跳执行的代码,分支,循环,跳转,异常处理,线程恢复等功能都依赖程序计数器完成。 Java虚拟机的多线程的实现方式:通过轮流切换并分配处理器执行时间实现。
  • 本地方法栈 和虚拟机栈作用类似,区别是一个执行java方法,一个执行native方法。线程私有。 与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。
  • 方法区(1.8为元数据区) 主要是存储类信息,静态变量,编译后的代码(字节码)等数据,常量池。线程共享
  • 栈 栈:创建线程时创建,存储栈帧,线程私有。栈桢在执行方法时创建,包括局部变量表,操作数栈,动态链接,方法出口等信息。 局部变量表:用来存储方法参数和方法中定义的局部变量 操作数栈:用于保存计算中的临时变量和中间结果,是JVM执行引擎的一个工作区,通过入栈和出栈进行数据访问。Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。 动态链接:指向方法区的运行时常量池

在Hotspot的演变过程中:

  • Java6及之前:方法区存在永久代,保存有静态变量
  • Java7:进行去永久代工作,虽然还保留着,但静态常量池,如字符串常量池,已经移动到堆中
  • Java8:移除永久代,类型信息、域(Field)信息、方法(Method)信息存放在元数据区;字符串常量池、静态变量存放在堆区

 堆中分为老年代和年轻代,年轻代中分为eden区和存活区,区中分为s0和s1 新生成的对象在Eden区 触发Minor GC后幸存的对象存入s0,再次触发Minor GC后,eden区和s0的对象存入s1中,s0清空。 每次移动,递增计数器,超过默认值15 (通过 -XX:+MaxTenuringThreshold 设置),移动到老年代中,eden中没有足够内存分配,也会分配到老年代。 老年代靠major GC。

新生代的回收机制采用复制算法,老生代采用的回收算法是标记整理算法。

堆和栈的区别

栈:创建线程时创建,存储栈帧,线程私有。栈桢在执行方法时创建,包括局部变量表,操作数栈,动态链接,方法出口等信息。栈中数据生命周期短,出栈即失效。栈超过虚拟机允许最大深度StackOverflow 堆:存储对象,线程共享。堆中数据声明周期长,由垃圾回收机制不定期回收。空间不够扩展申请不到足够的内存,oom

垃圾回收

参考【Java】垃圾回收 作用区域:频繁发生在年轻代,较少发生在老年代,极少发生在方法区(永久代/元空间) 引用类型才需要垃圾回收,基本数据类型不需要。 内存泄漏:这个对象不再使用,但是GC没法回收。 垃圾回收分为标记和清除阶段

标记阶段

引用计数法 引用对象+1,引用失效-1。为0则认为可以进行回收。 优点:实现简单,垃圾容易辨识;判定效率高,回收没有延迟 缺点: 1.需要单独的字段存储计时器,增加空间开销 2.每次赋值都需要进行加减法,增加时间开销 3.无法处理循环引用的情况 可达性分析算法 通过被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,走过的路径被称为引用链。当一个对象到GC roots没有任何引用相连时,证明该对象不可用。 同样具备实现简单和执行高效的特点,能有效解决循环依赖的问题,防止内存泄漏的发生。

可达性分析必须在一个能保证一致性的环境下进行。这点也是导致GC必须进行"stop the world"的一个重要原因。

所谓GC roots根集合就是一组必须活跃的引用。可以是: 1.虚拟机栈中引用的对象。 2.静态变量引用的对象,除非类卸载,否则他的引用对象一直存在。 3.所有被同步锁持有的对象。(同步锁要是被销毁,同步就失效了)

清除阶段

JVM中常见的清除方法:1.标记清除法 2.标记复制法 3.标记压缩法 标记清除法: 把存活的对象进行标记,清除死亡对象。 当堆中有效空间被用完,就会stw。然后进行标记和清除。要把用户线程停止保持一致性,防止用户线程产生垃圾。 缺点: 1、效率不高,需要遍历 2、进行GC时需要停止整个应用程序,用户体验差。 3、清理出来的空闲空间不是连续的,会产生碎片。 标记复制法: 内存分为两块,每次只用其中的一块。垃圾回收时,将存活的对象复制到未使用的一块,清除不可达的对象。 年轻代S0和S1也是用的复制算法。 优点:1.没有标记和清除的过程,实现简单,运行高效;2.复制后能保证空间的连续性 缺点:损失一半空间。 适合回收对象多的场景,复制少。适用于年轻代。 标记压缩算法 1.第一阶段和标记清除算法相同,从根节点开始标记被引用对象。 2.第二阶段将所有的存活对象压缩到内存的一端,按顺序排放,之后清理边界外所有的空间。 

 标记压缩算法等同于标记清除算法加压缩。 优点:1.没有碎片 2.不会内存减半 缺点: 1.效率低,因为会进行整理压缩 2.移动对象时,如果对象被其他对象引用,需要调整引用的地址。 3.移动过程中会stw 分代收集算法: 不同生命周期对象采用不同的算法,提高效率。 1.年轻代:区域小,生存周期短,回收频繁。 采用复制算法,内存利用率不高,hotspot中两个survivor的设计得以缓解 2.老年代:区域大,生命周期长,回收不频繁。 采用标记清除或者标记清楚整理算法。

Old GC: 只收集 old gen 的 GC。只有垃圾收集器 CMS 的 concurrent collection 是这个模式 Mixed GC: 收集整个 young gen 以及部分 old gen 的 GC。只有垃圾收集器 G1 有这个模式 年轻代的gc称为Minor GC。新生代Eden区满的时候触发Minor GC Full GC是回收整个堆。触发条件为: 1.System.gc 2.老年代空间不足 3.方法区空间不足 4.Minor GC后进入老年代的平均大小大于老年代的可用大小 5.由Eden区,from 区向to区复制,对象大于to的内存,也大于老年代的内存。 单例模式是静态的,生命周期长,如果中间引用了别的对象,那么这个对象一直不会被回收。

Major GC通常是跟full GC是等价的,收集整个GC堆,但也有说法是old GC。

异常类型

在这里插入图片描述

为了及时有效的处理异常,java引入了异常类。所有的异常都是Thorwable的子类。

Throwable下有两个分支Excepiton和Error。

异常类主要分为三种类型:

  • 系统错误Error

    系统错误是由虚拟机抛出的,用户无法处理,如

    OutOfMemoryError :内存耗尽 ; NoClassDefFoundError :无法加载某个Class ; StackOverflowError :栈溢出

  • 编译时异常:Exception (除了其子类RuntimeException)

    ​ 在编译时期抛出的异常,在编译期间检查程序可能出现的问题,如果有提前防范,捕获处理

    应用逻辑处理:

    • NumberFormatException :数值类型的格式错误;
    • FileNotFoundException :未找到文件;
    • SocketException :读取网络失败。

    编写逻辑造成:

    • NullPointerException :对某个 null 的对象调用方法或字段;
    • IndexOutOfBoundsException :数组索引越界
  • 运行时异常 RuntimeException

    • java虚拟机正常运行期间抛出的异常。这类异常只有在运行时才能发现是否有异常。

      RuntimeException,Error以及他们的子类都被称为免检类,其他异常被称为必检类(Checked Exception),编译器会强制程序员检查并try-catch处理,或者在方法头进行声明。如数组越界和空指针。

双亲委派机制

参考【Code皮皮虾】带你盘点双亲委派机制【原理、优缺点】,以及如何打破它? 双亲委派机制是在JDK1.2后才引入的。 加载类时不直接加载,委托给自己的父类加载器,递归直到加载成功。否则自己加载。 目的:1.防止类的重复加载。2.避免核心类遭到修改 Java提供四种类加载器:

  • BootStrap 启动类加载器:加载java核心类库 ,javahome/lib下的jar包,rt.jar等
  • Ext 扩展类加载器:加载java_home/ext/lib
  • Application 应用程序类加载器:主要用来加载当前应用claspath下的所有类。
  • User 用户自定义类加载器:用户自定义,加载指定路径下的类

什么时候破坏这个机制?

JDBC

 

java

代码解读

复制代码

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "0000");

获取连接时的DriverManager因为处于rt下,会被启动类加载器加载。 类加载时,会执行静态方法。其中会加载所有实现了Driver接口的实现类,但是这些实现类都是第三方提供的,启动类加载器无法加载,因此引入了ThreadContextClassLoader(线程上下文类加载器,默认情况下是AppClassLoader)来使用应用程序加载器,破坏双亲委派机制。 tomcat 比如tomcat web容器里面部署很多应用程序,但是每个应用依赖的第三方类库版本不同,但是类的全路径名可能相同。 双亲委派无法加载多个相同的class文件,因此tomcat给每个web容器单独同一个webAppClassLoader加载器。实现隔离性,优先加载Web应用自己定义的类,加载不到再交给CommonClassLoader加载,这和双亲委派机制恰好相反。

如何打破双亲委派机制? 1.自定义类加载器:继承ClassLoader,不想打破,只需要重写findClass,想打破,重写整个loadClass方法,设定自己的类加载逻辑。 

在这里插入图片描述

 2.使用线程上下文类加载器

 

java

代码解读

复制代码

public class Main { public static void main(String[] args) { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); } }

hashmap

顺序 hashmap无序,基于哈希表 linkedhashmap,插入顺序,基于链表加哈希表 treepmap,自然排序或者比较器,基于红黑树

hashmap 1.7 数组+链表 1.8 数组+链表+红黑树 ConcurrentHashMap简介 hashmap线程不安全。 hashtable线程安全,方法直接加synchronize锁,性能低下。 concurrentHashMap 对数组增加了voliate关键字 1.7 segment+hashentry 分段锁 1.8 cas+synchronized

getSize 1.7获取三次,两次一致返回,三次拿不到加锁进行计算 1.8 获取basecount put方法 计算哈希值; 当前map是否为空,为空先初始化; 判断哈希值所在位置是否有值,没值直接cas替换 有值判断key是否相等,相等不变 不相等判断是红黑树还是链表,进行循环,key相同替换,未找到相同的进行增加

扩容过程

1.触发扩容条件:负载因子默认0.75,当size超过的时候触发扩容 2.扩容过程: 2.1. 创建新数组,一般是容量的2倍 2.2. 更新阈值 2.3. 迁移旧数据: 遍历旧数组中的每一个桶 对于非空桶,将元素迁移到新数组中 在java8中,如果一个桶中的元素超过8,并且数组的容量大于64,链表转红黑树,否则继续使用链表 2.4.重新哈希 重新计算每个元素的哈希位置,使用新的数组长度来确定其在新数组中的位置。新的哈希位置通常通过 hash & (newCapacity - 1) 计算得到。

hashMap 存储大量数据

参考准备用HashMap存1W条数据,构造时传10000还会触发扩容吗?存1000呢?

不指定容量,会随着数据的增加不断扩容,影响性能。

在指定调用容量的构造方法时,会重新调用另一个构造方法,传入默认的负载因子0.75

 

java

代码解读

复制代码

public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }

构造方法中初始化了两个成员变量。threadHold 扩容阈值和 loadFactor 负载因子。 tableSizeFor就是找到大于入参的2的整数次方,如传入10,会得到16. 设置容量为2的整数次方是为了减少哈希冲突。

推荐在集合初始化的过程中指定集合初始化大小为 ((需要存储的元素个数)/0.75)+1

为什么HashMap中的键往往都使用String?

参考 为什么HashMap中的键往往都使用String?

1.String重写了hashCode,两个不同引用的String类型,只要值相等,hashcode就相等,而非地址相等。(设计 hashCode() 时最重要的因素就是对同一个对象调用 hashCode() 都应该产生相同的值。) 2.String不可变,每当创建一个字符串对象,他的hashcode就被缓存下来,所以存储hashMap不用重新计算,相比于其他对象更快。

hashmap红黑树转链表

resize方法:红黑树节点元素小于等于6,untreeify转化为链表 removenode方法:判断根节点和子节点是否为空来判断是否解除红黑树

为什么使用红黑树? 链表插入查询时间复杂度为O n,红黑树为O log n

hashMap和hashtable的区别

参考HashMap和Hashtable的区别

  1. hashMap线程不安全 hashtable线程安全,用synchronized关键字实现
  2. hashMap允许null作为键或者Value,HashTable会抛出异常
  3. hashtbale使用的是key的hashcode,hashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
  4. HsahMap在数组+链表的结构中引入了红黑树,Hashtable没有 5.HashMap初始容量为16,Hashtable初始容量为11
  5. HsahMap扩容是当前容量翻倍,Hashtable是当前容量翻倍+1
  6. HsahMap只支持Iterator遍历,Hashtable支持Iterator和Enumeration
  7. linkedhashmap 保留插入顺序,允许键和值为null,treemap 基于键的compareTo方法排序,不允许null键,允许null值

使用场景:

  • 非并发场景(单线程)使用HashMap,并发场景(多线程)可以使用Hashtable,但是推荐使用ConcurrentHashMap(锁粒度更低、效率更高)。
  • 另外使用在使用HashMap时要注意null值的判断, Hashtable也要注意防止put null key和 null value。

ThreadLocal

可以理解为线程本地变量,每个线程中都创建一个副本,在线程之间访问内部副本变量即可,做到了线程隔离,相比于synchronized的做法是用空间换时间。 

在这里插入图片描述

在使用完成之后需要remove掉,避免内存泄漏。ThreadLocal变量的key为弱引用,使用完成后TheadLocal没有使用的强引用后会释放,但是value是强引用,只要线程存活,一直存在强引用,需要通过remove删除Entry.线程池尤为严重。

单例模式

 

java

代码解读

复制代码

public class SingleTonObj { private static volatile SingleTonObj singleTonObj; private SingleTonObj() { } public static SingleTonObj getObj() { if (singleTonObj == null) { synchronized (SingleTonObj.class) { if (singleTonObj == null) { singleTonObj = new SingleTonObj(); } } } return singleTonObj; } }

  • volatile 其中volatile关键字是为了防止指令重排 jvm创建对象分三步:1.分配空间,2.实例化对象,3.将对象指向空间 如果不使用volatile,jvm会优化,将3移动到2前面,这样a线程,走到了3,还没2。b线程第一个判null已经不成立,返回了没有实例化完成的对象
  • 第二个判空的原因是 a,b都经过了第一次判空,但是a先拿到了class的锁,进行了实例化,然后进行释放锁,b拿到锁之后需要在进行判空防止重复实例化破坏单例
  • Happens-Beforene内存模型和程序模型顺序 程序A在B前,线程中A就会在B前执行 Happens-Beforene不会破坏代码中的先后顺序,但是在不同代码或者不同线程中的顺序无关

常量池

深度剖析Java常量池

通过javap命令生成更可读的JVM字节码指令文件:javap -v Math.class

Class常量池可以理解为Class文件中的资源仓库。Class文件中除了包含版本,字段,方法,接口,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用。 int a=1 1为字面量,a为符号引用 字面量是指由字母,数字构成的字符串和数值常量,字面量只可以右值出现。 符号引用是编译原理中的概念,是相对于直接引用来说的,主要包括了以下三大类:

  • 类和接口的全限定类名
  • 字段的名称和描述符
  • 方法的名称和描述符 运行时常量池:只有运行时被加载到内存中,这些符号才有对应的内存地址,那么这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用会转变为加载到内存区域的代码的直接引用,也就是动态链接 字符串常量池: jdk1.6以及之前,运行时常量池在永久代,运行时常量池包含字符串常量池。 jdk1.7: 有永久代,但是逐渐去永久代,字符串常量池从永久代的运行时常量池分配到堆中。 jdk1.8:无永久代,运行时常量池在元空间,字符串常量池依然在堆中。

intern 1.6 在常量池中寻找equal()相等的字符串,存在则返回常量池中的引用。不存在就在永久代的常量池中新建一个实例,放入常量池中并返回。 

在这里插入图片描述

1.7:不存在永久代,常量池存在返回,不存在可以直接指向堆上的实例。 

在这里插入图片描述

 

java

代码解读

复制代码

> String s0="zhigan"; String s1="zhigan"; String s2="zhi" + "gan"; System.out.println( s0==s1 ); //true System.out.println( s0==s2 );//true

字面量声明的字符串常量在编译期就能确定,会存储在常量池中,地址相同。

 

java

代码解读

复制代码

String s0="zhigan"; String s1=new String("zhigan"); String s2=“zhi”+new String("gan"); System . out . println ( s0 == s1 ); // false System . out . println ( s0 == s2 ); // false System . out . println ( s1 == s2 ); // false

new String() 的字符串不是常量,不能在编译期确定,不放入常量池,他们有自己的地址空间。

 

java

代码解读

复制代码

String a="a3.4"; String b="a"+3.4; System . out . println ( a == b ); // true

jvm对于加号连接,在编译器就会进行优化,将常量字符串连接。

 

java

代码解读

复制代码

String a="ab"; String bb="b"; String b="a"+bb; System . out . println ( a == b ); // false

在+中带有引用,JVM无法优化,因为引用无法在编译期确认,只能在程序运行是动态分配,并将连接后的新地址赋值给B,因此为false(如果bb是一个方法的返回结果,同样的原因)如果bb用final修饰,那么他在编译期会被解析为常量,比较的结果为true。

 

java

代码解读

复制代码

String s = "a" + "b" + "c" ; // 就等价于 String s = "abc"; String a = "a" ; String b = "b" ; String c = "c" ; String s1 = a + b + c ;

s1这个就不一样,可以通过观察器JVM指令码发现s1的"+"操作会变成如下: StringBuilder temp=new StringBuilder(); temp.append(a).append(b).append( c ); String s=temp.toString();

Java八大基本对象的包装类型除了两个浮点型,其他都有常量池。另外Byte,Short,Int,Long,Character这五种整形的包装类也只是对应值-128到127才可以使用对象池。

if,else嵌套优化

参考Java—优化 if-else 代码的 8 种方案 1.提前return,去除不必要的else

 

java

代码解读

复制代码

if(xx){ } else{ return } -------- if(!xx){ return }

2.使用三元表达式 3.使用枚举类

 

java

代码解读

复制代码

String OrderStatusDes; if(orderStatus==0){ OrderStatusDes="订单未支付"; }else if(OrderStatus==1){ OrderStatusDes="订单已支付"; }else if(OrderStatus==2){ OrderStatusDes="已发货"; } --------------------- String OrderStatusDes = OrderStatusEnum.0f(orderStatus).getDesc();

4.合并条件表达式 结果相同,合并表达式 5.使用Optional优化if,else

 

java

代码解读

复制代码

String str = "jay@huaxiao"; if(str != null) { System.out.println(str); } else{ System.out.println("Null"); } ----------------------------- Optional<String> strOptional = Optional.of("jay@huaxiao"); strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));

6.表驱动法 又称之为表驱动、表驱动方法。表驱动方法是一种使你可以在表中查找信息,而不必用很多的逻辑语句(if或case)来把它们找出来的方法。以下的demo,把map抽象成表,在map中查找信息,而省去不必要的逻辑语句。

 

java

代码解读

复制代码

if(param.equals(value1)) { doAction1(someParams); } else if(param.equals(value2)) { doAction2(someParams); } elseif(param.equals(value3)) { doAction3(someParams); } --------------- // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型 Map<?, Function<?> action> actionMappings = newHashMap<>(); // 初始化 actionMappings.put(value1, (someParams) -> { doAction1(someParams)}); actionMappings.put(value2, (someParams) -> { doAction2(someParams)}); actionMappings.put(value3, (someParams) -> { doAction3(someParams)}); // 省略多余逻辑语句 actionMappings.get(param).apply(someParams);

7.使用策略模式 例如支付场景下,支持多种支付方式

 

java

代码解读

复制代码

public class PaymentService { CreditService creditService; WeChatService weChatService; AlipayService alipayService; public void payment(PaymentType paymentType, BigDecimal amount) { if (PaymentType.Credit == paymentType) { creditService.payment(); } else if (PaymentType.WECHAT == paymentType) { weChatService.payment(); } else if (PaymentType.ALIPAY == paymentType) { alipayService.payment(); } else { throw new NotSupportPaymentException("paymentType not support"); } } } enum PaymentType { Credit, WECHAT, ALIPAY; } 作者:小黑说Java 链接:https://juejin.cn/post/7030976391596212255 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这种不满足开闭原则(对修改关闭,对扩展开放),修改后需要对其他支付方式进行测试。

策略设计模式是一种行为设计模式。当在处理一个业务时,有多种处理方式,并且需要再运行时决定使哪一种具体实现时,就会使用策略模式。

抽象支付方式为一个策略接口

 

java

代码解读

复制代码

public interface PaymentStrategy { public void payment(BigDecimal amount); }

针对具体的支付方式做实现

 

java

代码解读

复制代码

public class CreditPaymentStrategy implements PaymentStrategy{ @Override public void payment(BigDecimal amount) { System.out.println("使用银行卡支付" + amount); // 去调用网联接口 } } public class WechatPaymentStrategy implements PaymentStrategy{ @Override public void payment(BigDecimal amount) { System.out.println("使用微信支付" + amount); // 调用微信支付API } }

重新实现支付服务paymentservice

 

java

代码解读

复制代码

public class PaymentService { /** * 将strategy作为参数传递给支付服务 */ public void payment(PaymentStrategy strategy, BigDecimal amount) { strategy.payment(amount); } }

策略模式优化后

 

java

代码解读

复制代码

public class StrategyTest { public static void main(String[] args) { PaymentService paymentService = new PaymentService(); // 使用微信支付 paymentService.payment(new WechatPaymentStrategy(), new BigDecimal("100")); //使用支付宝支付 paymentService.payment(new AlipayPaymentStrategy(), new BigDecimal("100")); } }

在使用了策略模式之后,在我们的支付服务PaymentService中便不需要写复杂的if...else,如果需要新增加一种支付方式,只需要新增一个新的支付策略实现,这样就满足了开闭原则,并且对其他支付方式的业务逻辑也不会造成影响,扩展性很好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值