Java基础系列三、常用类的基本方法
1、包装类、自动装箱、拆箱、享元模
1、包装类概念:java为8个基本数据类型设计了一个包装类,为其提供字段和方法,方便使用。
2、包装类继承体系
数值类型的包装类继承了抽象父类Number,而Number类继承Object类,其他的(Boolean Character)直接继承Object,包装类均位于java.lang包下。
3、包装类的创建对象
类名 对象名 = new类名( 对应类型的变量); 具体的()里面放什么参数 查api的对应类型的构造方法。
Integer aa = new Integer(100);
4、自动装箱和拆箱
装箱:基本数据类型转换为包装类型
拆箱:包装类型转换为 基本数据类型
装箱过程(1.直接赋值 Integer i = 10; (自动装箱) 2.构造方法传值 Integer aa = new Integer(100); 3. valueOf(基本类型变量) Integer in = Integer.valueOf(a); )
拆箱过程(1.直接赋值 int c = new Integer(100); (自动拆箱) 2. 基本数据类型 变量名 = 包装类对象名.xxxValue() int value = in.intValue(); )
5、享元模
简单的理解为:对象之间可以共享,当超出某个范围外就会重新创建对象
java中的String类就是采用该模式创建的一个类
参考:https://blog.csdn.net/justloveyou_/article/details/55045638
public class Hello { public static void main(String[] args) { // Integer integer = new Integer("你给我转啊"); //编译成功,运行报错,数据格式异常。 Integer aa = new Integer(100); Integer bb = new Integer("100"); System.out.println(aa == bb); //false System.out.println(aa.equals(bb)); //true Integer i = 10; Integer j = 10; System.out.println(i.equals(j)); //true String a = "nihao"; String b = "nihao"; System.out.println(a.equals(b)); //true System.out.println(a==b);//true System.out.println(s.equals(new String("nihao"))); //true Hello o = new Hello(); Hello p = new Hello(); System.out.println(o.equals(p)); //false Integer ge1 = 128; Integer ge2 = 128; System.out.println(ge1.equals(ge2));//true System.out.println(ge1 == ge2); //false /* * 享元模式: * 简单的理解为:对象之间可以共享,当超出某个范围外就会重新创建对象 * java中的String类就是采用该模式创建的一个类 * */ } }
2、 Integer 包装类的常用方法
/* * Integer类的常用方法 */ public class Integer_Lei { public static void main(String[] args) { int a = 32; String year = "2018"; //1. Integer.parseInt(String s); 用于将字符串转换成基本数据类型(int),要求字符串必须是数字格式。 System.out.println(Integer.parseInt(year)); // 2018 //2. Integer.parseInt(String s,int radix); 将字符串s按照radix进行转换相应的进制数,然后运行的结果都是以十进制的形式打印。 //指定y的进制为2进制 System.out.println(Integer.parseInt("111",2)); //7 //指定age的进制为16进制 System.out.println(Integer.parseInt("A5",16)); //165 //3. Integer.toString(int i, int radix) 将int整数转成指定的进制数 System.out.println(Integer.toString(15,7)); //21 //4. 十进制转成不同的进制,三个静态方法的返回值都是以字符串的形式返回 System.out.println(Integer.toBinaryString(11)); //1011 二进制 System.out.println(Integer.toOctalString(11)); //13 八进制 System.out.println(Integer.toHexString(11)); //b 十六进制 //5.基本数据类型(int) 转换成字符串 // 5.1 任何类型+"" 变成String类型(推荐使用) String str = 53+""; System.out.println(str); //"53" 打印出来的没有"" ,知道是字符串就好。 // 5.2 Integer类中的toString()将整数值转换成字符串 System.out.println(Integer.toString(a)); //"32" //6. Integer的两个静态成员变量 MIN_VALUE MAX_VALUE System.out.println("int最大值是:"+Integer.MAX_VALUE); //2 31-1 System.out.println("Long最大值是:"+Long.MAX_VALUE); //2 63——1 } } //7. Integer.decode() 和 Integer.valueof() 的区别 Integer.decode()合适用来分析数字 可以分析 8进:010=>分析后为 8 10进:10=>分析后为 10 16进:#10|0X10|0x10=>分析后是 16 而 Integer.valueof() 只能数字的String .像 "010" 这样的8进制 他会解析成 =>10
3、Character包装类的常用方法
构造方法:
public Character(char value)
构造一个新分配的 Character 对象,用以表示指定的 char 值
Character类的判断功能:
public static boolean isDigit(char ch)确定指定字符是否为数字。
public static boolean isLetter(char ch)确定指定字符是否为字母。
public static boolean Character.isSpaceChar(ch);//判断字符是不是空格
public static boolean isLowerCase(char ch)确定是否是小写字母字符
public static boolean isUpperCase(char ch)确定是否大写字母字符
两个转换功能:
public static int toLowerCase(char ch)使用取自 UnicodeData 文件的大小写映射信息将字符参数转换为小写。
public static int toUpperCase(char ch)使用取自 UnicodeData 文件的大小写映射信息将字符参数转换为大写。
4、System 类常用的方法
System类在 jdk中文文档 表示没有构造方法,故不能创建对象,其实查看源码发现system类的 无参的构造方法被私有化(即被private关键字修饰)。
System类里面有out属性,该属性类型为 printStream ,而这个 printStream.java类 中有 println()方法。
/** * system类的构造方法被私有化(即被private关键字修饰),故不能在外界创建对象。 * System类中的都是静态方法(static关键字修饰),类名访问即可。下面是五个常用的方法。 * @author 甘劭 */ import java.util.Arrays; public class HaHa { public static void main(String[] args) { System.out.println(System.currentTimeMillis());//获取当前系统时间与1970年01月01日00:00点之前的毫秒差值 //System.exit(0);//结束正在运行的Java程序,通常传入0记为正常状态,其它为异常状态。 System.out.println("不能运行了"); //System.gc(); //用来运行JVM中的垃圾回收器,完成内存中垃圾的清除。 //System.out.println(System.getProperties());//确定当前的系统属性 //System.arraycopy(src, srcPos, dest, destPos, length); //从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。没有返回值。 /* Object src : 原数组 int srcPos : 从原数据的起始位置开始 Object dest : 目标数组 int destPos : 目标数组的开始起始位置 int length : 要copy的数组的长度*/ byte[] s1 = new byte[]{0,1,2,3,4,5,6,7,8,9}; // 源数组 byte[] s2 = new byte[7]; // 目标数组 System.arraycopy(s1,1,s2 ,2,5); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } } //运行结果 //1543924760287 //不能运行了 //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] //[0, 0, 1, 2, 3, 4, 5]
5、String类是怎么实现的
常见问题:String 是如何实现的?它有哪些重要的方法?
这道题目考察的重点是,你对 Java 源码的理解,这也从侧面反应了你是否热爱和喜欢专研程序,而这正是一个优秀程序员所必备的特质。
String 源码属于所有源码中最基础、最简单的一个,对 String 源码的理解也反应了你的 Java 基础功底。
String 问题如果再延伸一下,会问到一些更多的知识细节,这也是大厂一贯使用的面试策略,从一个知识点入手然后扩充更多的知识细节,对于 String 也不例外,通常还会关联的询问以下问题:
为什么 String 类型要用 final 修饰?
== 和 equals 的区别是什么?
String 和 StringBuilder、StringBuffer 有什么区别?
String 的 intern() 方法有什么含义?
String 类型在 JVM(Java 虚拟机)中是如何存储的?编译器对 String 做了哪些优化?
在Java更新的版本变化中,对String对象已经做了大量的优化,来节约内存空间,提升String对象在系统中的性能。来看看在Java版本迭代中String的优化过程;
1、在Java6以及以前的版本中,String对象是对char数组进行了封装实现的对象,主要有四个成员变量:char数组、偏移量offset、字符数量count、哈希值hash。
2、在Java7和8版本中,Java对String类做了改变,不再有offset和count两个变量,这样可以稍微减少String对象占用的内存。同时,String.substring()不再共享char[],从而解决了使用该方法可能导致的内存泄露问题。
3、从Java9版本开始,char[]改成了byte[],有维护了一个新的属性coder,它是一个编码格式的标识。
为什么从char[]改变成byte[],我们都知道一个char字符占用16位,2个字节,这种情况在存储单字节编码内的字符就有点浪费。Java9中String类为了更加节约内存空间,选择了占用8位,1字节的byte数组来存放字符串。
新属性coder的作用是在计算字符串长度或者使用indexOf()函数时,我们需要根据这个字段,判断如何计算字符串长度。coder属性默认有0和1两个值,0代表Latin-1(单字节编码),1代表UTF-16。如果String判断字符串只包含了Latin-1,则coder属性值为0,反之则为1.
典型回答:以主流的 JDK 版本 1.8 来说,String 内部实际存储结构为 char 数组,源码如下:
public final class String implements java.io.Serializable,Comparable<String>,CharSequence{ //用于存储字符串的值 private final charvalue[]; //缓存字符串的 hash code private int hash; //Default to 0 //......其他内容 }
Stirng中的几个重要方法
①、String字符串中4个重要的构造方法:
// String 为参数的构造方法 public String(String original) { this.value = original.value; this.hash = original.hash; } // char[] 为参数的构造方法 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } // StringBuffer 为参数的构造方法 public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } } // StringBuilder 为参数的构造方法 public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
②、equals()比较两个字符串是否相等
源码如下:
public boolean equals(Object anObject) { // 对象引用相同直接返回true if (this == anObject) { return true; } // 判断需要对比的值是否为 String 类型,如果不是则直接返回 false if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { // 把两个字符串转化为 char 数组对比 char v1[] = value; char v2[] = anotherString.value; int i = 0; // 循环比对两个字符串的每一字符 while (n-- != 0) { // 如果其中一个字符不相等就返回false 若相等就继续比对 if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
String
类型重写了Object
中的equals()
方法,equals()
方法需要传递一个Object
类型的参数值,在比较时会先通过instanceof
判断是否为String
类型,如果不是则会直接返回false
,当判断参数为String
类型之后,会循环对比两个字符串中的每一个字符,当所有字符都相等时返回true
,否则则返回false
。
还有一个和equals()
比较类似的方法equalslgnoreCase()
,它是用于忽略字符串的大小写之后进行字符串对比。
③、compareTo()比较两个字符串
compareTo() 比较字符串的大小 返回的是一个int类型值。 判断字符串大小的依据是:比较 对应的字符 在(ASCII码顺序)。
1、如果字符串相等返回值0
2、如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值(ASCII码值) (负值代表:前字符串的值小于后字符串,正值代表:前字符串大于后字符串)
3、如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较, 以此类推,直至比较的字符或被比较的字符有一方全比较完,这时就比较字符的长度。
还有一个和compareTo()
比较类似的方法 compareTolgnoreCase()
,用于忽略大小写后比较两个字符串。
源码如下:
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; // 获取两个字符串长度最短的那个 int 值 int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; // 对比每个字符串 while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { // 有字符不相等就返回差值 return c1 - c2; } k++; } return len1 - len2; }
equals()和compareTo()的不同点
-
equals()
可以接收一个Object
类型的参数,而compareTo()
只能接收一个String
类型的参数; -
equals()
返回值为Boolean
,而compareTo()
的返回值则为int
。
import java.util.Arrays; // String类 常用的方法 public class String_Lei { public static void main(String[] args) { //1. String类 常用的 构造方法 // 1.1 public String(); 空参构造 初始化一个新创建的 String 对象,使其表示一个空字符序列 // 注意,由于 String 是不可变的,所以无需使用此构造方法。 // 1.2 方法: public String(char[] value) 用字符数组value创建一个String对象 char[] value ={'a','b','c','d','e'}; String str1 = new String(value); System.out.println(str1); //"abcde" // 1.3 public String(char chars[], int x, int n) 用字符数组以x开始的n个字符创建一个String对象 String str2 = new String(value, 1, 3); //字符串下标从0开始 System.out.println(str2); //"bcd" //2. String类中 常用的 判断方法 // 2.1 boolean equals(Object obj); // 将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。 // 2.2 boolean equalsIgnoreCase(String str); // 将此 String 与另一个 String 比较,如果两个字符串的长度相同,且相应字符都相等(忽略大小写),则认为这两个字符串是相等的。 // 2.3 boolean contains(String str); // 当且仅当此字符串包含指定的 字符串 值序列时,返回 true。 String s1 = "abcdefg"; String s2 = "bcde"; System.out.println(s1.contains(s2)); //true // 2.4 boolean startsWith(String str); boolean endsWith(String str); // 判断是否 以 某个 字符串 开头/结尾 System.out.println(s1.startsWith("ab")); //ture System.out.println(s1.startsWith("bc")); //false // 2.5 boolean isEmpty(); 当且仅当 length() 为 0 时返回 true String s3 = ""; String s4 = null; System.out.println(s3.isEmpty()); //true // System.out.println(s4.isEmpty()); //NullPointerException 空指针异常 //3. String类中的获取方法 // 3.1 int length(); 返回此字符串的长度。 数组中的length是属性,String类中的length()是方法; // 3.2 Char charAt(int index); 返回指定 索引 处的 char 值。索引范围为从 0 到 length()-1。 // 3.3 int indexof(int ch); 返回指定 字符 在此 字符串 中第一次出现处的索引 // 3.4 查找子串在字符串中的位置 //public int indexOf(String str) //返回指定 子字符串 在此 字符串 中第一次出现处的索引。若没有出现则返回-1。 //public int indexOf(String str, int fromIndex) //该方法与第一种类似,区别在于该方法从fromIndex位置向后查找。 //public int lastIndexOf(String str) //该方法与第一种类似,区别在于该方法从字符串的末尾位置向前查找。 //public int lastIndexOf(String str, int fromIndex) //该方法与第二种方法类似,区别于该方法从fromIndex位置向前查找。 // 3.5 substring(int start); 从指定位置开始截取字符串,默认都末尾; // substring(int beginIndex, int endIndex) 从指定位置开始到指定位置截取字符串,前包后不包。 //4. String类的转换功能 // 4.1 byte[] getBytes();使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一 个新的 byte 数组中。 byte[] ch1 = s1.getBytes(); System.out.println(Arrays.toString(ch1)); // [97, 98, 99, 100, 101, 102, 103] // 4.2 char[] toCharArray(); 将此字符串转换为一个新的 字符 数组。 char[] ch = s1.toCharArray(); System.out.println(Arrays.toString(ch)); // [a, b, c, d, e, f, g] // 4.3 String.valueOf(char[] date);把 字符数组 转换成 字符串; char[] v1 ={'a','b','c','d','e'}; String aa = String.valueOf(v1); System.out.println(aa); // "abcde" // 4.4 String.valueOf(int i);把int类型的数据转换成字符串. // 注意:String类的valueOf方法可以把任意类型的数据转成字符串; System.out.println(String.valueOf(412412.434)); //"412412.434" //5. String类的其他方法 // 5.1 String replace(String old,String new); // 返回一个新的字符串,使用指定的字面值 (后面的字符串)替换 此字符串所有 相匹配子字符串(前面的字符串)。 String oldStr = " abc defg "; String newStr = oldStr.replace("f", "HHHH"); System.out.println(newStr); // " abc deHHHHg " // 5.2 trim();去掉字符串两端的空格,中间的空格无法去掉 System.out.println(oldStr.trim()); //"abc defg" // 5.3 concat() 将指定字符串连接到此字符串的结尾。 String ss = "哈哈\u4e00-\u9fa5"; String ssb ="一个汉字两个字符,汉字的范围"; System.out.println(ss.concat(ssb)); //"哈哈一-龥一个汉字两个字符,汉字的范围" // 5.4 split() 根据给定 正则表达式 的匹配拆分此字符串。 此方法返回的 字符串数组 包含此 字符串的子字符串。 // 其中:“.”和“|” “+” “*” 都是转义字符,必须得加"\\"; String[] sa = "aaa|bbb|ccc".split("\\|"); System.out.println(Arrays.toString(sa)); // [aaa, bbb, ccc] // 还有如果想在串中使用"\"字符,则也需要转义.首先要表达"aaaa\bbbb"是错误的,应该用"aaaa\\bbbb"才对。 String[] sb = "aaa\\bbb\\ccc".split("\\\\"); System.out.println(Arrays.toString(sb)); // [aaa, bbb, ccc] // 5.5 compareTo() 比较字符串的大小 返回的是一个int类型值。 判断字符串大小的依据是:比较 对应的字符 在(ASCII码顺序)。 /* 1、如果字符串相等返回值0 2、如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的差值(ASCII码值) (负值代表:前字符串的值小于后字符串,正值代表:前字符串大于后字符串) 3、如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较, 以此类推,直至比较的字符或被比较的字符有一方全比较完,这时就比较字符的长度. */ String xx = "abc"; String yy = "abczzz"; System.out.println(xx.compareTo(yy)); //-3 // 5.6 compareToIgnoreCase() 方法是不区分大小写,返回值是int,比较方式与compareTo()相同 String ss1 = "adger"; String ss2 = "adgerfa"; String ss3 = "ADGER"; String ss4 = "adher"; System.out.println(ss1.compareTo(ss2));//-2 System.out.println(ss1.compareTo(ss3));//32 System.out.println(ss1.compareToIgnoreCase(ss3));//0 System.out.println(ss3.compareToIgnoreCase(ss4));//-1 } }
6、为什么 String 类型要用 final 修饰?
从 String 类的源码我们可以看出 String 是被 final 修饰的不可继承类,源码如下:
public final class String implements java.io.Serializable,Comparable<String>,CharSequence{ ... }
那这样设计有什么好处呢?
Java语言之父James Gosling的回答是,他会更倾向于使用final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
James Gosling还说迫使String类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之情,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使String类设计成不可变类的一个重要原因。
总结来说,使用final修饰的第一个好处是安全;第二个好处是高效;以JVM中的字符串常量池来举例,如下两个变量:
String s1 = "java"; String s2 = "java";
有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率,如下图所示:
试想一下如果String是可变的,那么当s1的值修改之后,s2的值也跟着改变了,这样就和我们预期的结果不相符了,因此也就没有办法实现字符串常量池的功能了。
String 对象的不可变性好处
1、保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。
2、保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。
3、可以实现字符串常量池。
7、String,StringBuilder,StringBuffer三者的区别
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:
String str="abc"; System.out.println(str); str=str+"de"; System.out.println(str);
如果运行这段代码会发现先输出“abc”,然后又输出“abcde”
首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
另外,有时候我们会这样对字符串进行赋值
String str="abc"+"de"; StringBuilder stringBuilder=new StringBuilder().append("abc").append("de"); System.out.println(str); System.out.println(stringBuilder.toString());
这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和
String str="abcde";是完全一样的,所以会很快,而如果写成下面这种形式
String str1="abc"; String str2="de"; String str=str1+str2;
那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
2、再来说线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
3、总结一下
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
8、String 的 intern() 方法有什么含义?
源码:public native String intern();
String.intern() 方法可以使得所有含相同内容的字符串都共享同一个内存对象,可以节省内存空间。
JVM 中,存在一个字符串常量池,字符串的值都存放在这个池中。当调用 intern 方法时,如果字符串常量池中已经存在该字符串,那么返回池中的字符串引用;否则将此字符串添加到字符串常量池中,并返回字符串的引用。
JDK1.6 和 JDK1.7 在 intern() 方法的实现上,有相同,也有不同。
相同点: 先去查看字符串常量池是否有该字符串,如果有,则返回字符串常量池中的引用。 不同点: 如果是 JDK1.7,当字符串常量池中找不到对应的字符串时,不会将字符串拷贝到字符串常量池,而只是在字符串常量池生成一个对该字符串的引用。而 JDK1.6 会拷贝字符串至字符串常量池。
jdk1,6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。所以导致string的intern方法因为以上变化在不同版本会有不同表现。
注意:字符串常量池中的 String 对象,也是可以被 GC 回收的,只要它不再被引用了。
如何使用 String.intern 节省内存?
在每次赋值的时候使用 String 的 intern 方法,如果常量池中有相同值,就会重复使用该对象,返回对象引用,为了更好的理解,我们举一个简单的例子来看一下:
String a = new String("abc").intern(); String b = new String("abc").intern(); if (a == b) { System.out.print("a==b"); } String c = new String("abcd"); String d = new String("abcd"); if (c == d) { System.out.print("c==d"); }
输出结果只有:a==b 分析一下;
创建 a 变量时,调用 new Sting() 会在堆内存中创建一个 String 对象,String 对象中的 char 数组将会引用常量池中字符串。在调用 intern 方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。
创建 b 变量时,调用 new Sting() 会在堆内存中创建一个 String 对象,String 对象中的 char 数组将会引用常量池中字符串。在调用 intern 方法之后,会去常量池中查找是否有等于该字符串对象的引用,有就返回引用。
而在堆内存中的两个对象,由于没有引用指向它,将会被垃圾回收。所以 a 和 b 引用的是同一个对象。
使用 intern 方法需要注意的一点是,一定要结合实际场景。因为常量池的实现是类似于一个 HashTable 的实现方式,HashTable 存储的数据越大,遍历的时间复杂度就会增加。如果数据过大,会增加整个字符串常量池的负担。
9、编译器对 String 做了哪些优化?
1、 如何构建超大字符串?
编程过程中,字符串的拼接很常见。 String 对象是不可变的,如果我们使用 String 对象相加,拼接我们想要的字符串,是不是就会产生多个对象呢?例如以下代码:
String str= "ab" + "cd" + "ef";
分析代码可知:首先会生成 ab 对象,再生成 abcd 对象,最后生成 abcdef 对象,从理论上来说,这段代码是低效的。
但实际运行中,我们发现只有一个对象生成,这是为什么呢?难道我们的理论判断错了?我们再来看编译后的代码,你会发现编译器自动优化了这行代码,如下:
String str= "abcdef";
上面介绍的是字符串常量的累计,再来看看字符串变量的累计又是怎样的呢?
String str = "abcdef"; for(int i=0; i<1000; i++) { str = str + i; }
上面的代码编译后,你可以看到编译器同样对这段代码进行了优化。不难发现,Java 在进行字符串的拼接时,偏向使用 StringBuilder,这样可以提高程序的效率。
String str = "abcdef"; for(int i=0; i<1000; i++) { str = (new StringBuilder(String.valueOf(str))).append(i).toString(); }
综上已知:即使使用 + 号作为字符串的拼接,也一样可以被编译器优化成 StringBuilder 的方式。但再细致些,你会发现在编译器优化的代码中,每次循环都会生成一个新的 StringBuilder 实例,同样也会降低系统的性能。
所以平时做字符串拼接的时候,我建议你还是要显示地使用 StringBuilder 来提升系统性能。如果在多线程编程中,String 对象的拼接涉及到线程安全,你可以使用 StringBuffer。但是要注意,由于 StringBuffer 是线程安全的,涉及到锁竞争,所以从性能上来说,要比 StringBuilder 差一些。
10、创建字符串的两种方式
1、String str1= “abc”;
在编译期,JVM会去常量池来查找是否存在“abc”,如果不存在,就在常量池中开辟一个空间来存储“abc”;如果存在,就不用新开辟空间。
然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。
2、String str2 = new String("abc") ;
在编译阶段JVM先去常量池中查找是否存在“abc”,如果过不存在,则在常量池中开辟一个空间存储“abc”。在运行时期,通过String类的构造器在堆内存中new了一个空间,
然后将String池中的“abc”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。
也就是说,前者在初始化的时候可能创建了一个对象,也可能一个对象也没有创建;后者因为new关键字,至少在内存中创建了一个对象,也有可能是两个对象。
11、创建空字符串的三种情况
String s = new String(); String s = ""; String s = null;
注意: “” 和null的区别:
“”是字符串常量.同时也是一个String类的对象,既然是对象当然可以调用String类中的方法;
Null是空常量,不能调用任何的方法,否则会出现空指针异常,null常量可以给任意的引用数据类型赋值
public class Test { public static void main(String[] args) { //创建空字符串的三种方式 String str1 = null; // 表示str1没有指向一个对象 String str2 = ""; // 有一个对象,对象是空, String str3 = new String(); // 和上面差不多 System.out.println(str1==str2); //false System.out.println(str1==str3); //false System.out.println(str2==str3); //false System.out.println(str2.equals(str1)); //false System.out.println(str2.equals(str3)); //true System.out.println(str3.equals(str1)); //false System.out.println(str3.equals(str2)); //true System.out.println(str1.equals(str2)); //尽量使用一个已知的值去和未知的值比较。尽可能的避免空指针异常。} } }
12、创建了几个字符串对象的面试题
注意:一般这种问法不会考虑常量池中的对象,是问堆中的对象,如果是当面问,可以给他说说有常量池这么一个东西,表示你知道有这么回事。
①、 String s1 = “A” + “hello” 在编译成class的时候,就是编译器,因为”A”和”helllo”都是确定的常量,直接编译成”Ahello”,这个也是一个确定的常量,直接在常量区,堆中不会有对象。
②、 String s2 = “A”;//常量区
String s3 = s2 + “hello”; //你使用了+,要改变我字符串,我就不会存在在常量区了,我就跑到堆中去了,所以堆中:A hello Ahello 3个对象
③ 、String s4 = new String(“A”) + new String(“hello”);
很明显,直接使用了new关键字,堆中!!!!字符串不能更改,一个new就是一个,在来一个+号,又增加了一个。就是三个: A hello Ahello
13、 Runtime类常用方法
import java.io.IOException; public class Test { // Runtime类,他是一个与JVM运行时环境有关的类,该类不能创建对象,因为构造方法私有化了(单例模式); public static void main(String[] args) throws IOException { //可以取得当前JVM的运行时环境,这也是在Java中唯一一个得到运行时环境的方法。 // Runtime 类的大多数方法都是实例方法,也就是说每次进行运行时都要调用时getRuntime方法来获得与当前 Java 应用程序相关的运行时对象 Runtime run = Runtime.getRuntime(); run.exec("notepad"); //可以去执行程序的某个进程。 /* Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的一个吧, 因为我看到System类中的exit实际上也是通过调用 Runtime.exit()来退出JVM的, 这里说明一下Java对Runtime返回值的一般规则(后边也提到了),0代表正常退出,非0代表异常中 止,这只是Java的规则, 在各个操作系统中总会发生一些小的混淆。*/ System.out.println("程序开始了吗"); run.exit(0); System.out.println("程序终止了吗"); } }
14、 BigDecimal类
类似于包装类,但是有些地方也有不同,比如不能自动装箱和拆箱,主要用于处理金钱和精确度较高的数据。
import java.math.BigDecimal; public class BigDecimalDemo { public static void main(String[] args) { //使用double:结果不精确 //加减乘除操作都不精确 System.out.println("0.09 + 0.01="+(0.09+0.01)); //使用BigDecimal:BigDecimal(double vale) BigDecimal num1 = new BigDecimal(0.09); BigDecimal num2 = new BigDecimal(0.02); System.out.println(num1.add(num2)); //结果还是不精确,因为传入double值的时候该值就不精确 //为了解决这一问题 使用BigDecimal(String vale) num1 = new BigDecimal("0.09"); num2 = new BigDecimal("0.01"); System.out.println(num1.add(num2)); } } 0.09 + 0.01=0.09999999999999999 0.1099999999999999970856645603589640813879668712615966796875 0.10
15、BigInteger类
java中的进制BigInteger十分的强大,而且好用,他可以表示任意大的整数,同时还可以进行进制转换,十分的方便,
代码示例:
import java.math.BigInteger;//导入该包 public class Demo { public static void main(String[] args) { String str = new BigInteger("15", 10).toString(16); System.out.println(str); } }
注:这里是将10进制的15转为16进制,依葫芦画瓢,便很容易实现转换;
16、 产生随机数的三种方法
import java.util.Random; import java.util.UUID; public class Test { public static void main(String[] args) { // 1. 产生数值类型的随机数经常使用这种 System.out.println((int)(Math.random()*21)+5); // 2. 产生随机数的第二种方式 Random r = new Random(); int randomValue = r.nextInt(10); //十以内的整型随机数,前包后不包。[0, 10) System.out.println(randomValue); // 3. 产生随机数的第三种方式(经常使用这种方式给文件命名,命名的时候要去除“-”,再拿来使用) //该方法产生36位,有四位是 "-" 。 //这种方式的随机数永远不会重复,因为有 System.currentTimeMillis()这个种子参与。 UUID rand = UUID.randomUUID(); String name = rand.toString().replace("-", ""); System.out.println(name); }
17、随机生成四位验证码
public static void main(String[] args) { int count = 0; char [] ch = new char[62]; for(char i ='0';i<='z';i++){ if(Character.isLetter(i)|Character.isDigit(i)){ ch[count] = i; count++; } } char []checkCode = new char[4]; for(int i = 0;i<checkCode.length;i++){ checkCode[i] = ch[(int)(Math.random()*62 )]; } System.out.println("随机生成四位验证码:"+new String(checkCode)); }
18、时间格式转换
public static void main(String[] args) { //1. 自定义格式转化标准格式 String day = "2011年11月11日 11时11分11秒"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"); try { Date day1 = sdf.parse(day); System.out.println(day1); //Fri Nov 11 11:11:11 CST 2011 SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String tt = sdf2.format(day1); System.out.println(tt); //2011-11-11 11:11:11 } catch (ParseException e) { e.printStackTrace(); } //2. 当前时间转换成指定格式 Date now = new Date(); SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒"); String dd = sdf1.format(now); System.out.println(dd); //2018年12月08日 18时37分26秒 }
19、日历类的常用方法和属性
Calendar cal = Calendar.getInstance();//获取日历对象 cal.set(Calendar.YEAR, 225);//直接设置日历中的字段值 cal.set(2018, 11,4); cal.add(Calendar.MONTH, 2);//对时间进行偏移 System.out.println(cal.get(Calendar.DAY_OF_WEEK));//一周中的第几天 一周中第一天从星期天开始 System.out.println(cal.get(Calendar.MONTH));//一年中的第几月份 月份从0开始 System.out.println(cal.get(Calendar.DAY_OF_MONTH)); System.out.println(cal.get(Calendar.DAY_OF_YEAR)); System.out.println(cal.get(Calendar.WEEK_OF_YEAR)); System.out.println(cal.get(Calendar.WEEK_OF_MONTH)); System.out.println(cal.get(Calendar.HOUR_OF_DAY)); System.out.println(cal.get(Calendar.SECOND)); System.out.println(cal.get(Calendar.MINUTE)); System.out.println(cal.get(Calendar.YEAR)); Date time = cal.getTime(); //将日历对象转化为日期对象 cal.setTime(time); //将日期对象的时间设置给日历对象
20、Math类其他常用的方法
System.out.println(Math.hypot(3,4));//求直角三角形斜边长 System.out.println(Math.sqrt(9));//开平方根号 System.out.println(Math.cbrt(27));//开立方根号 System.out.println(Math.pow(2, 8));//求第一个参数的第二个参数幂次方 System.out.println(bd1.add(bd2));//加 System.out.println(bd1.subtract(bd2));//减 System.out.println(bd1.multiply(bd2));//乘 System.out.println(bd1.divide(bd2));//除 除不尽的时候出现算术异常(ArithmeticException) Math.round() 四舍五入 Math.ceil()向上取整 Math.floor()向下取整 Math.random() 0-1随机数