基础
Exception
- 当
try 或 catch 语句块中有 return 语句时,先执行 try 或 catch 语句块中 return 语句之前的代码,再执行 finally 语句块中的代码,之后再返回。所以,即使在 try 或 catch 语句块中有 return 语句,finally 语句块中的代码仍然会被执行。 - throw:手动抛出异常
- throws:将方法中的异常抛给调用该方法的对象
public class Test2 {
public static void main(String[] args) {
try {
System.out.println("111");
throw new NullPointerException("空指针异常");
}catch(ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
}
}
异常对象中传的参数是异常信息(e.getMessage())
-
自定义异常:当我们需要自定义一个异常时只需要将我们自定义的异常类去继承一个异常类就可以了 public class MyException extends RuntimeException{
public MyException(String msg) {
super(msg);
}
}
异常分为运行时异常和编译(检查)时异常,当一个异常类继承了RuntimeException这个类则说明这个异常类就是运行时异常,否则为编译时异常
finally
-
finally关键字内的内容不管是否发生异常都会被最终执行,如果在try之前发生异常或者在try块或者catch块中停止了jvm则不执行 -
当try、catch中存在return语句时,finally中的内容会在return语句执行之前执行,执行完了finally中的内容再return -
特殊情况:finally中存在return时,会返回finally的值 -
finally中的代码不被执行的情况:
- System.exit(); JVM虚拟机被终止
- 在try之前发生了异常
final
-
被final修饰的类不可以被继承 -
被final修饰的属性为常量 -
被final修饰的方法不可以被重写
上面我们说被final修饰的方法不可被重写,那看看下面的代码 public class Person {
private final void test() {
}
}
class Student extends Person{
private final void test() {
}
}
这里编译通过,运行也没出现异常,那这个是否是重写了方法呢? 因为在父类中的test()方法是用private关键字修饰的,所以当子类继承了父类,但是这个被private修饰的方法对子类并不可见,所以这并不是重写。
finalize
flush
- 将缓冲区中的内容刷新(强制写出),当调用close()方法时也会调用
static关键字
-
局部变量只能用final修饰 -
一个类中可以有多个static代码块 -
一个类中可以有多个代码块 -
当一个类中同时有代码块和static代码块,初始化这个类的一个对象时, 构造方法、代码块、static代码块、static修饰的成员变量赋值、普通成员变量赋值执行顺序: 如果static修饰的成员变量在static代码块前面则先执行static修饰的成员变量赋值,否则反之 如果普通的成员变量在代码块前面则先执行普通饰的成员变量赋值,否则反之 属性初始化->static修饰的成员变量赋值->static代码块->代码块->构造方法 public class TestStatic {
public int i=1;
{
System.out.println("普通成员变量赋值:i ==" +i);
i=10;
System.out.println("普通代码块赋值:i ==" +i);
}
public static int j=1;
static {
System.out.println("static成员变量赋值:j ==" +j);
j=10;
System.out.println("static代码块赋值:j ==" +j);
}
public TestStatic(){
System.out.println("构造方法");
}
public static void main(String[] args) {
new TestStatic();
}
}
执行结果:
static成员变量赋值:j ==1 static代码块赋值:j ==10 普通成员变量赋值:i ==1 普通代码块赋值:i ==10 构造方法
-
staitc代码块、方法中只能调用static修饰的方法和成员变量,不能使用super、this关键字 -
一个类中static修饰的方法可以被继承并通过子类的类名调用,但是不能被重写。
super关键字和this关键字
super
- super关键字指的是父类的对象
- 如果需要通过super关键字调用父类的构造方法只能在子类构造方法的第一行中调用
- 可以通过super关键字调用父类的属性和方法
- super关键字不能在static修饰的代码块或者方法中使用
- 如果我们不显式的使用super关键字调用父类的构造方法并且不使用this关键字调用构造方法,则编译器会给我们默认提供super()调用父类的无参构造
this
- this关键字是当前类的对象的引用
- 可以在构造方法中通过this关键字调用当前类中其他的构造方法,只需要填写与构造方法相同的参数列表的参数即可
- 可以使用this关键字区别同名的成员变量和局部变量
- this关键字不能在static修饰的方法或者静态代码块中使用
注意:
方法重载
- 重载方法:方法名一样,参数列表不同,和返回值、访问权限修饰符无关
- 参数列表不同:参数个数不同、参数类型不同、参数顺序不同
- 方法重载是方法的扩展
- 方法重载在同一类中发生
- 方法重载是静态绑定
方法重写
- 重写方法名要和被重写方法名一致
- 返回值与被重写方法的返回值一致,或者是被重写方法返回值的子类
- 重写方法不能缩小被重写方法的访问权限
- static修饰的方法不能被重写
- 被final关键字修饰的方法不能被重写
- 重写方法的参数列表和被重写方法的参数列表一致(参数类型是被重写方法参数类型的子类也不行)
- 方法重写发生在子类继承父类中,或者实现接口中
- 方法重写是对方法的覆盖
- 方法重写是动态绑定,也是多态
接口
-
JDK1.8之前接口中的方法全是抽象的,没有用public abstract修饰的方法编译器会自动帮我们在方法前加上,字段都为常量。 -
JDK1.8之后(包含1.8)可以有默认的方法和静态方法。 -
注意:1.8之后的静态方法只能通过接口名来调用,不能通过实现该接口的类名调用,接口的实现类没有接口中的静态方法,接口中的静态方法不能被别的接口继承 -
接口没有构造方法 -
实现了接口的类拥有接口中定义的常量 -
接口只能继承接口,并且能继承多个接口
抽象类
- 一个类中有抽象方法则这个类就是抽象类,一个抽象类中可以没有抽象方法,可以有普通方法。
- 抽象类有构造方法,但是不能被直接初始化。
- 抽象类中可以存在代码块(构造代码块、静态代码块)
- 抽象类中可以存在静态方法
- 抽象类中的属性和普通类的属性一样
继承
- 一个类通过extends关键字继承一个类,被继承的类称为基类或超类,子类称为衍生类。
- 当子类中有和父类相同的变量时,不会覆盖父类的。
- 父类中有静态方法/属性时,子类可以通过子类的类名调用父类中的静态方法/静态属性(接口中实现类不能通过实现类的类名调用接口中的静态方法)
- 一个类只能直接继承一个类,这是java的单继承,但是一个接口可以继承多个接口,所有的类的基类是Object类
- 当子类继承父类并重写了父类的方法,那么当父类的引用调用被重写的方法时会调用子类的方法。
- 当子类中有和父类相同名字的属性,当父类的引用调用同名的属性时,调用的是父类的,因为属性不会被覆盖。
- 子类的构造方法如果没有显式的使用super关键字调用父类的构造方法和使用this关键字调用其他的构造方法则编译器会默认的提供super()用于调用父类的无参构造方法,否则默认器不会提供。
匿名内部类
静态内部类
-
静态内部类的定义格式 public class OutClass {
public static String name;
public static class InnerClass{
public String name;
public void show(String name) {
System.out.println("参数name:"+name);
System.out.println("内部类name"+this.name);
System.out.println("外部类name:"+OutClass.name);
}
}
}
-
创建静态内部类的方式 InnerClass ic=new OutClass.InnerClass();
-
静态内部类中不能调用外部类中非静态的方法和属性 -
静态内部类中调用外部类中静态的方法或属性的格式 : 外部类名.方法名/属性名 -
静态内部类的创建无需依赖外部类对象(所以创建内部类对象时不需要创建外部类对象)
成员内部类
-
成员内部类的定义格式 public class OutClass {
public String name;
public class InnerClass {
public String name;
public void show(String name) {
System.out.println("方法的name:"+name);
System.out.println("内部类的成员name:"+this.name);
System.out.println("外部类成员name:"+OutClass.this.name);
}
}
}
-
成员内部类可以调用外部类的所有属性和方法(包括static、final、private修饰) -
创建成员内部类的方式 OutClass.InnerClass ic=new OutClass().new InnerClass();
-
内部类中和外部类中存在同名的属性或方法时,可以通过外部类名.this.属性名/方法名调用外部类的属性或方法 -
在内部类中的this指的是内部类对象的引用
局部内部类
-
局部内部类定义格式 public class OutterClass {
public String name;
public void show() {
class InnerClass{
public String name;
public void test() {
System.out.println("外部类的name:"+OutterClass.this.name);
System.out.println("内部类的name:"+this.name);
}
}
InnerClass ic=new InnerClass();
ic.test();
}
public static void main(String[] args) {
OutterClass oc=new OutterClass();
oc.show();
}
}
-
局部内部类只能在定义局部内部类的方法中创建 -
局部内部类和局部变量相似,只能用final修饰局部内部类 -
局部内部类可以调用外部类中的所有属性和方法
集合
HashSet
-
无序(存放元素的元素和取出的顺序不同),不可以存放相同的元素(通过hashCode()方法和equals()确定元素是否相同(当位置确定了就取决于存储的对象的equals()是怎么实现的)) -
底层实现是HashMap(jdk8之前是哈希表+单链表,8包括8之后是哈希表+单链表+红黑树),所以特点和HashMap集合差不多,可以存放null,并且在哈希表的第一个位置 -
HashSet集合也不能在foreach中对使用集合的remove()方法对集合进行移除,会报并发修改异常,因为foreach实际上就是用的iterator来进行遍历的,iterator的next()方法会对modCount属性进行校验,和ArrayList集合原理一样,HashMap的迭代器是HashIterator类创建的对象 -
可以存放null -
通过无参构造创建集合对象时,容量为0,在第一次添加元素时扩容为16 -
每次扩容为原来的两倍 public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
Vector
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
? Vector集合的扩容方法:
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
可以看到它新的容量首先是根据int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);来确定。capacityIncrement 这个是我们在创建Vector时可以确定的。如果不传这个值就默认是0,所以每次扩容是原来的一倍。
ArrayList
-
底层:Object类型的数组 -
扩容机制:每次扩容为原来数组长度的1.5倍 -
ArrayList集合特点:可以存储重复元素,有序,存入进去的顺序和取出来的顺序一样,可以存储null -
如果使用默认的无参构造时,它存储元素的数组长度为0,在添加第一个元素时进行扩容,扩容后的容量(数组长度)为默认的初始容量10 -
ArrayList集合的contains()判断一个元素是否存在的依据是通equals()方法,是否存在就看传入的类型的equals方法是如何实现的 -
当通过传递int类型的构造方法创建集合对象时,如果传递的是0,那么在添加第一个对象时将会进行扩容,扩容后的容量是1
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
-
不能在foreach中进行使用ArrayList集合的remove()方法进行移除元素,通过iterator迭代器迭代遍历元素时可以移除元素,不能用增强for,因为用增强for的时候它会生成一个iterator迭代器,调用的是iterator的hasNext()、next()方法,在集合里面有一个Itr内部类,在ArrayList集合的父类中有一个变量modCount,它的作用是记录集合结构被修改的次数,在创建内部类对象的时候它会赋值给内部类的expectedModCount这个成员变量中,当调用集合的remove()方法时modCount会自增,当调用iterator对象的next()方法时它会检查初始化时会调用checkForComodification()检查存储的expectedModCount和当前的modCount是否相等,很显然是不等的,所以会抛出ConcurrentModificationException异常。
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
public Iterator<E> iterator() {
return new Itr();
}
当我们调用集合的iterator()方法时会创建一个内部类Itr对象返回。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
return oldValue;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;
}
集合的remove()方法,不管调用哪个都会改变modCount的值
但是如果通过iterator对象的remove()方法来移除元素时,它实质上调用的也是集合的remove()方法,但是它移除完了之后会更新Itr对象的expectedModCount的值,所以就保证了expectedModCount和最新的modCount相等,所以就不会抛出异常
LinkedList
- LinkedList底层实现是双向链表
- 特点:增删快,空间理论上无限,可以存储重复的元素
- LinkedList集合的方法和ArrayList集合是差不多的,但是底层没有索引,之所以能够通过get(int index)去获取指定位置的值是因为LinkedList是连续的
- add(E e)采用的是尾插法
- LinkedList可以存储null
HashMap
-
HashMap的底层实现是:哈希表+单链表+红黑树(jdk8包括8之后,在8之前是哈希表+单链表) -
当通过HashMap的无参构造创建map集合时,它的数组为空,当我们进行第一次put元素时,会进行扩容,容量变为16,往后如果再进行扩容,每次扩容的大小为原来容量大小的两倍 -
HashMap中的键和值允许为null,HashTable中则不允许,当put()的键值对中的键已经存在,则会将原来的值进行覆盖 -
HashMap集合不能在foreach中通过keySet进行遍历使用集合的remove()方法对集合元素进行移除,会报并发修改异常,原因和ArrayList集合一样 -
HashMap每次扩容的要求是table数组的元素数量大于阈值,threshold(阈值)属性是用于记录下一次扩容时数组中元素个数 -
增加元素:扩容机制和转红黑树条件,当链表长度大于8时才调用转红黑树的方法,但是调用了转红黑数的方法中会先判断节点数组长度(容量)是否大于64,不大于则进行扩容,大于则进行转红黑树 -
HashMap的添加元素方法putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
-
HashMap的底层实现JDK1.8是一个 可变的哈希表+链表或者 数组+红黑树,JDK1.8前是数组+链表 ,哈希表是一个 Node<K,V>[]数组,Node<K,V>这个数组在创建集合对象初始时为null,在添加第一个元素时会进行扩容,将扩容后的数组重新赋值给table,这个table的容量为初始的16
transient Node<K,V>[] table;
-
remove()方法 final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0)
return;
int index = (n - 1) & hash;
TreeNode<K,V> first = (TreeNode<K,V>)tab[index], root = first, rl;
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
if (pred == null)
tab[index] = first = succ;
else
pred.next = succ;
if (succ != null)
succ.prev = pred;
if (first == null)
return;
if (root.parent != null)
root = root.root();
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map);
return;
}
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) {
TreeNode<K,V> s = pr, sl;
while ((sl = s.left) != null)
s = sl;
boolean c = s.red; s.red = p.red; p.red = c;
TreeNode<K,V> sr = s.right;
TreeNode<K,V> pp = p.parent;
if (s == pr) {
p.parent = s;
s.right = p;
}
else {
TreeNode<K,V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
if ((p.right = sr) != null)
sr.parent = p;
if ((s.left = pl) != null)
pl.parent = s;
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
replacement = p;
if (replacement != p) {
TreeNode<K,V> pp = replacement.parent = p.parent;
if (pp == null)
root = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
p.left = p.right = p.parent = null;
}
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) {
TreeNode<K,V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
if (movable)
moveRootToFront(tab, r);
}
-
HashMap集合的扩容方法 final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}else if (oldThr > 0)
newCap = oldThr;
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
-
由单链表转红黑树方法(实际上还没转成红黑树,只是把单链表的节点转成了红黑树的节点)
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root);
}
-
HashMap的toString()是调用的AbstractMap的toString(),实际上调用的是迭代器方法 public String toString() {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (! i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(',').append(' ');
}
}
Hashtable
- 底层是一个哈希表
- 默认初始容量是11(一创建对象就有容量了,不需要在第一次添加时扩容才有),如果通过传递容量的构造方法传递一个0创建对象则容量是1,默认加载因子是0.75
- 线程安全的集合
- key和value都不能存放null
- 如果存放的key已经存在,则会将对应的value进行覆盖
- 每次扩容为原来的两倍+1
TreeSet
-
TreeSet的底层实现是TreeMap集合(红黑树) -
TreeSet不可以存放相同元素(是否相同取决于比较方法如何实现,Comparable接口的compareTo(),Comparator的compara()) -
TreeSet存取顺序是无序的,不可以存放null -
在存放自定义的对象时,存放的对象的类需要实现Comparable接口或者是在创建TreeSet集合时传递一个Comparator接口的实现类对象 -
TreeSet存进去的元素在内部是有序的 -
在TreeSet中,判断一个对象是否存在集合中是通过比较器或者自然排序的排序方法进行比较,如果比较规则中使用到了equals()则关注点在equals() final Entry<K,V> getEntry(Object key) {
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
final Entry<K,V> getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}
-
当TreeSet集合同时实现自然排序和比较器时,比较器会优先于自然排序(当两个比较器共存时会优先使用Comparator比较器) -
当TreeSet调用了add(E e)实际上是调用的是TreeMap的put(K key, V value)方法
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key);
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
TreeMap
- 底层是红黑树
- key不能为null但是value可以为null
- 当key是自定义对象时,自定义对象需要实现Comparable接口或者在创建TreeMap集合对象时传递一个比较器
- 当存储的key在自然排序或者比较器的排序方法中返回0时则会替换对应key的value值
- 当TreeSet集合同时实现自然排序和比较器时,比较器会优先于自然排序
Queue
-
Queue是一个接口,我们一般使用他的实现类LinkedList集合 -
特点:先进先出、用完即删
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
Stack
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null;
}

Iterator迭代器
序列化
- 是一个接口,接口里面没有任何方法,接扣名:Serializable
- 序列化的作用:将一个对象的状态持久化保存,可以进行网络传输
- 序列化的目的就是为了反序列化
- 反序列化:将一个对象的状态进行还原(创建对象)
- 序列化方法:将需要序列化的类实现Serializable接口,通过ObjectOutputStream类的对象将需要序列化的对象写出
- 不能被序列化:
- private transient修饰的属性
- private static修饰的属性或者方法
- 实现该接口的类会有一个序列号,序列号用于在进行序列化和反序列化时进行加密解密
- 如果改变类的结构有可能会导致序列号发生改变,从而导致在反序列化时出现异常
- 解决办法:
- 让新添加的内容不被序列化
- 使用transient关键字
- 手动赋值序列号,使类的序列号和被序列化时的序列号一致
- transient关键字不能够修饰方法
- 如果子类继承了父类,父类实现了序列化接口,则子类不需要实现序列化接口也能被序列化
创建对象的方式
- 通过new关键字
- 克隆
- 需要实现Cloneable接口,这个接口中什么都没有,只是起标志作用(标志接口)
- 不会调用构造方法
- 反序列化
- 反射
单例实现方式
泛型
-
泛型只在编译时有效,当运行时JVM中是没有泛型这一说法的,这就是泛型的 可擦除机制 -
泛型是在编译时起一个约束作用,如: public class Test {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
}
}
上面定义的ArrayList集合中只能存放String类型的对象,如果放别的对象会编译不通过 -
在JDK1.7前泛型只能List list=new ArrayList();这样定义,但是在JDK1.8后有了类型推断,所以我们可以省略后面的泛型不写,也就是List list=new ArrayList<>(); -
如果我们在使用一个泛型类或者泛型接口时不去确定它的泛型类型,那么它默认是Object类型 -
泛型的优点:
- 类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
- 消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
- 潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
-
通配符的缺点 public static void fun(List<?> list) {…}
带有通配符的参数不能使用与泛型相关的方法,例如:list.add(“hello”)编译不通过。List<?> list参数中的通配符可以被赋任何值,但同时你也不知道通配符被赋了什么值。当你不知道“?”是什么时,会使你不能使用任何与泛型相关的方法。也就是说fun()方法的参数list不能再使用它的与泛型相关的方法了。例如:list.add(“hello”)是错误的,因为List类的add()方法的参数是T类型,而现在你不知道T是什么类型,你怎么去添加String的东西给list呢?如果使用者在调用fun()方法时传递的不是List< String>,而是List< Integer>时,你添加String当然是不可以的。当然,还可以调用list的get()方法。就算你不知道“?”是什么类型,但它肯定是Object类型的。所以你可以:Object o = list.get(0); -
泛型上限:例如List<? super String> 表示List集合中只能放类型为String或者String父类的对象 -
泛型下限:例如List<? extends String>表示List集合中只能放类型为Sring或者String子类的对象 -
泛型通配符:?
- 方法参数带有通配符会更加通用;
- 带有通配符类型的对象,被限制了与泛型相关方法的使用;
- 下边界通配符:可以使用返回值为泛型变量的方法;
- 上边界通配符:可以使用参数为泛型变量的方法。
-
泛型类、接口的使用 当我们定义了一个泛型的接口或者泛型的类,如果有一个子类/接口去继承它,这个类/接口需不需要去确定这个父类的泛型呢? public interface Animal <E>{
}
public interface Person<E> extends Animal<E>{
}
public class Student <E> implements Person<E>{
}
public class Student implements Person<String>{
}
当一个接口继承一个接口时,我们可以让这个父接口继续使用泛型,也可以确定这个父接口的类型。 当一个类实现一个泛型接口,我们确定接口的泛型,也可以继续使用泛型,还可以让我们这个类使用泛型 -
泛型可以用在接口、方法、属性、类上
public interface Person <E>{
}
public class Student <E>{
}
public <E> void test() {
}
public E name;
需要注意的是: public class Student <E>{
public <E> void test(E name) {
System.out.println(name);
}
public void t(E name) {
System.out.println(name);
}
}
public class Test {
public static void main(String[] args) {
Student<String> s = new Student<>();
s.test(123);
s.t(123);
}
}
上面这两个方法看似都是泛型方法,其实不是,只有test()方法才是泛型方法,泛型方法的定义是在访问权限修饰符和返回值之间需要定义一个泛型,而t()方法只是用了这个类的泛型的一个普通方法 从我们的测试中就可以知道,t()方法的参数类型是和创建对象时确定的泛型类型一致,而test()方法是在调用方法时传的参数时确定该方法参数的类型,这就是两者的区别。
IO
File
- File类对象是文件和目录路径名的抽象表示形式
- 常用方法:
- mkdir()创建一个目录
- mkdirs()如果父级目录不存在也会去创建
- exists()判断目录或者文件是否存在
- createNewFile()当且仅当不存在具有此抽象路径名指定名称的文件时,创建一个指定名称的文件
InputStream
OutputStream
Reader
Writer
FileInputStream
- FileInputStream是文件字节输入流,操作的单位是字节(可以操作所有二进制文件),作用是从文件中读取数据到程序中
FileOutputStream
- FileOutputStream是文件字节输出流,操作的单位是字节(可以操作所有二进制文件),作用是将程序中的数据写出到文件中
- 如果通过构造方法传入的抽象路径中的文件不存在时,会自动创建一个对应名称的文件,但是如果目录不存在则会报异常
- 在构建一个FileOutputStream对象时可以通过构造方法传入一个boolean值去决定是否在创建一个FileOutputStream对象时清空抽象路径对应的文件中的内容,默认为false,如果为true则不清空
FileWriter
FileReader
BufferedOutputStream
- 默认缓冲区大小是8192个字节,最大为Integer.MAX_VALUE
- 当写出数据时,数据会先保存到缓冲区中,当缓冲区满了、调用close()关闭流、调用flush()刷新时会将缓冲区的数据写到目的源中
- 如果通过write(byte[] b,int off,int len)写出数据时,如果len大于缓冲区的长度则会直接通过字节流写出,而不经过缓冲区
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
BufferedInputStream
-
默认缓冲区大小是8192个字节,最大为Integer.MAX_VALUE -
count代表的是缓冲区中元素的个数(byte数组中元素的个数) -
pos代表下一次需要读的数据的在数组中的索引 -
buf是缓冲区中存放数据的数组 -
如果扩容后的容量大于marklimit的话,缓冲区每次扩容为原来的两倍 -
marklimit为最大提前读取量 -
markpos为标记的位置 -
缓冲字节输入流是先通过字节流的read()把数据读到缓冲区中,等到需要取数据的时候再从缓冲区中拿
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0;
else if (pos >= buffer.length)
if (markpos > 0) {
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1;
pos = 0;
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else {
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
BufferedWriter
- 带缓冲区的字符输出流
newLine() 写出一行分隔符(换行)- 默认缓冲区大小8192字节
BufferedReader
- 带缓冲区的字符输入流
readLine() 读取一行,如果没有更多内容,返回null- 默认缓冲区大小8192字节
InputStreamReader
-
输入转换流,是输入字节流转成输入字符流的桥梁 -
本质是字符流,可以通过转换流读取不同编码的文件 -
我们可以在创建InputStreamReader对象时在构造方法中传递一个编码格式,以我们设定的编码把数据读进来
public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
@Test
public void test(){
BufferReader br = new BufferReader(new InputStreamReader(new FileInputStream("test.txt"),"GBK"));
}
OutputStreamWriter
-
输出转换流,是输出字节流转成输出字符流的桥梁 -
本质是字符流,可以通过转换流写出不同编码的内容 -
当传入的OutputStream对象没有设置需要拼接,那么目的源如果已经存在并且里面有内容并且编码与我们需要写出去时设置的编码不同,那么当我们写出数据时,目的源的编码会变成我们设置的编码 @Test
public void test02(){
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\File\\a.txt"), "UTF-8"));
bw.write("呵呵呵呵");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(bw);
}
}
ObjectInputStream
- 对象输入流,用来读取对象的流,读取对象的操作为反序列化
ObjectOutputStream
- 对象输出流,用来写出对象的流,写出对象的操作为序列化
PrintStream
- 打印字节流
- 有自动刷新机制,自动调用flush()
- 只能输出,不能输入
- 原样打印内容,如调用print(97),打印到目的源上的内容就是97,而不是a
- System.out就是PrintStream的一个对象,可以通过System.setOut(PrintStream out)和System.setInt(InputStream in)来重定向输出和输入的目的源和目标源
- 案例:改变System.out的目的源和System.in的目标源
@Test
public void test01(){
PrintStream ps = null;
try {
ps = new PrintStream("IO1.txt");
System.setOut(ps);
System.out.print("hello,world");
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (ps != null){
ps.close();
}
}
}
@Test
public void test02(){
FileInputStream fis = null;
try {
fis = new FileInputStream("IO1.txt");
System.setIn(fis);
Scanner sc = new Scanner(System.in);
System.out.println(sc.nextLine());
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
PrintWriter
ByteArrayInputStream
- 内存输入流(字节流)
- 内存->程序
- 特点:操作数据量小,效率高
- 无法关闭
ByteArrayOutputStream
- 内存输出流(字节流)
- 程序->内存
- 特点:操作数据量小,效率高
- 无法关闭
- 默认为32个字节
- 最大Integer.MAX_VALUE-8
RandomAccessFile
- 随机访问流(字节流)
- 特点:
- 当目标源文件中存在内容时会从偏移量位置开始进行覆盖原有内容
- 有指针,可以根据指针的指向开始读或者写
- 实例化对象时,偏移量(指针)为0
- 可以根据实例化对象时通过构造方法传递的参数去确定这个对象是否能对文件进行读写操作
- 案例:断点续传
- 当进行传输文件时,如果没传输完时程序终止了,再次启动程序时可以接着上次传输的位置继续传输文件内容(因为有指针)
public class Test{
public static void main(String[] args){
RandomAccessFile r = null;
RandomAccessFile rw = null;
try {
r = new RandomAccessFile("E:\\File\\1.mp4", "r");
rw = new RandomAccessFile("E:\\File\\2.mp4", "rw");
r.seek(rw.length());
rw.seek(rw.length());
byte[] bs = new byte[10];
int len;
while((len = r.read(bs)) != -1){
rw.write(bs,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(r,rw);
}
}
}
public class IOUtils {
public static void closeAll(Closeable ...closeables){
for (Closeable closeable : closeables) {
if (closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总结

多线程
- 进程:操作系统资源基本分配的基本单位
- 线程:处理器任务调度和执行的基本单位
实现多线程的方式
- 自定义线程类继承Thread类,重写run()方法,实例化自定义线程类对象并通过start()启动线程,使线程进入就绪状态,等待cpu调度
- 自定义任务类实现Runnable接口,重写run()方法,实例化任务类对象,实例化Thread对象,通过Thread的构造方法传递任务类对象,通过Thread类对象调用start()启动线程,使线程进入就绪状态,等待cpu调度
- 自定义任务类实现Callable接口,重写call()方法
- 通过线程池启动线程
线程的状态

同步锁(synchronized )
包装类
Integer
-
在底层实现中,Integer类中存在一个IntegerCache静态内部类,这个内部类中存在一个类型为Integer类型的静态的数组,这个数组里存放着我们常用的一些数字,最小固定为-128,最大为Integer.MAX_VALUE -129,这个最大值可以在JVM虚拟机的设置中设定,默认是127 private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
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);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
-
在自动装箱时调用的是valueOf(int i)方法进行自动装箱,而这个方法会对需要转换的int数字进行一个范围判断,判断这个数字是否在缓冲数组中,如果在则直接返回数组中对应的Integer对象,否则需要重新new。 public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
-
当一个Integer对象和一个int类型的数字通过==进行比较时,比较的是他们的值,因为Integer对象会通过intValue()方法进行拆箱成int类型的数字再进行比较,可以通过反编译下面这个文件的生成的字节码论证 public class Demo06 {
public static void main(String[] args) {
Integer integer = new Integer(39);
System.out.println(integer == 39);
}
}
String
-
String是不可变的,因为String的底层是一个被final修饰字符数组(jdk9之前,9包括9之后是一个byte数组)
案例分析
public class Test{
public static void main(String args[]) {
String s1 = "ab";
String s2 = "abc";
String s3 = s1 + "c";
System.out.println(s3.getClass());
System.out.println(s3 == s2);
}
}
当字符串与变量进行拼接时,实际上是创建了一个StringBuilder对象,并使用了append(String)方法进行了拼接,最终调用StringBuilder的toString()返回一个字符串对象,所以变量和字符串拼接的结果不会被存储进常量池中(可以通过反编译进行校验),而是在堆内存中
而当字符串和字符串进行拼接时,实际上他们在编译器就已经合并成了一个字符串(可以通过反编译进行校验)
-
String是被final修饰的类 -
String的equals()是先判断是否为同一个对象,如果是直接返回true,再判断是不是String类型的对象,如果不是直接返回false,如果是则判断他们的值是否相同。 public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
StringBuffer
- StringBuffer的底层是一个字符数组(jdk9之前,9包括9之后是一个byte数组),StringBuffer是一个线程安全的可变字符序列,但是因为线程安全所以效率比线程不安全的StringBuilder要低
- 被final修饰,是一个最终类,不可以被继承,也就是没有子类
- StringBuffer初始容量为16(也就是字符数组的长度),还有一个属性count用于记录字符数组中存放了几个字符,不等同于容量
- 每次扩容为原来容量的的两倍+2,最大容量为Integer.MAX_VALUE
- StringBuffer的toString()实际上是创建一个String对象
- 多线程下,如果字符串频繁拼接时可以用StringBuffer
StringBuilder
-
StringBuilder是一个字符数组(jdk9之前,9包括9之后是一个byte数组) -
被final修饰,是一个最终类,不可以被继承,也就是没有子类 -
StringBuilder是一个线程不安全的可变字符序列 -
StringBuiler初始容量是16,还有一个属性count用于记录字符数组中存放了几个字符,不等同于容量 -
每次扩容为原来容量的的两倍+2,最大容量为Integer.MAX_VALUE -
StringBuffer的toString()实际上是创建一个String对象 -
单线程下,如果字符串频繁拼接时可以用StringBuilder
|