Java基本数据结构
上篇文章我们基本介绍了Java相关的发展过程,那么,我们使用Java就必须了解其具体的语法和基本的数据结构以及相应的关键字
基本语法是所有编程语言比较通用的,这边不过多的介绍,那么我们主要从数据结构以及Java中常用的关键字来举例,顺便提出一些遇见过的面试题
基本数据结构
一. 封装类型和基本数据类型
Java基本数据类型.png针对基本数据类型(primitive type),Java支持自动拆箱和自动装箱
- 装箱:Java自动将原始类型值转换成对应的对象,比如,将int变量转换成Integer对象
-
拆箱:反之,将对象类型转换成对应的原始类型,Integer对象转换成int类型值
Java的装箱拆箱.png
自动装箱的弊端
- 谨防多余变量,导致内存消耗
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
## 这里会发生自动拆箱和装箱
int result = sum.intValue() + i;
Integer sum = new Integer(result);
- 精度丢失问题
为什么要使用装箱和拆箱?既然会出现上述弊端,为何不直接使用基本数据类型就好?
- 对象引用:基本类型不是对象,无法使用对象引用,而包装类型是对象,可以通过引用来操作。
- 泛型:泛型不能使用基本数据类型,必须使用包装类型。如,使用List<Integer>来存储一组整数,而不是使用List<int>。
- null值:基本类型不能为null,而包装类型可以为null。
- 类型转换:包装类型提供了许多类型转换方法,例如:将字符串转换为整数或浮点数等,这些方法非常方便。
- 方法重载:方法重载可以使用不同的参数类型,但是基本数据类型和其对应的包装类型在方法中被认为是相同的类型,因此可以在方法中使用它们进行重载。
这篇文章介绍的挺好 java的装箱与拆箱
缓存的对象
Java中对于小整数范围内的整数(-128到127),会进行缓存,因此同样的整数值在缓存范围内时,它们对应的包装类型对象是同一个。但是超过缓存范围的整数值,即使值相等,它们对应的包装类型对象也不相同。
Java整型缓存.png
Integer.valueOf(int i) 源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache 类源码
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
Java中另一个节省内存的例子就是 字符串常量池
自动拆箱、装箱过程中,会遇到 == 和 equals 两种比较大小,不管是int和Integer,还是int和Long,这里不做详细描述,可以手动实验
这篇博主提出的面试题跳转
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
System.out.println(g.equals(a + h));
结果
true
false
true
true
true
false
true
引用数据类型
二、String
String的考察,也是前期面试的高频题,弄懂String才能更高效的开发
什么是String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
}
首先,它是一个类,是引用数据类型,不是基本数据类型;
它是 final 修饰的,说明它是不可修改的;即一旦一个 String 对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
它的底层是通过 final char value[] char数组来保存数据的
但是,我们通常理解的String,应该都是可以修改的才对,其实,这里底层创建了新的String对象
String、StringBuilder、StringBuffer的区别
两个可变字符串类 StringBuffer 和 StringBuilder,中文翻译为“字符串缓冲区”
StringBuffer 是线程安全的,而 StringBuilder 则没有实现线程安全功能
其底层的原理创建对象的过程,还需要涉及JVM的知识,也就是创建对象的过程在堆内存是一个对象还是多个对象
String类,如果用赋值操作,会在堆内存生成新的对象,例外:字符串常量池;StringBuilder、StringBuffer是只有一个对象在堆内存
使用环境:
操作少量的数据使用 String
单线程操作大量数据使用 StringBuilder
多线程操作大量数据使用 StringBuffer
什么是字符串常量池?
这篇文章介绍的非常详细,文章归纳的话,在首次使用某个字符串字面量时,字符串字面量以真正的String对象的方式存放在 字符串常量池(String Pool) 中
Java字符串字面量是何时进入到字符串常量池中的
new String("Hello");到底创建了几个对象
类class引用
- Object:Object是一个很重要的类,Object是类层次结构的根类,每个类都使用Object作为超类,所有对象(包括数组)都实现这个类的方法。用Object可以定义所有的类
- String:String类代表字符串,Java 程序中的所有字符串字面值(如"abc")都作为此类的实例来实现。检查序列的单个字符、比较字符串、搜索字符串、提取子字符串、创建字符串副本、在该副本中、所有的字符都被转换为大写或小写形式。
- Date:Date表示特定的瞬间,精确到毫秒。Date的类一般现在都被Calendar 和GregorianCalendar所有代替
- Void:Void 类是一个不可实例化的占位符类,它保持一个对代表 Java 关键字 void 的 Class 对象的引用。
三、接口类型
Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
java接口和类有什么区别?
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
- 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
四、枚举类型
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠.。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
下面这篇文章介绍的比较详细
枚举类型的介绍
Enumset
EnumSet是用于枚举类型的专用Set实现。EnumSet中的所有元素必须来自单个枚举类型,该类型在创建集时显式或隐式指定。枚举集在内部表示为位向量,这种表现非常紧凑和高效。它不允许有空值,如果是试图插入空值,将会抛出NullPointerException异常,但是可以检测是否含有空值。通之前讲的其他集合一样,他也是非同步的。
EnumSet的迭代器方法返回的迭代器以其自然顺序(枚举类中枚举常量的顺序)遍历元素。返回的迭代器是弱一致的:它永远不会抛出ConcurrentModificationException,它可能会也可能不会显示迭代进行过程中对集合所做的任何修改的影响。
总结:
- EnumSet中的所有元素必须来自单个枚举类型
- EnumSet不允许有空值,但是可以检测是否含有空值
- EnumSet的迭代器方法返回的迭代器以枚举类中枚举常量的顺序遍历元素,且永远不会抛出ConcurrentModificationException
- EnumSet是非同步的
- EnumSet是一个抽象类,我们对他的操作实际是对它的两个实现类JumboEnumSet或者RegularEnumSet其中之一做的操作。具体判断为,如果枚举类中的枚举常量大于64个,则使用JumboEnumSet,反之使用RegularEnumSet
- EnumSet是使用位向量实现的,即使用一个位表示一个元素的状态
五、数组类型
数组的定义和初始化
int[] arr = new int{1,2,3,4,5};
int[] arr1 = {1,2,3,4,5};
int arr2[] = new int[10];
数组和链表的优缺点?