文章目录
1. 数组
—— 优先容器而不是数组,只有在证明用容器有性能问题时,才将程序重构转换成数组
效率最高的存储和随机访问对象引用序列
线性序列、长度固定
能够持有具体对象,而容器只是Object
只有一个 可读字段:length —— 数组中最高维数的值
已分配空间未赋值:
- 基本变量数组:系统自动赋值
- 对象数组: 元素为Null
泛型数组
1.不可实例化具有参数化类型的数组 - A[] a (不可实例化)
2.可以 非泛型的数组 可 强制向下转型成 有参数化类型的数组
3.任何具体类型的数组,都是 Object[] 数组 —— 运行是否报错主要看分配的是不是所属的类 ——(不看泛型,运行期无泛型)
class A<T> {}
A<integer>[] array1 = new A<Integer>[4] //编译错误
A<Integer>[] array2 = { new A<Integer>() } //编译错误
A[] array3 = new A[4] //编译通过,有警告
array1 = ( A<Integer>[] )arrays; //编译通过,有警告
Object[] object = array1;
object[0] = new Integer(5); //编译正常,因为Integer是Object, 运行时报错,因为分配的空间以及类型信息并不是Integer,而是类A
public <T> T[] createArray( int size ) {
return (T[])new Object[size]; //警告, 实质返回的是一个Object[]数组,假强制转型
return new T[size]; // 编译期报错,运行时生成的T信息已经完全擦除了
}
方法
- 初始化
int[] a = new int[5];
int[] a = {1,2};
int[] a = new int[] { 1, 2, 3 };
int[] a = {1, 2, 3, 4 } //动态的聚集初始化
- 多维数数组
int[][][] = new int[2][1][2]; //前面一个是维数,后面两个就是int[1][2]
//输出 [ [ [0,0] ], [ [0,0] ] ]
int[][][][] = new int[2][2][1][2]; //前面一个是维数,后面三个是每维数是int[2][1][2];
int[2][1][2] = new int[2][1][2];
a[0] = b[1][2]; //代码格式是不对的,表达大概意思
a[1] = b[1][2]; //同上
Arrays类的静态方法
用于对数组的处理
- equals、deepEqual: 比较数组
- binarySearch( 数组,查找的元素 ): 已经排序数组中查找元素 —— *有则返回对应元素的序号,无则返回( -数组长度) *—— 用Comparable接口进行比较,二分法进行查找(该方法只能用在已经排好序数组上,否则会忽略掉一半的元素)
先调用 Arrays.sort(数组)进行排序,才能调用这个方法,否则返回的是负数值
binarySearch( 数组, 查找的元素, 比较器 ) —— 用接口Comparator进行查找
Integer[] numbers = new Integer(10);
for(int i = 0; i < numbers.length; i++) {
numbers[i] = random.nextInt(100);
}
Arrays.sort(numbers); //从小到大 进行系统的比较器进行排序
while(true) {
int item = random.nextInt(100);
int index = Arrays.binarySearch(numbers, item); //该方法返回匹配项在数组中的序号
if( index >= 0 ) {
println( "index" + index + ": " + item );
break;
}
}
- hasCode: 产生数组散列码
- equals / deepEquals: 引用等 或者 长度等且每个对应序号的元素为true
例如:a[0].equals[ b[0] ]都需true
- sort( 数组 ): 排序数组 —— *前提: *数组的类型需实现 Comparable接口
sort( 数组,Comparator<数组类型> ): —— 用Comparator的接口进行排序
系统中有实现的Comparator比较器:
- Collections.reverseOrder() —— 自然排序的相反
- Collections.reverseOrder( Comparator接口 ) —— 参数排序的相反
- String.CASE_INSENSITIVE_ORDER —— 字段,对字母大小不敏感
//要使得Father[]数组能排序,则必须Father是实现 Comparable接口
class Father implements Comparable<Father> {
int i ;
Father(int i) { this.i = i; }
public String toString() { return String.valueOf( i ); }
public int compareTo( Father father ) {
if ( i < father.i ) {
return -1;
}else {
return i == father.i ? 0 : 1;
}
}
}
class FatherComparator implements Comparator<Father> {
public int compare(Father father1, Father father2 ) {
return father.i < father2.i ? 1 : (father1 == father2 ? 0 : -1 ) ;
}
}
Arrays.sort( a ); //用的是Comparable接口中的 ComparaTo 进行比较排序
Arrays.sort( a, new FatherComparator(); } // 用的是Comparator接口中的 compare 进行比较排序
- 输出输出Arrays.toString —— <一维> 、Arrays.deepTostring —— <多维>
int[] numbers = { 1, 2, 3, 4, 5 }
System.out.println( Arrays.toString(numbers) )
//output: [1,23,4,5]
int[][] numbers= { {1, 2}, {2, 3} };
System.out.println( Arrays.deepToString(numbers) );
//output:【 【1, 2】,【2,3】 】
- Arrays.fill( 数组引用,开始,结束,填充数值)
Integer[] numbers = new Integer[5];
Integer a = new Integer(5);
Arrays.fill( numbers, a ) //数组的各元素都是 a的引用
Arrays.fill( numbers, 0, 2, new Integer(6) ) //数组中的 numbers[0], numbers[1] 都是整形6;
-
Arrays.copyOf(数组,长度, 返回的数组类型信息(即反射)) —— 调用了下面的方法
- 返回 基于参数数组 的 新数组
-
源码
-
System.arraycopy( 被复制原数组a,复制开始位置b,被黏贴数组c,被黏贴开始位置d,黏贴的元素个数e )—— 数组d被修改了 —— 比for循环复制快 —— 不会自动拆/装包,故需同类型的数组 —— 浅拷贝
-
ArrayList实例.toArray( T[] a ):
- 如果size >= a.length 则返回 不包含a的列表数组
- 如果size < a.length 则返回 被修改元素的数组a( list的元素,null, a本身剩余元素 )
Integer[] a = {1,2,3};
Integer[] b = Arrays.copyOf(a, 10, Integer[].class); //数组长度为10,前三个有赋值
Integer[] c = new Integer[5];
System.arraycopy(a, 0, c, 1, a.length); // c的数组为: [ null, 1, 2, 3, null ]
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < 3; i++) { list.add( i+1 ); }
Integer[] a = {5};
Integer[] b = {5,6,7,8};
Integer[] one = list.toArray(a); // [ 0, 1, 2 ]
Integer[] two = list.toArray(b); // [ 0, 1, 2, null ]
创建测试数据
import java.lang.reflect.Array;
interface Generator<T> { //元素生成器接口
public <T> T next();
}
class ColleactionData<T> extends ArrayList<T> { //实现 ArrayList接口 的自定义列表
ColleactionData( Generator<T> generator, int length) { //构造器初始化列表用元素生成器来填充
for( int i = 0; i < length; i++) {
this.add( generator.next() );
}
}
}
class Generated { // 静态数组类 —— 用来生成已经初始化好的数组的
public static <T> T[] array( T[] t, Generator<T> generator) { //参数需要 已经初始化的数组、相对应的元素生成器
List<T> list = new CollectionData( generator, t.length );
return list.toArray(t);
}
// 这里的类型信息是用来生成 上面静态方法的 t[]数组
public static <T> T[] array( Class<T> type, int length, Generator<T> generator ) {
T[] t= Array.newInstance(type, length);
return array(t, generator);
}
}
2. 容器深入理解
虚线框:接口、实现部分接口方法的抽象类( 用来继承,而不必自己些接口的方法,到时继承类只需重写抽象方法就行 )—— 减少工作量
Collection - 接口方法 - AbstractCollection实现
- 可选操作: 表示这些方法不是为所有该接口实现类提供使用,而只是为某个实现类提供实现的
- toArray(): 把该容器的元素转成数组
抽象类 AbstractCollection 实现了该方法
对容器进行迭代,每个元素赋值给将要返回的新数组
源码 —— AbstractCollection
- retainAll(): 进行交集处理,对该容器本身进行操作,而不是另返回一个新的容器
这就有可能意味着对该容器元素进行 remove操作
返回:该容器是否有被修改
源码 —— 抽象类AbstractCollection
Collections - 静态方法,对容器的操作
- 很多种类型checkedList(列表容器,指定输入的类型信息): 返回一个容器视图,不过会对增加的元素进行类型检查 —— 相关还有checkedSet、checkedQueue等类型检查视图方法
- nCopies( 容器长度,单对象实例 ): 返回一个不可增删改的列表容器
Integer a = new Integer(5);
List<Integer> list = Collections.nCopies( 4, a ) // 固定长度为4的容器,且每个元素都是a的引用
//list.add(5) 直接抛出运行时异常
- fill( 列表, 对象实例 ): 填充列表相同的引用(即列表所有元素变成 对象实例),用 set()来进行填充替换操作
List<integer> a = new ArrayList<Integer>();
a.add(1);
a.ad(2); // a列表:【1,2 】
Collections.fil( a, new Integer(5) ); // a列表:【 5,5 】
- unmodifiableCollection( Collection a): 返回一个不可修改的容器对象
unmodifiableSet( Set )、unmodifiableList( List )、unmodifiableMap( Map )
- 应用:客户端用户用不可修改的这个“复制”表 - 单纯只能读取不可写,而代码程序员用真正可修改的表
- 实质是Collections里面的一个内部类,该类对象仍然保存的是a的引用
- 该类重载了所有有关对容器进行操作的方法,即抛出 该容器不支持该操作的异常( UnsupportedOperationException )
源码
5. 各种方法:
(1) min/max( Collction )、min/max( Collection, 比较器 ): 返回该容器中 元素最大、最小值
(2) indexOfSubList/lastIndexOfSubList( list源,list目标 ): 返回目标容器 在 源容器 中的位置
(3) reverse(List)、reverseOrder( 比较器(可选) ): 把list所有元素逆转排序、返回 自然排序(参数比较器)逆转的的比较器
(4) rotate( List, int ): 所有元素往后推int的距离,后面元素往前提
(5) Shuffle( LIst, Random(可选) ): 重新随机排列list的元素
(6) sort( List, 比较器(可选) ): 排序list的元素
(7) copy( List1, List2 ): 将list2 的元素 复制到 list1,如果list2的元素个数 > list1的元素个数,会抛出异常
(8) swap( List, int i, int j ): 交换i、j 位置的元素
(9) nCopies( int, T ): 返回一个不可改变的基于T对象的固定长度 List
(10) singleton(T)、singletonList(T)、singletonMap(K, V): 返回size为1不可更改的Set、List、Map容器
(10) disjoint( Colleciton1, Collection2 ): 判断两个容器是否有相同的元素,无则返回 true
(11) frequency( Collection, Object x): 返回 容器中与x相等 的 元素个数
(12) list( Enumeration ): 返回基于Enumeration( Iterator的前身)的ArrayList → 用来转换老代码
(13) enumeration( Collection ): 返回一个Enumeration(类似迭代器)
(14) replaceAll( List,T old, T new ): 将容器中的 old元素全改为 new元素
(15) 多种类型synchronizedCollection( Collection ): 返回线程安全的容器 → 返回的容器会同时同步到实参引用的容器
(16) binarySearch( List, 元素, 比较器(可选) ): 返回元素在排序好的list的位置,调用这个方法时需要 先调用sort()进行排序
binarySearch()调用 - 源码细节
没有传比较器进入
传入比较器进入
Map - 接口方法
- containsKey( k key ): 返回boolean - 判断map中的是否有key → 调用了equals()、hashCode()这两个函数得出是否包含该结论
自己定义新类时 用到Map时,需记住重载
自定义一个线性查询Map类
-
步骤:
- 继承 AbstractMap类 而少选择实现接口 Map - 必须重写 entrySet() 方法
- 实现1的方法 需重写一个 Map.Entry的实现类
class SlowMap<K, V> extends AbstractMap<K, V> {
private List<K> keys = new ArrayList<K>();
private List<V> values = new ArrayList<V>();
public V put(K key, V value) {
V oldValue = get(key);
boolean bl = keys.contains(key);
if(bl == false) {
keys.add(key);
values.add(value);
}else {
int index = keys.indexOf(key);
values.set(index, value);
}
return oldValue;
}
public V get(Object key) {
int index = keys.indexOf(key);
if(index == -1)
return null;
else {
return values.get(index);
}
}
private class DesignEntrySet extends AbstractSet<Map.Entry<K, V>> {
public int size() {
return keys.size();
}
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
int index = -1;
public boolean hasNext() {
if(index < size()-1)
return true;
else
return false;
}
public Map.Entry<K, V> next() {
++index;
return new DesignMapEntry<K,
V>(keys.get(index), values.get(index));
}
public void remove() {
keys.remove(index);
values.remove(index);
}
};
}
}
public Set<Map.Entry<K, V>> entrySet() { //必须实现这个方法
Set<Map.Entry<K, V>> es = new HashSet<Map.Entry<K,
V>>();
Iterator<K> kIt = keys.iterator();
Iterator<V> vIt = values.iterator();
while(kIt.hasNext()) {
MapEntry<K,V> me = new MapEntry<K,
V>(kIt.next(), vIt.next());
es.add(me);
}
return es; // 这里整一大段都是 建立了一个副表,而 entrySet() 官方意思是视图的作用
//单单这句就是 return new DesignEntrySet() 就是视图作用了
}
}
class MapEntry<K, V> implements Map.Entry<K, V> { // 实现Map.Entry接口
K key;
V value;
MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public int hashCode() {
int kCode;
int vCode;
if(key == null)
kCode = 0;
else
kCode = key.hashCode();
if(value == null)
vCode = 0;
else
vCode = value.hashCode();
return kCode ^ vCode;
// 更简单点 <u>reutrn</u> (key == null ? 0 : key.hashCode()
) ^ (value == null ? 0 : value.hashCode());
}
@SuppressWarnings("rawtypes")
public boolean equals(Object o) {
boolean isType = o instanceof MapEntry;
if( isType == false)
return false;
else {
MapEntry me = (MapEntry)o;
if(hashCode() == me.hashCode())
return true;
else
return false;
}
}
public V setValue(V value) {
throw new UnsupportedOperationException();
}
}
自定义简单的散列Map
散列:使得查询快速进行,将键保存在某处
HashMap设计思想:用数组进行保存相同hash值的list数组,每个键值有对应的hash值,用hash值确定对应的list值列表在数组中的位置 → {list1, list2, list3, list3, list4…}
-
关于Hash容器的相关概念:
- 容量: 表中的桶位数
- 尺寸: 该容器当前已经存储的元素个数
- 负载因子: 尺寸/容量 → 半满表0.5、HashMap0.75、负载轻冲突小( 利于插入、查找)、
- 容器当达到一定的负载因子时: 增加容量,并重新分布原先的元素位置 → 开销大
-
在进行重写iterator()时:
- 需要得出 第一个不是空桶的序号
- 需要得出 第N个元素所在桶位置之前的所有元素个数
- 需要得出 第N个元素所在桶的元素 以及 该桶之前的 总元素个数 → 即:( 步骤2 + 第N个元素所在桶的元素个数)
横向是hash值查询( 数量多,但是快 ),纵向是线性查询( 慢,但是数量少 )
class SimpleHashMap<K, V> extends AbstractMap<K, V> {
private static final int SIZE = 1000;
List<MapEntry>[] map = new List[SIZE];
public V put(K key, V value) {
int hCode = key.hashCode() % SIZE; //切记这一步非常的重要,要不然你的容器数组怎么都不会够用
if(map[hCode] == null) {
map[hCode] = new ArrayList<MapEntry>();
map[hCode].add(new MapEntry(key, value));
return null;
}else {
for(MapEntry me : map[hCode]) {
boolean isEqual = me.getKey().equals(key);
if(isEqual == true) {
V oldValue = me.getValue();
me.setValue(value);
return oldValue;
}
}
map[hCode].add(new MapEntry(key, value));
return null;
}
}
public V get(Object key) {
int hCode = key.hashCode() % SIZE;
if(map[hCode] == null) {
return null;
}else {
for(MapEntry me : map[hCode]) {
if(me.getKey().equals(key)) {
return me.getValue();
}
}
return null;
}
}
private class MapEntry implements Map.Entry<K, V> {
K key;
V value;
MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
public int hashCode() {
int keyNum = (key == null? 0 : key.hashCode());
int valueNum = (value == null? 0 : value.hashCode());
return keyNum ^ valueNum;
}
public boolean equals(Object o) {
boolean isType = o instanceof Map.Entry;
if(isType == false) {
return false;
}else {
MapEntry me = (MapEntry)o;
if(hashCode() == me.hashCode())
return true;
else
return false;
}
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = value;
this.value = value;
return oldValue;
}
}
public Set<Map.Entry<K, V>> entrySet() { //泛型参数都是:系统的接口或者系统抽象类,java不能够检测或者强制转换为你需要的返回参数。
return new EntrySet();
}
@SuppressWarnings("rawtypes")
private class EntrySet extends AbstractSet<Map.Entry<K, V>> { //继承的是泛型接口而不是子类
public int size() {
int size = 0;
for(List list : map) {
if(list != null)
size += list.size();
}
return size;
}
public Iterator<Map.Entry<K, V>> iterator() { // 这里也是泛型为 接口
return new Iterator<Map.Entry<K, V>>() {
int count = 0;
int[] listSize = new int[map.length];
{
int index = 0;
for(List list : map) {
if(list != null)
listSize[index++] = list.size();
else
listSize[index++] = 0;
}
}
public boolean hasNext() {
if( count < size())
return true;
else
return false;
}
public Map.Entry<K, V> next() {
count++;
if(count <= listSize[0]) {
return map[0].get(count - 1);
}
for(int i = 0, number = 0; i < number = 0; i < listSize.length - 1; i++) {
number += listSize[i];
int number2 = number + listSize[i+1];
if(count > number && count <= number2) {
//int number3 = number - listSize[i];
return map[i+1].get(count - number - 1);
}
}
return null;
}
};
}
}
}
3持有引用Reference
③必须依赖④工具,①②可选择性依赖④工具
-
Reference作用: 希望继续持有对象引用、同时能允许垃圾回收器释放它 → Reference对象 作为 程序员 与 对象 的媒介( 代理 )
-
对象可获得: 某个对象你知道其引用( 类似地址 )
- 对象可获得:垃圾回收器不能释放
- 对象不可获得:程序无法使用它,将其回收是安全的
String a = new String("fsd"); // a即是有 该字符串对象的引用,地址 即对象可获得
new String("aa"); // 而该字符串对象 无引用字段 即对象不可获得
强引用
定义: 直接通过new创建的对象所关联的引用 → 无任何Reference修饰
特点:
- JVM内存不足时,不会随意释放强引用解决内存问题,而是选择抛出”内存不足异常“结束程序
- 只要超过引用作用域或者null → 可以被垃圾回收
class H {
private static int count = 0;
private int id = count++;
@Override
protected void finalize() {
println("H" + id);
}
}
//test
new H();
H h = new H();
System.gc(); // 输出:H0 强制回收,不过只终结回收了无引用的对象,h并未回收
SoftReference( 软引用 )
特点:
- 只有JVM认为内存不足时,才会试图回收 软引用所指向的对象
- 如果软引用所指向的对象被回收,则会把这个引用加入到所绑定的引用队列中
class H {
private static int count = 0;
private int id = count++;
@Override
protected void finalize() {
println("H" + id);
}
public String toString() {
return "H" + id;
}
}
//test
ReferenceQueue<H> rq = new ReferenceQueue<H>(); // 创建一个引用队列
SoftReference<H> sr = new SoftReference<H>( new H(), rq ); // 创建一个基于H对象的软引用
Reference< ? extends H > rf = rq.poll(); // 检查该软引用是否在引用队列,”非空即在“表明其准备被垃圾回收器回收
if( rf != null)
println( rf.get() ); // 得到 该软引用的实际H对象
// 输出:没有任何输出,即使强制回收也不会加入到 垃圾回收队列
WeakReference( 弱引用 )
特点:
- 垃圾回收器线程扫描它的内存区域,只要发现有弱引用,都会回收 → 不一定立即回收
- 该引用指向的对象被回收,则会把引用加入到引用队列中
class H {
private static int count = 0;
private int id = count++;
@Override
protected void finalize() {
println("终结H" + id);
}
public String toString() {
return "字符串H" + id;
}
}
//test
ReferenceQueue<H> rq = new ReferenceQueue<H>();
WeakReference<H>sr = new WeakReference<H>( new H(), rq );
Reference< ? extends H > rf = rq.poll(); //表明此时垃圾扫描器并没有开始启动
if( rf != null)
println( rf.get() );
System.gc(); // 强制启动垃圾扫描器
//输出:终结H0 只要垃圾扫描器启动就会回收 弱引用对象,即终结对象
WeakHashMap
- 用来保存 WeakReference对象
- 每个value值 只保存一个对象实例,节省空间
- 允许垃圾回收器自动清理 key值 与 value值
- 对于添加的元素,该类会自动封装成为弱引用
- 清理元素:当不再需要此键时,会自动清理键值
Element {
Integer a;
Element(Integer a ) { this.a = a; }
public String toString() { return a.toString(); }
public hashCode() { return a.hashCode(); }
public boolean equals(Object o) {
return o instanceof Element && a.equals( ((Element)o).a );
}
protected void finalize() { return "finalizing" + getClass().getSimpleName() + " " + a );
}
classs Key extends Element {
Key(Integer a ) { super(a); }
}
class Value extends Element {
Value(Integer a) { super(a); }
}
//测试
int size = 10;
Key[] keys = new Key[size];
WeakHashMap<Key, Value> map = new WeakHashMap<Key, Value>();
for(int i = 0; i < 10; i++) {
Key k = new Key(i);
Value v = new Value(i);
if( i % 3 == 0 ) { keys[i] = k; }
map.put( k, v );
}
System.gc();
//输出
//随机性的输出 Key的 finalize() 终结函数的内容。不输出Key对象为0、3、6、9的对象,因为这四个键值引用在被数组keys使用。而所有的Value对象并没有被终结。
PhantomReference( 虚/幻象引用 )
特点:
- 不能通过它访问对象
- 仅仅提供对象被 finalize() 应该做某些事情的机制
- 自动提前加入到 引用队列里,该对象被准备垃圾处理器回收
- 对象仅持有该引用,任何时候都可能被垃圾回收器回收
class H {
private static int count = 0;
private int id = count++;
@Override
protected void finalize() {
println("终结H" + id);
}
public String toString() {
return "字符串H" + id;
}
}
4.Object - 祖宗级类
-
equals( Object ): 由 native修饰 即说明调用的是非java编写的外部函数,比较的是对象的地址 - boolean
-
满足五个特点:
- 自反性:自身比较为true
- 对称性:a.equals(b)、b.equals(a) 得到的结果一致
- 传递性:a.equals(b) = true、b.equals(z) = true 则 a.equals(z) = true;
- 非null的a:a.equals(null) = false
- 一致性:无论多少次都是相同的结果
-
2. hashCode(): 由 native修饰 、返回一个基于对象内存地址的值 - int
可覆盖重写 —— 重写时不必写关键词native
散列、哈希( Hash ): 使用一个对象查找另一个对象
重载的hashCode()实用: 速度快、且有实际意义 → 尽量基于对象的内容生成散列码
-
重载的hashCode() 建议的规则:
-
返回的result 初始值为 非0常量
-
给对象内的每个属性赋予转化 为 int整形常量 c
-
合并计算散列值 即 (步骤1 * 常量 + 步骤2)
-
返回result
下面是对象内对应属性类型转化为 int值 参考建议
-
重载的hashCode() 例子
class People {
private static int count = 0;
private String name;
private final int id = count++;
People( String name ) { this.name = name; }
public int hashCode() {
int result = 17; //初始化result 非0值
result = 37 * result + name.hashCode(); //String系统有自己编写的hash值
result = 37 * result + id; // id已经是int值,无需在转化
return result; // 返回最终的hash值 result
}