cycJava基础以及集合总结

Java基础

1、Integer缓存池

  • Integer缓存池范围:-128-127 1个字节
  • Integer.valueOf()会使用缓存池中的对象,多次调用会获得同一个对象的引用。
  • 编译器会在自动装箱的过程调用valueOf()方法,因此在缓存值范围内的使用自动装箱来创建的话,会引用相同的对象。
Integer m = 123;
Integer m = 123;
System.out.println(m==n);  //true

2、字符串常量池

  • 字符串常量池保存着所有的字符串字面量,这些字面量在编译的时候就确定了。
  • String的intern()方法在运行的时候将字符串添加到字符串常量池中。
  • 当一个字符串调用intern()方法的时候,如果String pool中已经存在了一个字符串和该字符串的值相等,那么就会返回String pool中字符串的引用,否则就会在String pool中添加一个新的字符串,并返回新的字符串的引用。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1==s2);  //false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3==s4);  //true

Java1.7之前String pool被放在运行时常亮池中,属于永久代(方法区中),在Java7,String Pool被移到了对堆里面,这是因为永久代的空间有限,在大量使用字符串的时候会出现OOM错误。

直接反编译String s = “abc”;

public class Test
{
    public static void main(String[] args) 
    {
        String s1 = "abc";
    }
}

在这里插入图片描述

new String(“abc”)会创建几个对象?

博客讲解

一个或者两个(主要就是看常量池中的字面量创建还是不创建)。

  • new会创建一个字符串对象。
  • 还有一个就是常亮池中创建还是不创建这个字面量。
public class Test
{
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");

    }
}

在这里插入图片描述ldc的含义是:ldc系列命令负责把数值常量或String常量值从常量池中推送至栈顶。从上图中,我们可以看到第0行和第7行中的字符串引用是同一个,这说明了,在编译期间,该字符串变量的值已经确定了下来,并且将该字符串值缓存在缓冲区中,同时让该变量指向该字符串值,后面如果有使用相同的字符串值,则继续指向同一个字符串值。

  • JVM的new指令会创建一个对象并把引用入栈。
  • JVM的dup指令会复制栈顶数值,并且复制值进栈
  • JVM的ldc指令会命令负责把数值常量或String常量值从常量池中推送至栈顶。

第9行调用String的String(String str)实例构造器,执行new String(字面量),需要使用操作数栈的最上面的两个元素(new指令得到的一个引用和ldc指令放入的数值常量),并把new String()的引用放到操作数栈中。

String ss = “abc”+“bcd”;会创建几个对象

public class Test
{
    public static void main(String[] args)
    {
        String ss = "abc"+"bcd";
    }
}

在这里插入图片描述
从代码显示就创建了一个对象

String ss = s1+s2;创建了几个对象?


public class Test
{
    public static void main(String[] args)
    {
        String s1 = "abc";
        String s2 = "bcd";
        String ss = s1+s2;
    }
}

在这里插入图片描述创建了三个对象,分别是在常量池中创建了“abc”和“bcd”,还有一个是StringBuilder对象,两个String对象相加底层是调用StringBuilder来实现的。

3、参数传递

Java参数传递是以值传递的方式。

class Student
{
    String name;
    int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class Test
{
    public static void changeStudent(Student student)
    {
        System.out.println(student);
        student = new Student();
        System.out.println(student);

    }
    public static void main(String[] args)
    {

        Student student = new Student();
        System.out.println(student); 
        System.out.println("changeStudent---------");
        changeStudent(student);
        System.out.println("changeStudent---------");
        System.out.println(student);

    }
}

输出

Student@61bbe9ba
changeStudent---------
Student@61bbe9ba
Student@610455d6
changeStudent---------
Student@61bbe9ba

从输出可以看出,前后都是Student@61bbe9ba,没变,所以是传值。

本质是将地址以值的形式传递,因此在方法中使指针引用其他对象,那么这两个指针此时其他是完全不同的对象,一个改变对另外一个没影响。

4、向下转型(精度会降低。)

Java不能隐式向下转型,但是可以通过++和+=实现。

比如下面的例子就是错误的:

float f = 1.1; //错误
float f1 = 1.1f; //正确
short s1 = 1; //正确
s1 = s1 + 1; //错误
s1 += 1;  //正确
s1++;  //正确

5、switch(不支持long,Java7开始支持String)

支持byte、short、char、int、String、enum以及前面四个基本类型的包装类。

在这里插入图片描述

6、修饰符

访问修饰符本类本包子类其他
publictruetruetruetrue
protectedtruetruetruefalse
默认truetruefalsefalse
privatetruefalsefalsefalse

7、抽象类和接口

抽象类和普通类的最大的区别是:
抽象类不可以被实例化。

接口和抽象类区别:

  • 接对于方法而言:接口的成员(字段和方法)必须是public的,不允许定义为private和protected,而抽象类的成员可以有多种访问限制。
  • 对于字段而言:接口的字段默认是static和final的,抽象类的字段没有这种限制。
  • 一个类可以实现多个接口,但是只能继承一个抽象类。

抽象类可以在几个相关的类中共享代码。而接口可以使得实现类都实现接口的方法。

8、重写和重载

为了满足里式替换原则。重写有三个限制:

  • 1、子类方法的访问权限要大于等于父类
  • 2、子类方法的返回值必须是父类方法返回类型或者是其子类
  • 3、子类方法抛出的异常必须是父类抛出异常类型或者是其子类型。

重载:

  • 重载不能参数类型相同,返回值不同
  • 重载指的是一个方法与已经存在的方法函数名称相同,但是参数类型、个数、顺序至少有一个不同。

9、hashCode()和equals()方法

equals()相同的话,hashCode()必须相同。
因为如果不等的话,可能会破坏JDK一些类的特性,比如因为散列值不同,两个equals的对象可以放进Set中,使得Set会添加两个等价的对象,从而破坏Set的特性。

10、深拷贝和浅拷贝

首先做出回答:因为如果不继承自Cloneable接口,当调用clone()时会抛出CloneNotSupportedException异常,其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中clone()方法的。

浅拷贝

拷贝对象和原始对象引用同一个对象。

深拷贝

拷贝对象和原始对象引用不同对象。

11、反射讲解

Java 中的反射首先是能够获取到 Java 中要反射类的字节码,获取字节码有三种方法:

  • 1.Class.forName(className)
  • 2.类名.class
  • 3.this.getClass()。

然后将字节码中的方法,变量,构造函数等映射成相应的 Method、Filed、Constructor 等类,这些类提供了丰富的方法可以被我们所使用。

/**
 * 取得全部普通方法:
 *public Method[] getMethods() throws SecurityException
 * 取得指定普通方法:
 *public Method getMethod(String name, Class<?>... parameterTypes)
 * 以上两个方法范辉的类型是java.lang.reflect.Method类的对象,在此类中提供有一个调用方法的支持:
 * 调用:
 *public Object invoke(Object obj, Object... args)throws
 * IllegalAccessException, IllegalArgumentException,InvocationTargetException
 */

import java.lang.reflect.Method;
class Person
{
    private String name;
    private int age;
    public Person(){}
    public Person(String name,int age)
    {
        this.age = age;
        this.name = name;
    }
    @Override
    public String toString()
    {
        return "name is:"+this.name+" "+"age is:"+this.age;
    }
    public String getName() 
    {
        return name;
    }

    public void setName(String name) 
    {
        this.name = name;
    }

    public int getAge() 
    {
        return age;
    }

    public void setAge(int age) 
    {
        this.age = age;
    }

}

public class ClassTest 
{
    public static void main(String[] args) throws Exception 
    {
        Class<?> classes = Person.class;// 任何时候调用类中的普通方法都必须有实例化对象
        Method[] GetMethods = classes.getMethods();

        Object object = classes.newInstance(); // 取得setName这个方法的实例化对象,设置方法名称与参数类型
        // 随后需要通过Method类对象调用指定的方法,调用方法需要有实例化对象
        // 同时传入参数
        Method method = classes.getMethod("setName", String.class);
        method.invoke(object,"Regina Spektor");// 相当于Person对象.setName("Regina Spektor") ;
        Method method1 = classes.getMethod("getName");
        Object result = method1.invoke(object);//相当于Person对象.getName()
        System.out.println("My favourite singer is "+result);
    }
}


12、静态方法可以是抽象的吗?

不可以,静态方法在类加载的时候就存在了,它不依赖于任何实例,所以静态方法必须有实现。
方法中不可以有this和super关键字。

13、静态代码块块


```java
public class A 
{
    static 
    {
		System.out.println("123"); 
	}
    public static void main(String[] args) 
    {
        A a1 = new A();
        A a2 = new A();
    }
}

输出:
123

静态语句块在类初始化时运行一次。

14、静态内部类

非静态内部类依赖于外部类的实例,而静态内部类不需要。

public class OuterClass 
{
    class InnerClass 
    {
    }
    static class StaticInnerClass 
    {
    }
	public static void main(String[] args) 
	{
		// InnerClass innerClass = new InnerClass(); 
		// 'OuterClass.this' cannot be referenced from a static context
		OuterClass outerClass = new OuterClass();
		InnerClass innerClass = outerClass.new InnerClass(); 	
		StaticInnerClass staticInnerClass = new StaticInnerClass();
	} 
}

15、Throwable

Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:

  • 受检异常 :需要用 try…catch… 语句捕获并进行处理或者throws给调用者,并且可以从异常中恢复;一般是除了一般是RuntimeException及其子类之外的类。
  • 非受检异常 :是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复,一般是RuntimeException及其子类。
    在这里插入图片描述# 容器
    容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射 表。

Java集合

Collection(继承了Iterator)

Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。

Set、List、Queue三个子接口
在这里插入图片描述

Queue

  • LinkedList:可以用它来实现双向队列。
  • PriorityQueue:基于堆结构实现,可以用它来实现优先队列。

Set

  • TreeSet:基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)
  • HashSet:基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说 使用 Iterator 遍历 HashSet 得到的结果是不确定的。
  • LinkedHashSet:具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。

List

  • ArrayList:基于动态数组实现,支持随机访问。
  • Vector:和 ArrayList 类似,但它是线程安全的。
  • LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList还可以用作栈、队列和双向队列。

List相关源码

ArrayList源码

  • 默认大小为10。扩容为1.5倍扩容即oldCapacity+(oldCapacity>>1),也就是旧容量的1.5倍。
  • 通过Arrays.copyOf()把原数组整个复制到新数组中。
Fail-Fast快速失败

modCount用来记录ArrayList结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组大小,仅仅设置元素的值不算结构发生变化。

在进行序列化或者迭代等操作的时候,需要比较操作前后modCount是否改变,如果改变了需要抛出ConcurrentModificationException。

Vector源码

  • 初始大小10,扩容2倍
  • 方法同步

Collection.synchronizedList();实现现场安全的List

List list = new ArrayList();
List synList = Collections.synchronziedList(list);

CopyOnWriteArrayList(适合读多写少)

  • 写操作在一个复制的数组上进行,读操作还是在原始数组上进行,这样实现读写分离,互不影响。
  • 写操作时候会加锁,使用ReentrantLock加锁。
  • 写操作的时候允许读操作,大大提高了读操作的性能,很适合读多写少的场景。
缺点
  • 内存占用:写操作的时候要复制一个新的数组,使得内存占用为原来数组两倍左右。
  • 数据不一致:读操作不能读取到实时的数据,因为部分写操作的数据还未同步到读数组中。

Map

在这里插入图片描述

  • TreeMap:基于红黑树实现。
  • HashMap
  • HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并 且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安 全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
  • LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序
    顺序由accessOrder决定,当为true的时候,表示LRU顺序,当一个节点被访问的时候,会将该节点移动到链表尾部,从而包装尾部是最近访问的节点,那么首部就是最近最久未使用的节点。

Map相关源码

HashMap

1.7的HashMap(Entry数组+链表)

底层上一个Entry数组即Entry<K,V>[] table。Entry是一个节点。Entry节点中包含key、value、hashCode以及Entry next四个字段。

数组的每个位置被当成一个桶。

  • 使用数组和链表实现,使用拉链法解决冲突
  • 链表以头插法的方式插入数据
  • 大小必须是2的幂次方(因为2的幂次方减去1之后,后面都变成了1,再进行按位与和取模效果是一样的)。
  • HashMap可以放入key为null的键值对,但是无法调用null的hashCode(根据key来计算的),也就无法计算桶下标,所以HashMap使用桶0存放键为null的键值对。
  • 加载因子是0.75,根据泊松分布计算得来的。
  • resize在并发的时候可能会产生死锁。
  • 在resize的时候使用一种特殊的方式降低元素移动
    比如:原来大小为16,扩容之后为32。他的哈希值如果在第5位上为0,则按位与之后结果和以前一样,如果为1,则得到的结果会加16。
1.8的HashMap
  • 使用数组+链表+红黑树的数据结果。
  • 从1.8开始,在一个桶存储的链表长度大于8的时候会将链表转换为红黑树。
  • 插入使用的是尾插法。

1.7的ConcurrentHashMap

里面维护一个Segment数组。Segment节点里面有HashEntry数组。

static final class HashEntry<K,V>
{
	final int hash;
	final K key;
	volatile V value;
	volatile HashEntry<K,V> next;
}

Segment数据结构

static final class Segment extends ReentrantLock implements Serualizable
{
	private static final long serialVersionUID = 2249069246763182397L;
	static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors()>1 ? 64:1;
	transient volatile HashEntry<K,V>[] table;
	transient int count;
	transient int modCount;
	transient int threshold;
	final float loadFactor;
}
  • 使用分段锁Segment,每个分段锁维护几个桶,多个线程可以同时访问不同分段锁上的桶,从而使得并发度更高(并发度就是Segment的个数),Segment继承自ReentrantLock
  • 默认的并发级别是16即static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  • Segment维护一个count变量来统计Segment中键值对的个数。在计算size的时候要遍历所有的Segment把count加起来。
  • ConcurrentHashMap在执行size的时候先尝试不加锁,如果连续两次不加锁操作得到的结果是一致的,那么这个结果就是正确的即RETRIES_BEFORE_LOCK为2,retries初始值为-1,因此尝试次数为3,在三次中如果连续两次得到结果一致,那么认为结果正确,否则加锁。

1.8的ConcurrentHashMap

  • 1.7并发度和Segment数量相等。JDK1.8使用CAS来支持更高的并发度
  • 在cas操作失败的时候,使用内置锁synchronized。
  • JDK1.8的实现也在链表过长的时候转换为红黑树。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值