做网站 商标分类,腾讯企点怎么注册,手机如何登入网站服务器,做网站的环境配置jdk9中对字符串进行了一个什么优化#xff1f;
jdk9之前 字符串的拼接通常都是使用进行拼接 但是的实现我们是基于stringbuilder进行的
这个过程通常比较低效 包含了创建stringbuilder对象 通过append方法去将stringbuilder对象进行拼接 最后使用tostring方法去转换成最终的…jdk9中对字符串进行了一个什么优化
jdk9之前 字符串的拼接通常都是使用进行拼接 但是的实现我们是基于stringbuilder进行的
这个过程通常比较低效 包含了创建stringbuilder对象 通过append方法去将stringbuilder对象进行拼接 最后使用tostring方法去转换成最终的字符串
在jdk9之后引入了stringconcatfactory 这个被推出的主要目标就是提供一个灵活且高效的方式去拼接字符串 代替之前的stringbuilder或者stringbuffer的静态编译方法
stringconcatfactory是基于invokedynamic指令来实现的
他是java7引入发一种动态类型的指令 允许jvm在运行的时候动态解析和调用方法
也就是说可以使用invokedynamic的特性 将字符串的拼接从编译时候推迟到运行时
这就使得 jvm可以在运行的时候根据实际的场景 去选择最优的拼接策略 可能是使用stringbuilder 也可能是stringbuffer或者是其他更高效的方法
stream的并行流一定会比串行流更快吗
不一定
stream的底层使用的是forkjoin进行并发处理 但是这并不代表的并行流会快 因为他受到一些因素的影响
线程管理的开销并行流使用了多线程 使用了多线程增加了线程管理和任务分配的开销
任务分割并行流的性能依赖于任务能有效的分割和分配 如果任务分配不均衡一些线程可能会空闲或者等待 影响性能
线程争用stream使用forkjoinpool 如果系统中有其他并行的任务 这些任务会争用线程资源 可能会导致性能下降
数据依赖性并行流适用于没有数据依赖性的操作 如果操作之间有依赖关系 并行流无法有效的提高性能 还有可能会导致错误
环境配置机器的硬件配置egcpu核数和当前系统的负载也会影响并行流的性能 如果cpu核心数较少或者是负载较高 并行流的性能可能不如串行流 在单核的cpu的情况下 并行流的迭效率低于穿行流
在多核cpu的情况下 并行流的迭代效率高于串行流 但是如果元素数量比较少的情况下 常规迭代的性能反而较好
怎么去修改一个类中的private修饰的string参数的值
面试官问这个问题要么是在问反射 要么就是在挖坑
string对象一旦被创建就确定了 他的内容不会被修改 看似修改了string的值实际上就是创建了一个新的string对象
但是如果创建一个新的string对象也算是修改了的话 他有几种方式
一使用setter方法 public class MyClass {private String myString;public void setMyString(String value) {this.myString value;}
}// 使用
MyClass obj new MyClass();
obj.setMyString(new value);
二使用反射
如果没有setter方法可以使用 可以使用反射 但是这种方法破坏了封装性 增加了代码的复杂性和出错的可能性 并且性能并不好 import java.lang.reflect.Field;public class MyClass {private String myString initial value;
}// 使用反射修改
MyClass obj new MyClass();
try {Field field MyClass.class.getDeclaredField(myString);field.setAccessible(true); // 使得private字段可访问field.set(obj, new value);
} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();
} bigdecimal和long表示金额哪个更合适 怎么去选择
大家都知道float和double不能用来表示金额 会存在精度的丢失
要表示金额的话可以使用long或者是bigdecimal
1.单位为分 数据库中使用bigint代码中用long
2.单位为元 数据库中用decimal 代码中用bigdecimal
首先是bigdecimal是java中用于精确计算的类 特别适用于需要高精度数值计算的场景 如 金融 计量和工程领域 其特点
精确度高bigdecimal可以表示非常大或非常精确的小数 而不会出现浮点数那样的舍入误差
灵活的数学计算他提供了各种方法进行精确的算数运算 包括加减乘除和四舍五入
控制舍入行为 在进行数学计算的时候 你可以指定舍入模式 这对于金融计算非常重要
所以BigDecimal的适用场景是需要高精度计算的金融应用如货币计算、利率计算等。比如我们的结算系统、支付系统、账单系统等都是用BigDecimal的。 其次再说Longlong 是 Java 的一种基本数据类型用于表示没有小数部分的整数。其特点如下 ●性能高作为基本数据类型long 在处理速度上比 BigDecimal 快很多。 ●容量限制long 可以表示的最大值为 (2^{63}-1)最小值为 (-2^{63})。这在大多数应用程序中已经足够但在表示非常大的数或需要小数的计算中则不适用。 ●不适合精确的小数运算long 无法处理小数如果需要代表金额中的小数部分如厘则需要自行管理这一部分。 所以Long的适用场景是适合于不涉及小数计算的大整数运算如某些计数应用或者金额以整数形式表示。比如我们的额度系统、积分系统等。 我一笔账单有两笔订单金额都是1元存储的时候按照分存储即100分然后我的服务费费率是0.004。 如果是以分为单位long存储和表示的话那么两笔订单分开算费率的话100*0.004 0.4 四舍五入 0 两笔加在一起收入的费率就是0分。 但是如说是以元为单位bigdecimal存储和表示的话那么两笔订单分开算费率的话1*0.004 0.004 两笔加在一起0.008当我要结算的时候再做四舍五入就是0.01元即1分钱。 所以因为long在计算和存储的过程中都会丢失掉小数部分那就会导致每一次都被迫需要四舍五入。而decimal完全可以保留过程中的数据再最终需要的时候做一次整体的四舍五入这样结果就会更加精确
所以如果你的应用需要处理小数点后的精确计算如金融计算中常见的多位小数则应选择 BigDecimal。 如果你的应用对性能要求极高并且没有乘除类运算不需要很小的精度时那么使用 long 可能更合适。 总结来说对于绝大多数涉及货币计算的应用推荐使用 BigDecimal因为它提供了必要的精度和灵活性尽管牺牲了一些性能。如果确定不需要处理小数并且对执行速度有极端要求使用 long 可能更适合。 有了equals为啥需要hashCode方法
在java中equals和hashcode通常是成对出现的 他们在 使用hash机制的数据结构中非常的重要 例如
hashmap hashset hashtable
equals是用来判断两个对象是否相等
hashcode是生成对象的哈希码 返回值是一个整数 用于确定对象在哈希表中的位置
为什么需要hashcode是因为在这种数据结构中 想要把一个对象放进去 就需要定位到他应该放在哪个桶里面 而这个桶的位置 就需要通过一个整数来获取 然后再对桶的长度进行取模
那么如何快速获取这个对象有关的整数 就是需要hashcode方法 hashcode的结果和对象的内容息息相关 那么也就意味着 如果两个对象通过equals方法比较是相等的那么他们的hashcode也是相等的
那么在一个对象中 定义了equals方法 还需要同时定义他的hashcode方法这样就可以在hashmap和hashtable中存放的时候 才能快速定位到位置
所以 基于两方面进行考虑 一方面是效率 hashcode方法提供了一种快速计算对象hash值的方式 这些哈希值用于确定对象在哈希表中的位置 这意味着可以快速定位到对象应该存储在哪一个位置或者从哪个位置检索 限制提高了查找的效率
另一方面 可以和equals做协同来保证数据的一致性和准确性 根据java规范 如果两个对象通过equals方法比较时是相等的 那么这两个对象的hashcode方法必须返回相同的整数值 如果违反了这一规则 将导致哈希表等数据结构无法正确的处理对象 从而导致数据丢失和检索失败
java中的static都能用来修饰什么
static是一个非常重要的修饰符 他通常被用来修饰变量 代码块 方法 以及类
1.静态变量
定义静态变量属于类本身而不是类的任何特定实例new出来的对象
特点
所有 的实例共享同一静态变量
在类加载到内存的时候就被初始化了 而不是在创建对象的时候
常用于管理类的全局状态或作为常量仓库
2.静态方法
定义静态方法同样属于类 而非类的实例
特点
可以在不创建类的实例的时候就调用
不能访问类的实例变量或实例方法 他们只能访问其他 的 静态成员
常用于工具类的方法
3.静态代码块
定义用于初始化类的静态变量
特点
当类被java虚拟机加载并初始化时执行
通常用于执行静态变量的复杂初始化
4.静态内部类
定义在一个类的内部定义的静态类
特点
可以不依赖于外部类的实例而独立存在
可以访问外部类的所有静态成员 但不能直接访问外部类的实例成员
常用于当内部类的行为不依赖于外部类的实例时 使用static修饰符的好处就是可以减少内存的使用 共享静态变量 而不是给每一个变量创建一个副本提供一个全局发访问点 如静态方法和变量以及无需实例化类即可使用其中的变量和方法
为什么Java中的main方法必须是public static void的
常见的形式
public static void main(String[] args) {}
对于main方法来说我们需要jvm直接去调用它 所以需要设置为public
static是静态修饰符 被他修饰的方法我们称之为静态方法 静态方法有一个特点就是独立于该类的任何对象 它不依赖于类的特定的实例 被类的所有实例共享 只要这个类被加载 java虚拟机就能根据类名在运行时数据区的方法区内找到他们
对于main方法来说 他的调用过程是经历了类加载 连接和初始化的 但是并没有被实例化过 这个时候如果想要调用一个类中的方法这个方法必须是静态方法 否则不能调用
像一般c或c语言的话是以int为main函数返回值的编程语言 这个返回值是在程序退出时的exit code 一般被命令解释器或者其他的外部程序调用已确定流程是否完成 一般正常情况下用0返回 为0为正常退出
但是在java中这个退出的过程是由jvm进行控制的一般在发生以下两种情况的时候程序会中止所有的i行为并退出
1.除了守护线程外 所有线程都全部中止
2.某个线程调用了runtime类或者system类的exit方法 并且安全管理器并不禁止exit操作
当第二种情况发生的时候jvm不会管main方法有没有执行完的 他会终止所有的行为并且退出 这时候main方法有没有返回值没有任何意义 所以是void
string[ ]字符串数组是由于java是由命令行来接收一个参数进行传入的 因为命令行参数最终都是以字符串的形式传递的 并且有的时候命令行的参数不止一个 所有就有可能传递多个参数
为什么不建议使用异常控制业务流程
在effective java中作者提出过 不建议使用异常来控制业务流程
eg解决幂等问题的时候 先插入 然后再捕获唯一性约束冲突异常 在反查 返回幂等
这样做非常不建议
1.存在性能问题 在java中异常的生成和处理是非常昂贵的 因为他设计到填充栈跟踪信息频繁的抛出和捕获异常会导致性能下降
2.异常的职责就不是干这个java中的异常被定义为处理一些非正常情况的 它的使用应该是比较谨慎的 异常应该用于处理非预期的错误情况 而不是利用它来控制正常的业务流程 这样会使代码的意图变得不清晰 增加了理解和维护代码的难度
3.异常的捕获会影响事务的回滚本身的这个方法如果还有其他的数据库操作逻辑 或者方法外嵌套了一层方法 那么就可能由于异常导致不能回滚
4.过度依赖数据库异常 这里过度依赖了duplicatekeyexception 万一哪天这个异常发生了变化 比如版本升级看 或者底层数据库变了 不再抛出这个异常 了那么这个代码就会失去作用 可能导致意想不到的问题
良好的api应该使清晰的表达意图 如果api使用异常来表示常规的业务流程控制可能会误导api的使用者 使他们误解api的真正的用途
前面提到的幂等问题要解决幂等问题应该是先查再改。如果为了防止并发应该是一锁、二判、三更新。
为什么这段代码在JDK不同版本中结果不同
一些在线的Java代码执行工具测试如在线运行Java 。
以下代码中在JDK 1.8中JDK 11及以上版本中执行后结果不是一样的。
String s3 new String(1) new String(1);
s3.intern();
String s4 11;
System.out.println(s3 s4); 你会发现在JDK 1.8中以上代码得到的结果是true而JDK 11及以上的版本中结果却是false。 那么再稍作修改呢在目前的所有JDK版本中执行以下代码 String s3 new String(3) new String(3);// ①
s3.intern();// ②
String s4 33;
System.out.println(s3 s4);// ③
得到的结果也是true你知道为什么嘛 看这篇文章之前先确保自己了解了intern的原理
出现上述现象肯定是因为在JDK 11 及以上的版本中11这个字面量已经被提前存入字符串池了。那什么时候存进去的呢这个问题全网应该没人提过 经过我七七四十九天的研究终于发现了端倪就在以下代码中Source.java
public enum Source {/** 1.0 had no inner classes, and so could not pass the JCK. */// public static final Source JDK1_0 new Source(1.0);/** 1.1 did not have strictfp, and so could not pass the JCK. */// public static final Source JDK1_1 new Source(1.1);/** 1.2 introduced strictfp. */JDK1_2(1.2),/** 1.3 is the same language as 1.2. */JDK1_3(1.3),/** 1.4 introduced assert. */JDK1_4(1.4),/** 1.5 introduced generics, attributes, foreach, boxing, static import,* covariant return, enums, varargs, et al. */JDK5(5),/** 1.6 reports encoding problems as errors instead of warnings. */JDK6(6),/** 1.7 introduced try-with-resources, multi-catch, string switch, etc. */JDK7(7),/** 1.8 lambda expressions and default methods. */JDK8(8),/** 1.9 modularity. */JDK9(9),/** 1.10 local-variable type inference (var). */JDK10(10),/** 1.11 covers the to be determined language features that will be added in JDK 11. */JDK11(11);
}
看到了么xdm在JDK 11 的源码中定义了11这个字面量那么他会提前进入到字符串池中那么后续的intern的过程就会直接从字符串池中获取到这个字符串引用。 按照这个思路大家可以在JDK 11中执行以下代码
String s3 new String(1) new String(1);
s3.intern();
String s4 11;
System.out.println(s3 s4);String s3 new String(1) new String(2);
s3.intern();
String s4 12;
System.out.println(s3 s4);
得到的结果就是false和true。 或者我是在JDK 21中分别执行了以下代码
String s3 new String(2) new String(1);
s3.intern();
String s4 21;
System.out.println(s3 s4);String s3 new String(2) new String(2);
s3.intern();
String s4 22;
System.out.println(s3 s4);
得到的结果就也是false和true。
为什么建议自定义一个无参的构造函数
java中的构造函数分为有参和无参
两种构造函数都是为了对象的初始化的 无参的构造函数是为了给对象一个默认值 有参构造函数就是根据我们的参数进行初始化
如果没有显示定义任何构造函数 会自动添加一个无参的构造函数 但是如果定义过构造函数 那么就不会默认添加了
定义一个无参的构造函数 通常是认为在java编程中的好习惯 如果我们没有定义jdk会帮我们生成一个 但是如果我们定义了一个有参的构造函数 那么就不会帮我们生成无参构造函数了 没有无参构造函数会带来一些列问题
1.反射及序列化要求
在使用java反射或者序列化的时候经常调用类的无参构造函数进行对象的创建
2.兼容性和可推广性
许多java框架和库 如spring hibernate jackson 在进行对象的创建和初始化时 依赖于类的无参构造器如果没有定义无参构造器这些框架可能无法正常工作。 3. JavaBean规范 根据JavaBean规范一个标准的JavaBean必须拥有一个公共的无参构造器。这使得JavaBean可以被实例化并且其属性可以通过反射机制被外部访问和修改。 4. 子类构造器的默认行为 在Java中子类构造器默认会调用父类的无参构造器。如果父类没有定义无参构造器而子类又没有显式调用父类的其他构造器这将导致编译错误。
final、finally、finalize有什么区别
final、finally和finalize是Java中的三个不同的概念。 ●final用于声明变量、方法或类使之不可变、不可重写或不可继承。 ●finally是异常处理的一部分用于确保代码块通常用于资源清理总是执行。 ●finalize是Object类的一个方法用于在对象被垃圾回收前执行清理操作但通常不推荐使用。
finally是一个用于异常处理它和try、catch块一起使用。无论是否捕获或处理异常finally块中的代码总是执行程序正常执行的情况。通常用于关闭资源如输入/输出流、数据库连接等。
finalize是Object类的一个方法用于垃圾收集过程中的资源回收。在对象被垃圾收集器回收之前finalize方法会被调用用于执行清理操作例如释放资源。但是不推荐依赖finalize方法进行资源清理因为它的调用时机不确定且不可靠。
try中return Acatch中return Bfinally中return C最终返回值是什么
最终的返回值将会是C 因为finally块总是在try和catch块之后执行无论是否有异常发生。如果finally块中有一个return语句它将覆盖try块和catch块中的任何return语句。
//无异常情况
public static String getValue(){try{return A;}catch (Exception e){return B;}finally {return C;}
}//有异常情况
public static String getValue(){try{System.out.println(1/0);return A;}catch (Exception e){return B;}finally {return C;}
} 所以在这种情况下无论try和catch块的执行情况如何finally块中的return C;总是最后执行的语句并且其返回值将是整个代码块的返回值。 这个问题还有一个兄弟问题那就是如下代码得到的结果是什么 public static void getValue() {int i 0;try {i 1;} catch (Exception e) {i 2;} finally {i 3;}System.out.println(i);
}
原理和上面的是一样的最终输出内容为3。
finally和return的关系 很多时候我们的一个方法会通过return返回一个值那么如以下代码
public static int getValue() {int i 1;try {i;return i;} catch (Exception e) {i 66;} finally {i 100;}return i;
}
这个代码得到的结果是2try-catch-finally的执行顺序是try-finally或者try-catch-finally然后在执行每一个代码块的过程中如果遇到return那么就会把当前的结果暂存然后再执行后面的代码块然后再把之前暂存的结果返回回去。 所以以上代码会先把i即2的结果暂存然后执行i100接着再把2返回。 但是在执行后续的代码块过程中如果遇到了新的return那么之前的暂存结果就会被覆盖。如
public static int getValue() {int i 1;try {i;return i;} catch (Exception e) {i 66;} finally {i 100;return i;}
}
以上代码方法得到的结果是100是因为在finally中遇到了一个新的return就会把之前的结果给覆盖掉。 如果代码出现异常也同理
public static int getValue() {int i 1;try {i;System.out.println(1 / 0);return i;} catch (Exception e) {i 66;return i;} finally {i 100;return i;}
} 在try中出现一个异常之后会执行catch在执行finally最终得到100。如果没有finally
public static int getValue() {int i 1;try {i;System.out.println(1 / 0);return i;} catch (Exception e) {i 66;return i;} 那么得到的结果将是66。 所以如果finally块中有return语句则其返回值将是整个try-catch-finally结构的返回值。如果finally块中没有return语句则try或catch块中的return语句取决于哪个执行了将确定最终的返回值。 为什么建议多用组合少用继承
java的复用有继承组合代理三种具体的表现形式
继承相当于一个白盒复用 由于父类的内部细节都对子类是可见的 如果基类的代码发生改变 那派生类的实现也随之改变 这样就导致了子类行为的不可预知性
继承在写代码的时候就要指名具体继承哪个类所以在编译期就确定了关系。从基类继承来的实现是无法在运行期动态改变的因此降低了应用的灵活性。 组合是通过对现有的对象进行拼装组合产生新的、更复杂的功能。因为在对象之间各自的内部细节是不可见的所以我们也说这种方式的代码复用是黑盒式代码复用。因为组合中一般都定义一个类型所以在编译期根本不知道具体会调用哪个实现类的方法 组合在写代码的时候可以采用面向接口编程。所以类的组合关系一般在运行期确定。 相信很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出组合确实比继承更加灵活也更有助于代码维护。 所以建议在同样可行的情况下优先使用组合而不是继承。因为组合更安全更简单更灵活更高效。 注意并不是说继承就一点用都没有了前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的或者是更适合使用继承。 继承要慎用其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是问一问自己是否需要从新类向基类进行向上转型。如果是必须的则继承是必要的。反之则应该好好考虑是否需要继承。《Java编程思想》 只有当子类真正是超类的子类型时才适合用继承。换句话说对于两个类A和B只有当两者之间确实存在is-a关系的时候类B才应该继承类A。《Effective Java》 Java中Timer实现定时调度的原理是什么
java中的timer是一个定时的调度器 用于在指定的时间点执行任务 public class Timer {/*** The timer task queue. This data structure is shared with the timer* thread. The timer produces tasks, via its various schedule calls,* and the timer thread consumes, executing timer tasks as appropriate,* and removing them from the queue when theyre obsolete.*/private final TaskQueue queue new TaskQueue();/*** The timer thread.*/private final TimerThread thread new TimerThread(queue);
}
taskqueue 一个任务队列 用于存储已计划的定时任务 任务队列按照任务的执行时间进行排序 确保最早执行的任务排在队列前面 在队列中的任务可能是一次性的也可能是周期性的
timerthread timer内部的后台线程 他负责扫描task queue中的任务 检查任务的执行时间 在执行时间到达执行任务的run方法 timerthread是一个守护线程 当所有的非守护线程全部结束的时候 他也随之中止
任务的定时调度的核心代码就在TimerThread中
class TimerThread extends Thread {boolean newTasksMayBeScheduled true;/*** 存储 TimerTask 的队列*/private TaskQueue queue;TimerThread(TaskQueue queue) {this.queue queue;}public void run() {try {mainLoop();} finally {synchronized (queue) {newTasksMayBeScheduled false;queue.clear(); }}}/*** 主要的计时器循环。 */private void mainLoop() {while (true) {try {TimerTask task;boolean taskFired;synchronized (queue) {// 等待队列变为非空while (queue.isEmpty() newTasksMayBeScheduled)queue.wait();if (queue.isEmpty())break; // 队列为空将永远保持为空线程终止// 队列非空查看第一个事件并执行相应操作long currentTime, executionTime;task queue.getMin();synchronized (task.lock) {if (task.state TimerTask.CANCELLED) {queue.removeMin();continue; // 无需执行任何操作再次轮询队列}currentTime System.currentTimeMillis();executionTime task.nextExecutionTime;if (taskFired (executionTime currentTime)) {if (task.period 0) { // 非重复移除queue.removeMin();task.state TimerTask.EXECUTED;} else { // 重复任务重新安排queue.rescheduleMin(task.period 0 ? currentTime - task.period: executionTime task.period);}}}if (!taskFired) // 任务尚未触发等待queue.wait(executionTime - currentTime);}if (taskFired) // 任务触发运行它不持有锁task.run();} catch (InterruptedException e) {}}}
}可以看到TimerThread的实际是在运行mainLoop方法这个方法一进来就是一个while(true)的循环他在循环中不断地从TaskQueue中取出第一个任务然后判断他是否到达执行时间了如果到了就触发任务执行。否则就继续等一会再次执行。 不断地重复这个动作从队列中取出第一个任务进行判断执行。。。 这样只要有新的任务加入队列就在队列中按照时间排序然后唤醒timerThread重新检查队列进行执行就可以了。代码如下 private void sched(TimerTask task, long time, long period) {if (time 0)throw new IllegalArgumentException(Illegal execution time.);// Constrain value of period sufficiently to prevent numeric// overflow while still being effectively infinitely large.if (Math.abs(period) (Long.MAX_VALUE 1))period 1;synchronized(queue) {if (!thread.newTasksMayBeScheduled)throw new IllegalStateException(Timer already cancelled.);synchronized(task.lock) {if (task.state ! TimerTask.VIRGIN)throw new IllegalStateException(Task already scheduled or cancelled);task.nextExecutionTime time;task.period period;task.state TimerTask.SCHEDULED;}//新任务入队列queue.add(task);//唤醒任务if (queue.getMin() task)queue.notify();}} 优缺点 Timer 类用于实现定时任务最大的好处就是他的实现非常简单特别的轻量级因为它是Java内置的所以只需要简单调用就行了。 但是他并不是特别的解决定时任务的好的方案因为他存在以下问题 1、Timer内部是单线程执行任务的如果某个任务执行时间较长会影响后续任务的执行。 2、如果任务抛出未捕获异常将导致整个 Timer 线程终止影响其他任务的执行。 3、Timer 无法提供高精度的定时任务。因为系统调度和任务执行时间的不确定性可能导致任务执行的时间不准确。 4、虽然可以使用 cancel 方法取消任务但这仅仅是将任务标记为取消状态仍然会在任务队列中占用位置无法释放资源。这可能导致内存泄漏。 5、当有大量任务时Timer 的性能可能受到影响因为它在每次扫描任务队列时都要进行时间比较。 6、Timer执行任务完全基于JVM内存一旦应用重启那么队列中的任务就都没有了 什么是序列化与反序列化
在java中 我们创建的java对象都是存放在jvm的堆内存中的 当jvm处于运行状态的时候 这些对象才可能存在 一旦jvm停止运行这些对象的状态随之消失
但是在真实的应用场景中 我们要将这些对象持久化下来 就是通过序列化 将这些对象的状态保存为字节数组 并且可以在有需要的时候将他通过反序列化的方式转换为对象 对象序列化可以很容易的在jvm中的活动对象和字节数组之间进行转换 以下几个和序列化反序列化有关的知识点大家可以重点关注一下 1、在Java中只要一个类实现了java.io.Serializable接口那么它就可以被序列化。 2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化 3、虚拟机是否允许反序列化不仅取决于类路径和功能代码是否一致一个非常重要的一点是两个类的序列化 ID 是否一致就是 private static final long serialVersionUID 4、序列化并不保存静态变量。 5、要想将父类对象也序列化就需要让父类也实现Serializable 接口。 6、transient 关键字的作用是控制变量的序列化在变量声明前加上该关键字可以阻止该变量被序列化到文件中在被反序列化后transient 变量的值被设为初始值如 int 型的是 0对象型的是 null。 7、服务器端给客户端发送序列化对象数据对象中有一些数据是敏感的比如密码字符串等希望对该密码字段在序列化时进行加密而客户端如果拥有解密的密钥只有在客户端进行反序列化时才可以对密码进行读取这样可以一定程度保证序列化对象的数据安全。 未实现Serializable可以序列化吗 如果使用Java原生的序列化机制即通过 ObjectOutputStream 和 ObjectInputStream 类则对象必须实现 Serializable 接口。如果对象没有实现这个接口尝试原生序列化会抛出 NotSerializableException。 对于像Jackson、Gson这样的JSON序列化库或用于XML的库如JAXB对象不需要实现 Serializable 接口。这些库使用反射机制来访问对象的字段并将它们转换成JSON或XML格式。在这种情况下对象的序列化与 Serializable 接口无关。 String中intern的原理是什么
1
字符串常量是什么时候进入到字符串常量池的
2
String是如何实现不可变的
我们都知道string是不可变的 那他是怎么实现的
1.string类首先是被设置为final类 意味着他不能被继承 那么他里面的方法就是没办法被覆盖的
2.被final修饰字符串内容的char[]从jdk1.9开始 char[]就变成了byte[]由于该数组被声明为final 一旦数组被初始化 就不能再指向其他数组
3.string类没有提供用于修改字符串内容的公共方法 eg 没有提供用于追加 删除 或修改字符的方法 如果要对字符串进行修改就会创建一个新的string对象
如果我们想要一个可修改的字符串可以选择StringBuffer 或者 StringBuilder这两个代替String。 Arrays.sort是使用什么排序算法实现的
array。sort是java中提供的对数组进行排序的方法 根据参数类型的不同 他提供了很多重载方法
对于常见的基本数据类型 int double char 就是采用了 双轴快速排序
对于对象数组的排序 支持归并排序 和timsort
TimSort 是一种混合排序算法结合了归并排序Merge Sort和插入排序Insertion Sort的特点。 为什么JDK 9中把String的char[]改成了byte[]
在Java 9之前字符串内部是由字符数组char[] 来表示的。 由于Java内部使用UTF-16每个char占据两个字节即使某些字符可以用一个字节LATIN-1表示但是也仍然会占用两个字节。所以JDK 9就对他做了优化。 每当我们创建一个字符串时如果它的所有字符都可以用单个字节Latin-1表示那么将会在内部使用字节数组来保存一半所需的空间但是如果有一个字符需要超过8位来表示Java将继续使用UTF-16与字符数组。 ClassNotFoundException和NoClassDefFoundError的区别是什么
classnotfoundexception是一个受检异常 它通常在运行的时候在类加载阶段尝试加载类的过程中 找不到类的定义时触发 通常是由class。forname或者类加载器loadclass或者findsystemclass时在类路径中没有找到指定名称的类时 会报出该异常 表示所需的类路径不存在 通常是由于类名拼写错误或缺少依赖导致的
NoClassDefFoundError是一个错误error它表示运行时尝试加载一个类的定义时虽然找到了类文件但在加载、解析或链接类的过程中发生了问题。这通常是由于依赖问题或类定义文件.class文件损坏导致的。也就是说这个类在编译时存在运行时丢失了就会导致这个异常。
在编译后会生成A.class和B.class当我们删除A.class之后单独运行B.class的时候就会发生NoClassDefFoundErrorNoSuchMethodError NoSuchMethodError表示方法找不到他和NoClassDefFoundError类似都是编译期找得到运行期找不到了。 这种error发生在生产环境中是通常来说大概率是发生了jar包冲突。 while(true)和for(;;)哪个性能好
while(true)和for(;;)都是做无限循环的代码他俩有啥区别呢 关于这个问题网上有很多讨论说那么多没用直接反编译看看字节码有啥区别就行了。然后再通过javap对class文件进行反编译然后我们就会发现两个文件内容一模一样
可以看到都是通过goto来干的所以这两者其实是没啥区别的。用哪个都行 有人愿意用while(true)因为他更清晰的看出来这里是个无限循环。有人愿意用for(;;)因为有些IDE对于while(true)会给出警告。至于你爱用啥用啥。 char能存储中文吗
在Java中char类型是用来表示一个16位2个字节的Unicode字符它可以存储任何Unicode字符集中的字符当然也包括中文字符。
但是有人说Java中的char是没办法表示生僻字的这么说其实有点绝对了。 因为Unicode字符集包含了几乎所有的字符包括常见字符、生僻字、罕见字以及其他语言的字符。所以用char类型其实是可以存储生僻字的。 但是在处理生僻字时需要确保Java源代码文件本身以及编译器和运行时环境都支持Unicode字符集。另外如果在字符串中使用生僻字也需要注意字符编码和字符串长度的问题。 还有一点需要注意Unicode字符集的目标是覆盖世界上所有的字符。然而由于生僻字的数量庞大且不断增长Unicode字符集可能无法及时收录所有生僻字。这主要取决于Unicode标准的版本以及生僻字的使用频率和普及程度。 虽然Unicode字符集也在一直不断的迭代更新但是对于一些非常罕见的生僻字它们可能因为版本问题或者时间问题暂时不在Unicode字符集中。在这种情况下可能就会无法表示。 什么是UUID能保证唯一吗
uuid全局唯一标识符 是指在一台机器上生成的数字 他的目标是保证对在同一时空中的所有机器都是唯一的
uuid的生成是基于一定的算法 通常使用的是随机数生成器或者基于时间戳的方式 生成的uuid由32位16进制数表示 一共有128位 8444 12一共32个字符
由于uuid是由mac地址时间戳 随机数等信息生成的 因此uuid具有极高的唯一性可以说几乎不可能重复 但是在实际实现过程中 uuid有多种实现的版本 他们的唯一性指标也不同
有基于时间的uuid v1 基于随机数的uuid v4
优缺点 UUID的优点就是他的性能比较高不依赖网络本地就可以生成使用起来也比较简单。 但是他也有两个比较明显的缺点那就是长度过长和没有任何含义。长度自然不必说他有32位16进制数字。对于550e8400-e29b-41d4-a716-446655440000这个字符串来说我想任何一个程序员都看不出其表达的含义。一旦使用它作为全局唯一标识就意味着在日后的问题排查和开发调试过程中会遇到很大的困难。 各个版本实现 V1. 基于时间戳的UUID 基于时间的UUID通过计算当前时间戳、随机数和机器MAC地址得到。由于在算法中使用了MAC地址这个版本的UUID可以保证在全球范围的唯一性。 但与此同时使用MAC地址会带来安全性问题这就是这个版本UUID受到批评的地方。如果应用只是在局域网中使用也可以使用退化的算法以IP地址来代替MAC地址。 V2. DCE(Distributed Computing Environment)安全的UUID 和基于时间的UUID算法相同但会把时间戳的前4位置换为POSIX的UID或GID这个版本的UUID在实际中较少用到。 V3. 基于名称空间的UUID(MD5) 基于名称的UUID通过计算名称和名称空间的MD5散列值得到。 这个版本的UUID保证了相同名称空间中不同名称生成的UUID的唯一性不同名称空间中的UUID的唯一性相同名称空间中相同名称的UUID重复生成得到的结果是相同的。 V4. 基于随机数的UUID 根据随机数或者伪随机数生成UUID。该版本 UUID 采用随机数生成器生成它可以保证生成的 UUID 具有极佳的唯一性。但是因为基于随机数的所以并不适合数据量特别大的场景。 V5. 基于名称空间的UUID(SHA1) 和版本3的UUID算法类似只是散列值计算使用SHA1(Secure Hash Algorithm 1)算法。 各个版本总结 可以简单总结一下Version 1和Version 2 这两个版本的UUID主要基于时间和MAC地址所以比较适合应用于分布式计算环境下具有高度唯一性。 Version 3和 Version 5 这两种UUID都是基于名称空间的所以在一定范围内是唯一的而且如果有需要生成重复UUID的场景的话这两种是可以实现的。 Version 4 这种是最简单的只是基于随机数生成的但是也是最不靠谱的。适合数据量不是特别大的场景下
JDK新版本中都有哪些新特性
jdk8推出了lambda表达式 stream optional 新的日期api
jdk9推出了模块化
jdk10推出了本地变量类型判断
本地变量类型判断
在jdk10之前的版本 我们想要定义一个局部的变量 我们需要在赋值的左边提供一个显式类型 并在赋值的右边提供一个实现的类型
MyObject value new MyObject();
在Java 10中提供了本地变量类型推断的功能可以通过var声明变量
var value new MyObject();
本地变量的类型引入var 关键字不需要显示的规范变量的类型
其实所谓的本地变量类型推断 也就是java10提供给开发者的语法糖
虽然我们在代码中进行了var定义 但是对于虚拟机来说他是不认识这var的 在java文件编译成class文件的过程中 会进行一个解糖 使用变成真正的类型
jdk12增加了switch表示式
在JDK 12中引入了Switch表达式作为预览特性。并在Java 13中修改了这个特性引入了yield语句用于返回值。 而在之后的Java 14中这一功能正式作为标准功能提供出来。 在以前我们想要在switch中返回内容还是比较麻烦的一般语法如下
int i;
switch (x) {case 1:i1;break;case 2:i2;break;default:i x.length();break;
} 在JDK13中使用以下语法
int i switch (x) {case 1 - 1;case 2 - 2;default - {int len args[1].length();yield len;}
};
或者
int i switch (x) {case 1: yield 1;case 2: yield 2;default: {int len args[1].length();yield len;}
}; 在这之后switch中就多了一个关键字用于跳出switch块了那就是yield他用于返回一个值。 和return的区别在于return会直接跳出当前循环或者方法而yield只会跳出当前switch块。
jdk13增加了text block
jdk13中提供了一个text blocks 的预览特性 并且在java14中提供了第二个版本的预览
text block文本块
我们之前从外部copy一段文本串到java中 会被自动转义 htmlbodypHello, world/p/body
/html
将其复制到Java的字符串中会展示成以下内容
html\n body\n pHello, world/p\n /body\n
/html\n; 即被自动进行了转义这样的字符串看起来不是很直观在JDK 13中就可以使用以下语法了 htmlbodypHello, world/p/body
/html
; 使用作为文本块的开始符合结束符在其中就可以放置多行的字符串不需要进行任何转义。看起来就十分清爽了。 如常见的SQL语句
String query SELECT EMP_ID, LAST_NAME FROM EMPLOYEE_TBWHERE CITY INDIANAPOLISORDER BY EMP_ID, LAST_NAME;
;jdk14增加了records Java 14 中便包含了一个新特性EP 359: Records Records的目标是扩展Java语言语法Records为声明类提供了一种紧凑的语法用于创建一种类中是“字段只是字段除了字段什么都没有”的类。 通过对类做这样的声明编译器可以通过自动创建所有方法并让所有字段参与hashCode()等方法。这是JDK 14中的一个预览特性。 使用record关键字可以定义一个记录
record Person (String firstName, String lastName) {}
record 解决了使用类作为数据包装器的一个常见问题。纯数据类从几行代码显著地简化为一行代码。详见Java 14 发布了不使用”class”也能定义类了还顺手要干掉Lombokhttp://www.hollischuang.com/archives/4548
jdk14增加了instance模式匹配
instanceof是Java中的一个关键字我们在对类型做强制转换之前会使用instanceof做一次判断例如
if (animal instanceof Cat) {Cat cat (Cat) animal;cat.miaow();
} else if (animal instanceof Dog) {Dog dog (Dog) animal;dog.bark();
}
Java 14带来了改进版的instanceof操作符这意味着我们可以用更简洁的方式写出之前的代码例子
if (animal instanceof Cat cat) {cat.miaow();
} else if(animal instanceof Dog dog) {dog.bark();
}
jdk15增加了封闭类
在java15之前 java认为代码重用始终是一个终极的目标所以一个类和接口都可以被任意的类实现或者继承 但是 在很多的场景中 这样做是容易造成错误的
例如假设一个业务领域只适用于汽车和卡车而不适用于摩托车。 在Java中创建Vehicle抽象类时应该只允许Car和Truck类扩展它。 通过这种方式我们希望确保在域内不会出现误用Vehicle抽象类的情况。 为了解决类似的问题在Java 15中引入了一个新的特性——密闭。
public sealed interface Service permits Car, Truck {
}想要定义一个密闭接口可以将sealed修饰符应用到接口的声明中。然后permit子句指定允许实现密闭接口的类
public abstract sealed class Vehicle permits Car, Truck {
}
以上代码定义了一个密闭接口Service它规定只能被Car和Truck两个类实现。 与接口类似我们可以通过使用相同的sealed修饰符来定义密闭类
通过密闭特性我们定义出来的Vehicle类只能被Car和Truck继承。
jdk17扩展了switch模式匹配
jdk21增加了协程 现在JDK的最新版本是什么
目前Java的发布周期是每半年发布一次大概在每年的3月份和9月份都会发布新版本。 在2023年9月份的时候发布了JDK 21。 2024年3月19日JDK22正式发布。根据正常的发布节奏接下来的发布情况应该是 2024-09 —— JDK 23 2025-03 —— JDK 24 2025-09 —— JDK 25 2026-03 —— JDK 26 在JDK 22及之前的版本中最后一个LTS版本Long Term Support是JDK 21。
SimpleDateFormat是线程安全的吗使用时应该注意什么
在日常的开发中 我们经常会用到时间 我们有很多办法在java代码中获取时间 但是不同的方式获取到的时间的格式不相同 我们这时候就需要一个格式化工具 把时间显示成我们需要的格式
最常用的方式就是使用simpledateformat类 这看起来是一个功能比较简单的类 但是一旦使用不当也有可能导致很大的问题
在阿里巴巴使用手册中 也就是说SimpleDateFormat是非线程安全的所以在多线程场景中不能使用SimpleDateFormat作为共享变量。 因为在simpledateformat中的format方法在执行的过程中 会使用一个成员变量calendar来保存时间
如果我们在声明SimpleDateFormat的时候使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量随之SimpleDateFormat中的calendar也就可以被多个线程访问到。 假设线程1刚刚执行完calendar.setTime把时间设置成2018-11-11还没等执行完线程2又执行了calendar.setTime把时间改成了2018-12-12。这时候线程1继续往下执行拿到的calendar.getTime得到的时间就是线程2改过之后的。 想要保证线程安全要么就是不要把SDF设置成成员变量只设置成局部变量就行了要不然就是加锁避免并发或者使用JDK 1.8中的DateTimeFormatter
SimpleDateFormat用法
1111111111
什么是AIO、BIO和NIO
BIO Blocking I/O同步阻塞I/O是JDK1.4之前的传统IO模型。 线程发起IO请求后一直阻塞直到缓冲区数据就绪后再进入下一步操作。 NIO Non-Blocking I/O同步非阻塞IO线程发起IO请求后不需要阻塞立即返回。用户线程不原地等待IO缓冲区可以先做一些其他操作只需要定时轮询检查IO缓冲区数据是否就绪即可。 AIO Asynchronous I/O异步非阻塞I/O模型。线程发起IO请求后不需要阻塞立即返回也不需要定时轮询检查结果异步IO操作之后会回调通知调用方。 Java中BIO、NIO、AIO分别适用哪些场景
BIO方式适用于连接数目比较小且固定的架构这种方式对服务器资源要求比较高并发局限于应用中JDK1.4以前的唯一选择但程序直观简单易理解。 NIO方式适用于连接数目多且连接比较短轻操作的架构比如聊天服务器并发局限于应用中编程比较复杂JDK1.4开始支持。 AIO方式适用于连接数目多且连接比较长重操作的架构比如相册服务器充分调用OS参与并发操作编程比较复杂JDK7开始支持。