0.String:
1.要了解String首先应知道JAVA内存结构基础; 内存结构和内存模型需要专题介绍。
内存结构
方法区(Method Area),是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等。虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non一Heap(非堆),目的就是要和堆分开。这部分存储的是运行时必须的类相关信息,装载进此区域的数据是不会被垃圾收集器回收的,只有关闭Jvm才会释放这块区域占用的内存。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生产的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。Java语言不要求常量一定只有编译器才能产生,运行时也可能将新的常量放入池中,该特性用的比较多的就是String类的intern()方法。运行时常量池是方法区的一部分,在内存不够时,也会抛出OutOfMemoryError异常。
Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是线程共享的,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
虚拟机栈也称为Java栈,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)。Java虚拟机栈是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)。栈帧包括局部变量表、操作数栈、动态链接、方法返回地址和一些附加信息。每一个方法被调用直至执行完毕的过程,就对应这一个栈帧在虚拟机栈中从入栈到出栈的过程。
2.String详解
2.1字符串是常量,一旦被创建就不能改变,这是因为字符串的值是存放在方法区的常量池里面,但是引用可以改变。字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。
private final char value[]:被final修饰导致其变量被变成常量,value[]使用数组存储变量,导致其一旦被创建,长度就不可变。在常量池中String是比较特殊,有专门的池子,字符串常量池。
所有被String定义的变量都会被放入字符串常量池中(包括“”包括的字符串)。而new的String一定会在堆中被重新创建。
public final class String
implements java.io.Serializable, Comparable, CharSequence{
/** The value is used for character storage. */
private final char value[];
........
}
String str=new String("aaa"); 该语句创建了一个或者两个对象。JVM首先在字符串池中查找有没有"aaa"这个字符串对象。
如果没有,则首先在常量池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str引用。
aaa不在常量池
如果有,则不在常量池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str,这样,str就指向了堆中创建的这个"aaa"字符串对象;
aaa在常量池
String str1 = new String("A"+"B") ; 会创建多少个对象?
String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?
str1:
字符串常量池:"A","B","AB" : 3个
堆:new String("AB") :1个
引用: str1 :1个
总共 : 5个
str2 :
字符串常量池:"ABC" : 1个
堆:new String("ABC") :1个
引用: str2 :1个
总共 : 3个
2.2 intern方法使用:一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。Java语言不要求常量一定只有编译器才能产生,运行时也可能将新的常量放入池中,该特性用的比较多的就是String类的intern()方法。
2.3 字符串拼接:关键在于JVM编译时能否优化。
public void test(){
String str1="abc";
String str2="def";
String str3=str1+str2;
System.out.println(str3=="abcdef"); //false
}
步骤:
1)栈中开辟一块中间存放引用str1,str1指向池中String常量"abc"。
2)栈中开辟一块中间存放引用str2,str2指向池中String常量"def"。
3)栈中开辟一块中间存放引用str3。
4)str1 + str2通过StringBuilder的最后一步toString()方法还原一个新的String对象"abcdef",因此堆中开辟一块空间存放此对象。
5)引用str3指向堆中(str1 + str2)所还原的新String对象。
6)str3指向的对象在堆中,而常量"abcdef"在池中,输出为false
public void test(){
String s0 = "a1";
String s1 = "a" + 1;
System.out.println("===========test6============");
System.out.println((s0 == s1)); //result = true
String s2 = "atrue";
String s3= "a" + "true";
System.out.println((s2 == s3)); //result = true
String s4 = "a3.4";
String s5 = "a" + 3.4;
System.out.println((s4 == s5)); //result = true
}
此实例与上面实例差别在于一个是拼接的引用一个是拼接的常量。在编译期间可以优化的属于第二种情况,在第一种情况下编译期间不能优化,等到运行时候进行拼接则会产生新对象。看下面的特殊情况
public void test(){
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = true
}
对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + s1和"a" + "b"效果是一样的。故上面程序的结果为true。
public void test(){
String s0 = "ab";
final String s1 = getS1();
String s2 = "a" + s1;
System.out.println((s0 == s2)); //result = false
}
private static String getS1() {
return "b";
}
这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
String、StringBuffer、StringBuilder的区别
(1)可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。
(2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
(3)String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。
StringBuffer str="a"; //会报错
String str1 ="b"; // 不报错:Sting "b" 是同一类型,都是字符串常量。
参考:https://www.cnblogs.com/xiaoxi/p/6036701.html
1.Object
Object是超类:String中的equals()重写过了。Object的equals()是采用"=="进行比较的。而"=="可以比较基本数据类型(比较值相等)和引用数据类型(比较引用地址相等)。
Objects是工具类。
Object的API:
protected Object clone():创建并返回一个对象的拷贝。
boolean equals(Object obj):比较两个对象是否相等。
protected void finalize():当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法。
Class> getClass():获取对象的运行时对象的类。
int hashCode():获取对象的 hash 值。
void notify():唤醒在该对象上等待的某个线程。
void notifyAll():唤醒在该对象上等待的所有线程。
String toString():返回对象的字符串表示形式。
void wait():让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout):让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间。
void wait(long timeout, int nanos):
与 wait(long timeout) 方法类似,多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。
Objects实例:
public class Test {
public static void main(String[] args) {
String s1 = null;
String s2 = "sss";
System.out.println(s1.equals(s2));
//很多方法里考虑了对象是null的情况,在输入的参数是null时有特定的处理方式。
Boolean result = Objects.equals(s1,s2);
System.out.println(result);
}
}
2.Date
Date(): 构造函数使用当前日期和时间来初始化对象。
Date(long millisec):构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
boolean after(Date date):若当调用此方法的Date对象在指定日期之后返回true,否则返回false。
boolean before(Date date):若当调用此方法的Date对象在指定日期之前返回true,否则返回false。
Object clone( ):返回此对象的副本。
int compareTo(Date date):比较当调用此方法的Date对象和指定日期。两者相等时候返回0。调用对象在指定日期之前则返回负数。调用对象在指定日期之后则返回正数。
int compareTo(Object obj):若obj是Date类型则操作等同于compareTo(Date) 。否则它抛出ClassCastException。
boolean equals(Object date):当调用此方法的Date对象和指定日期相等时候返回true,否则返回false。
long getTime( ):返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
int hashCode( ): 返回此对象的哈希码值。
void setTime(long time):用自1970年1月1日00:00:00 GMT以后time毫秒数设置时间和日期。
String toString( ):把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中: dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。
实例:
import java.util.*;
import java.text.*;
public class DateDemo {
public static void main(String args[]) {
Date dNow = new Date();
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(dNow));
}
}
3.Calendar
Calendar类是一个抽象类,在实际使用时实现特定的子类的对象,创建对象的过程对程序员来说是透明的,只需要使用getInstance方法创建即可。
Calendar c1 = Calendar.getInstance();
// 获得年份
int year = c1.get(Calendar.YEAR);
// 获得月份
int month = c1.get(Calendar.MONTH) + 1;
// 获得日期
int date = c1.get(Calendar.DATE);
// 获得小时
int hour = c1.get(Calendar.HOUR_OF_DAY);
// 获得分钟
int minute = c1.get(Calendar.MINUTE);
// 获得秒
int second = c1.get(Calendar.SECOND);
// 获得星期几(注意(这个与Date类是不同的):1代表星期日、2代表星期1、3代表星期二,以此类推)
int day = c1.get(Calendar.DAY_OF_WEEK);
Calendar类实现了公历日历,GregorianCalendar是Calendar类的一个具体实现。其构造方法。
GregorianCalendar():在具有默认语言环境的默认时区内使用当前时间构造一个默认的 GregorianCalendar。
GregorianCalendar(int year, int month, int date):在具有默认语言环境的默认时区内构造一个带有给定日期设置的 GregorianCalendar
GregorianCalendar(int year, int month, int date, int hour, int minute):为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
GregorianCalendar(int year, int month, int date, int hour, int minute, int second): 为具有默认语言环境的默认时区构造一个具有给定日期和时间设置的 GregorianCalendar。
GregorianCalendar(Locale aLocale):在具有给定语言环境的默认时区内构造一个基于当前时间的 GregorianCalendar。
GregorianCalendar(TimeZone zone):在具有默认语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。
GregorianCalendar(TimeZone zone, Locale aLocale): 在具有给定语言环境的给定时区内构造一个基于当前时间的 GregorianCalendar。
Calendar类的API:
void add(int field, int amount):根据日历规则,将指定的(有符号的)时间量添加到给定的日历字段中。
protected void computeFields():转换UTC毫秒值为时间域值
protected void computeTime():覆盖Calendar ,转换时间域值为UTC毫秒值
boolean equals(Object obj):比较此 GregorianCalendar 与指定的 Object。
int get(int field):获取指定字段的时间值
int getActualMaximum(int field):返回当前日期,给定字段的最大值
int getActualMinimum(int field):返回当前日期,给定字段的最小值
int getGreatestMinimum(int field): 返回此 GregorianCalendar 实例给定日历字段的最高的最小值。
Date getGregorianChange():获得格里高利历的更改日期。
int getLeastMaximum(int field):返回此 GregorianCalendar 实例给定日历字段的最低的最大值
int getMaximum(int field):返回此 GregorianCalendar 实例的给定日历字段的最大值。
Date getTime():获取日历当前时间。
long getTimeInMillis():获取用长整型表示的日历的当前时间
TimeZone getTimeZone():获取时区。
int getMinimum(int field):返回给定字段的最小值。
int hashCode():重写hashCode.
boolean isLeapYear(int year):确定给定的年份是否为闰年。
void roll(int field, boolean up):在给定的时间字段上添加或减去(上/下)单个时间单元,不更改更大的字段。
void set(int field, int value):用给定的值设置时间字段。
void set(int year, int month, int date):设置年、月、日的值。
void set(int year, int month, int date, int hour, int minute):设置年、月、日、小时、分钟的值。
void set(int year, int month, int date, int hour, int minute, int second):设置年、月、日、小时、分钟、秒的值。
void setGregorianChange(Date date):设置 GregorianCalendar 的更改日期。
void setTime(Date date):用给定的日期设置Calendar的当前时间。
void setTimeInMillis(long millis):用给定的long型毫秒数设置Calendar的当前时间。
void setTimeZone(TimeZone value):用给定时区值设置当前时区。
String toString():返回代表日历的字符串。
4.System
system中包含了in、out和err三个成员变量,分别代表标准输入流(键盘输入)、标准输出流(显示器)和标准错误输出流(显示器)。
//标准输入流
public final static InputStream in;
//标准输出流
public final static PrintStream out;
//标准错误流
public final static PrintStream err;
常用API
System.arraycopy(a,b,c,d,e): 其中,a是被复制的数组,b是复制的起始位置,c是复制到的数组,d是复制到这个数组的起始位置,e是复制到这个数组的结束位置。
System.currentTimeMillis():返回毫秒数。
System.getProperties():获取系统属性。
System.gc():用来运行JVM中的垃圾回收器,完成内存中垃圾的清除。
System.exit(0):结束正在运行的Java程序。参数传入一个数字即可。通常传入0记为正常状态,其它为异常状态。
5.Scanner
创建 Scanner 对象的基本语法:
Scanner s = new Scanner(System.in);
实例:如果要输入 int 或 float 类型的数据,在 Scanner 类中也有支持,但是在输入之前最好先使用 hasNextXxx() 方法进行验证,再使用 nextXxx() 来读取。
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
int i = 0;
float f = 0.0f;
System.out.print("输入整数:");
if (scan.hasNextInt()) {
// 判断输入的是否是整数
i = scan.nextInt();
// 接收整数
System.out.println("整数数据:" + i);
} else {
// 输入错误的信息
System.out.println("输入的不是整数!");
}
System.out.print("输入小数:");
if (scan.hasNextFloat()) {
// 判断输入的是否是小数
f = scan.nextFloat();
// 接收小数
System.out.println("小数数据:" + f);
} else {
// 输入错误的信息
System.out.println("输入的不是小数!");
}
scan.close();
}
}
next() 与 nextLine() 区别
next():
一定要读取到有效字符后才可以结束输入。
对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
next() 不能得到带有空格的字符串。
nextLine():
以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
可以获得空白。
6.Integer
public void testEquals() {
int int1 = 12;
int int2 = 12;
Integer integer1 = new Integer(12);
Integer integer2 = new Integer(12);
Integer integer3 = new Integer(127);
Integer a1 = 127; //或者写成Integer a1 = Integer.valueOf(127);
Integer a2 = 127;//或者写成Integer a2 = Integer.valueOf(127);
Integer a = 128;
Integer b = 128;
System.out.println("int1 == int2 -> " + (int1 == int2));
System.out.println("int1 == integer1 -> " + (int1 == integer1));
System.out.println("integer1 == integer2 -> " + (integer1 == integer2));
System.out.println("integer3 == a1 -> " + (integer3 == a1));
System.out.println("a1 == a2 -> " + (a1 == a2));
System.out.println("a == b -> " + (a == b));
}
答案是:
1、 int1 == int2 -> true
2、 int1 == integer1 -> true
3、 integer1 == integer2 -> false
4、 integer3 == a1 -> false
5、 a1 == a2 -> true
6、 a == b -> false
分析:
1、int1 == int2 为true。
2、int1 == integer1,Integer是int的封装类,当Integer与int进行==比较时,Integer就会拆箱成一个int类型,所以还是相当于两个int类型进行比较,这里的Integer,不管是直接赋值,还是new创建的对象,只要跟int比较就会拆箱为int类型,所以就是相等的。
3、integer1 == integer2 -> false,这是两个都是对象类型,而且不会进行
4、integer3 == a1 -> false , integer3是一个对象类型,而a1是一个常量它们存放内存的位置不一样,所以也不等。
5、6 看起来是一模一样的为什么一个是true,一个是false,这是因为Integer作为常量时,对于-128到127之间的数,会进行缓存,也就是说int a1 = 127时,在范围之内,这个时候就存放在缓存中,当再创建a2时,java发现缓存中存在127这个数了,就直接取出来赋值给a2,所以a1 == a2的。当超过范围就是new Integer()来new一个对象了,所以a、b都是new Integer(128)出来的变量,所以它们不等。
7.Optional
从 Java 8 引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException)。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
API
public class Java8Tester {
public static void main(String args[]){
Java8Tester java8Tester = new Java8Tester();
Integer value1 = null;
Integer value2 = new Integer(10);
// Optional.ofNullable - 允许传递为 null 参数
Optional a = Optional.ofNullable(value1);
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional a, Optional b){
// Optional.isPresent - 判断值是否存在
System.out.println("第一个参数值存在: " + a.isPresent());
System.out.println("第二个参数值存在: " + b.isPresent());
// Optional.orElse - 如果值存在,返回它,否则返回默认值
Integer value1 = a.orElse(new Integer(0));
//Optional.get - 获取值,值需要存在
Integer value2 = b.get();
return value1 + value2;
}
}
结果
8.Future
9.LocalThread
10.Time