1 数组
多维数组中构成矩阵的每个向量都可以具有任意长度。
1.1 Arrays 实用功能
1.1.1 复制数组
System.arraycopy 复制数组比用for循环复制要快很多。
@Test
public void test2() {
int[] array1 = {1,2,3,4};
int[] array2 = new int[10];
System.arraycopy(array1,1,array2,2,3);
System.out.println(Arrays.toString(array2)); //运行结果: [0, 0, 2, 3, 4, 0, 0, 0, 0, 0]
Integer[] array3 = new Integer[10];
System.arraycopy(array1,1,array3,2,3); //报错,arraycopy不会执行自动包装和自动拆包,两个数组必须具有相同的确切类型
}
1.1.2 数组的比较
Arrays.equals(),用来比较整个数组,数组相等的条件是元素个数必须相等,并且对应位置的元素也相等。
1.1.3 数组元素的比较
1,实现Comparable接口;
2,实现Comparator接口;
public class CompareTest {
private static class Phone implements Comparable<Phone>{
public String name;
public int system;
public int cpu;
public Phone(String name,int system, int cpu) {
this.name = name;
this.system = system;
this.cpu = cpu;
}
@Override
public int compareTo(Phone o) {
int tag = 0;
if (system != o.system) tag = system > o.system ? 1 : -1; //不直接将两个值向减,防止结果超出int 的范围
else if (cpu != o.cpu) tag = cpu > o.cpu ? 1 : -1;
return tag; //小于参数则返回负值,相等则返回0
}
@Override
public String toString() {
return "Phone{" +
"name='" + name + '\'' +
", system=" + system +
", cpu=" + cpu +
'}';
}
}
public static void main(String[] args) {
Phone[] array = {new Phone("HTC",1,1),new Phone("iPhone",2,1),new Phone("Samsung",1,2)};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
// 运行结果:
// [Phone{name='HTC', system=1, cpu=1}, Phone{name='Samsung', system=1, cpu=2}, Phone{name='iPhone', system=2, cpu=1}]
}
}
public class CompareTest2 {
private static class Computer{
public String name;
public int system;
public int cpu;
public Computer(String name, int system, int cpu) {
this.name = name;
this.system = system;
this.cpu = cpu;
}
@Override
public String toString() {
return "Computer{" +
"name='" + name + '\'' +
", system=" + system +
", cpu=" + cpu +
'}';
}
}
private static class ComputerComparator implements Comparator<Computer> {
@Override
public int compare(Computer o1, Computer o2) {
int tag = 0;
if (o1.system != o2.system) tag = o1.system > o2.system ? 1 : -1;
else if (o1.cpu != o2.cpu) tag = o1.cpu > o2.cpu ? 1 : -1;
return tag;
}
}
public static void main(String[] args) {
Computer[] array = {new Computer("苹果",8,6),new Computer("联想", 5,4),new Computer("外星人",5,9)};
Arrays.sort(array, new ComputerComparator());
System.out.println(Arrays.toString(array));
// 运行结果:
// [Computer{name='联想', system=5, cpu=4}, Computer{name='外星人', system=5, cpu=9}, Computer{name='苹果', system=8, cpu=6}]
}
}
基本类型数组无法使用Comparator进行排序。
1.1.4 在已排序的数组中查找
如果数组已经排好序,就可以使用Arrays.binarySearch执行快速查找,如果要对未排序的使用binarySearch(),那么将产生不可预料的结果。
如果数组中包含重复的元素,则无法保证找到的是这些副本中的哪一个。
如果使用Comparator排序了某个对象数组,在使用binarySearch时必须提供同样的Comparator。
2 容器
2.1 填充容器
Collections.fill(list,obj):复制同一个对象引用来填充整个容器,并且只对List对象有用。
Collections.nCopies(n,obj) 创建不可修改的,大小为n,且内容填充为obj的不可修改 List, 将其返回值传递个ArrayList构造函数时,新的对象可修改。
@Test
public void test() {
List<String> list = Arrays.asList("hello","java","and","word");
System.out.println("original list:"+ list); // 返回结果:original list:[hello, java, and, word]
Collections.fill(list,"apple");
System.out.println("new list" + list);//返回结果: new list[apple, apple, apple, apple]
List<String> list2 = new ArrayList<>(Collections.nCopies(5, "mac"));
list2.add("apple");
System.out.println(list2);// 返回结果: new list[apple, apple, apple, apple]
List<String> nList = Collections.nCopies(5, "mac");
nList.set(1,"apple"); // 报错,不能修改
}
2.2 Collection的功能方法
Boolean retainAll(Collection<?>): 只保存参数重的元素(应用“交集”概念),只要Collection发生了变化就返回true。
@Test
public void test2() {
List<Integer> list1 = new ArrayList<>(Arrays.asList(1,2,3));
List<Integer> list2 =new ArrayList<>(Arrays.asList(3,4,5));
System.out.println(list1.retainAll(list2));; //运行结果: true
System.out.println(list1);//运行结果: [3]
}
2.3 可选操作
2.3.1 未获支持的操作
UnsupportedOperationException 必须是一种罕见的事件。即对于大多数类来说,所有操作应该可以工作,只有在特例中才会有未获支持的操作。其只有在运行时才能探测到。
2.4 Set和存储顺序
2.4.1 SortedSet
可以保证元素处于排序状态,是一个接口,TreeSet(元素必须实现Comparable接口)是其实现类。
@Test
public void test3() {
Random random = new Random(21);
SortedSet<Integer> treeSet = new TreeSet<>();
for (int i = 0; i < 10; i++)
treeSet.add(random.nextInt(100));
System.out.println(treeSet); //运行结果:[20, 24, 27, 44, 46, 52, 53, 78, 81]
SortedSet<Integer> headSet = treeSet.headSet(50); // 返回小于50元素集合
System.out.println(headSet); //运行结果:[20, 24, 27, 44, 46]
SortedSet<Integer> tailSet = treeSet.tailSet(50); // 返回大于或等于50元素的集合
System.out.println(tailSet); //运行结果:[52, 53, 78, 81]
}
2.5 队列
2.5.1 优先级队列
PriorityQueue 优先级队列
public class QueueTest {
private static class Employee implements Comparable<Employee>{
String name;
int position; // 职位
int entryTime; // 入职时间
public Employee(String name, int position, int entryTime) {
this.name = name;
this.position = position;
this.entryTime = entryTime;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", position=" + position +
", entryTime=" + entryTime +
'}';
}
@Override
public int compareTo(Employee o) {
int tag = 0;
if (position != o.position) tag = position > o.position ? 1 : -1;
else if (entryTime != o.entryTime) tag = entryTime > o.entryTime ? 1 : -1;
return tag;
}
}
public static void main(String[] args) {
PriorityQueue<Employee> employees = new PriorityQueue<>();
employees.addAll(Arrays.asList(new Employee("小李",5,2),new Employee("小张",3,10),new Employee("小萌",3,1)));
System.out.println(employees); //运行结果:[Employee{name='小萌', position=3, entryTime=1}, Employee{name='小李', position=5, entryTime=2}, Employee{name='小张', position=3, entryTime=10}]
}
}
2.6 理解Map
2.6.1 性能
HashMap | 基于散列表的实现,插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能 |
LinkedHashMap | 类型于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序 |
TreeMap | 基于红黑树的实现,查看“键”或“键值对”时,它们会被排序。特点在于:所得到的结果是经过排序的。TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树 |
WeakHashMap | 弱键映射,运行释放映射所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾收集器回收 |
ConcurrentHashMap | 一种线程安全的Map,它不涉及同步加锁。 |
IdentityHashMap | 使用==代替equals对“键”进行比较的散列映射。专为解决特殊问题而设计 |
2.6.2 LinkedHashMap
为提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定其采用基于访问的最近最少使用(LRU)算法,于是没有被访问过的(可被看作需要删除的)元素就会出现在队列的最前面。
@Test
public void test() {
LinkedHashMap<Integer, String> map =
new LinkedHashMap<>(16,0.75f,true);//初始化容量,负载因子,是否采用LRU算法
map.put(1,"a"); map.put(2,"b");map.put(3,"c");map.put(4,"d");
System.out.println(map);//1=a, 2=b, 3=c, 4=d}
System.out.println(map.get(3));
System.out.println(map.get(2));
System.out.println(map.get(2));
System.out.println(map);//{1=a, 4=d, 3=c, 2=b}
}
2.7 散列和散列码
2.7.1 理解hashCode()
并不需要总是能够返回唯一的标志码,但是equals()方法必须严格地判断两个对象是否相同。
使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用Tree Map或者你自己实现的Map也可以达到此目的。
散列的价值在于速度,使得查询得以快速进行。瓶颈位于键的查询速度。散列将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息。 Map 保存数量不确定的值,如果键的数量被数组的容量限制了,该怎么办?
答案就是:数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由Object中且可能由你的类覆盖的hashCode()方法生成。
为解决数组容量被固定的问题,不同的键可以产生相同的下标。也就是说,可能会有冲突。因此,数组多大不重要,任何键总能在数组中找到它的位置。
于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。通常冲突由外部链接处理:数组并不直接保存值,而是保存值的list.然后对list中的值使用equals()方法进行线性查询。
2.7.2 编写像样的hashCode()基本指导
1)给int变量result赋予某个非零值常量,例如17;
2)为对象内每个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c;
3)合并计算得到的散列码:
result = 37 * result _ c;
4) f返回result;
5)检查hashCode()最好生成的结果,确保相同的对象有相同的散列码;
public class HashTest {
private static class HashItem {
String name;
int age;
public HashItem(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
int result = 17;
result += 37*result + name.hashCode();
result += 37*result + age;
return result;
}
}
public static void main(String[] args) {
Map<String,HashItem> map = new HashMap<>();
map.put("one",new HashItem("黄",18));
map.put("two",new HashItem("刘", 19));
System.out.println("one hashCode: " + map.get("one").hashCode());//one hashCode: 1569038
}
}
2.7.3 HashMap的性能因子
容量:表中的桶位数。
初始容量:表在创建时所拥有的桶位数。
尺寸:表中当前存储的项数。
负载因子:尺寸/容量。 空表的负载因子是0,半满表的负载因子是0.5。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的(但是会减慢使用迭代器进行遍历的进程)。当负载情况达到该负载因子的水平时,容器将自动增加其容量,实现方式是使容量大致加倍。HashMap使用的默认负载因子是0.75。 更高的负载因子可以降低表所需的空间,但是会增加查找代价。(查找是我们在大多数时间所做的操作)
2.8 实用方法
2.8.1 设定Collection或Map为不可修改
Collections.unmodifiableList(List list): 此方法允许你保留一份可修改的容器(list),作为private成员,然后通过某个方法调用返回对该容器“只读”对引用。这样一来,就只有你可以修改容器的内容,而别人只能读取。
2.8.2 Collection或Map的同步控制
Collections.synchronizedCollection(List list)。
Java容器有一种保护机制,能防止多个进程同时修改一个容器的内容。如果在你迭代遍历某个容器的过程中,另一个进程介入其中,并且插入、删除或修改此容器内的某个对象,那么就会出现问题。 Java容器类类库采用“快速报错”的机制,它会探查容器上的任何除你的进程所进行的操作外的所有变化,一旦它发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException异常。
@Test
public void test() {
List<String> list = new ArrayList<>(Arrays.asList("hello","java"));
Iterator<String> iterator = list.iterator();
list.add("word");
System.out.println(iterator.next()); //报错 ConcurrentModificationException
}
程序运行时发生了异常,因为在容器取得迭代器后,又有东西被放入到该容器了。
ConcurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet都使用了可以避免该异常的技术。
2.9 持有引用
对象是可获得的,是指此对象可在程序中的某处找到。这意味着你在栈中有一个普通的引用,而它正指向此对象;也可能是你的引用指向某个对象,而那个对象含有另一个引用指向正在讨论的对象;也可能有更多的中间链接。如果一个对象是“可获得的”,垃圾回收器就不能释放它。如果一个对象不是“可获得的”,那么你的程序将无法使用到它,所以将其回收是安全的。
如果想继续持有某个对象的引用,希望以后还能访问到该对象,但是也希望能够允许垃圾回收器释放它,这时就应该使用Reference对象。这样,你可以继续使用该对象,而在内存消耗殆尽的时候又允许释放该对象。
以Reference对象作为你和普通引用之间的媒介,另外,一定不能有普通的引用指向那个对象(指没有经Reference对象包装过的引用。),这样就不能达到上诉目的。 如果垃圾回收器发现某个对象通过普通引用是可获得的,该对象就不会被释放。
SoftReference、WeakReference和PhantomReference由强到弱排列,对应不同级别的“可获得性”。
SoftReference 用以实现内存敏感的高速缓存。内存不够时,GC会回收Soft Reference所引用对象,在项目中可以将某些数据设置成SoftReference可以避免内存溢出。因为其引用对象可能会空,在获取该引用对象时,需要判断是否为空。
WeakReference 是为实现“规范映射”中对象的实例可以在程序的多次被同时使用,以节省存储空间。当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象。
Phantomreference(虚引用)用以调度回收前的清理工作,它比Java终止机制更灵活。虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
2.9.1 WeakHashMap
它被用来保存WeakReference。它使得规范映射更容易使用。在这种映射中,每个值只保存一份实例以节省存储空间。当程序需要那个“值”的时候,便在映射中查询现有的对象,然后使用它(而不是重新再创建)。它允许垃圾回收器自动清理键和值。允许清理元素的触发条件是:不再需要此键了。
public class WeakHashMapTest {
private static class Element {
String name;
public Element(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("name:" + name + "被销毁了");
}
@Override
public String toString() {
return "Element{" +
"name='" + name + '\'' +
'}';
}
}
private static class Key extends Element {
public Key(String name) {
super(name);
}
}
private static class Value extends Element{
public Value(String name) {
super(name);
}
}
public static void main(String[] args) {
WeakHashMap<Key, Value> weakHashMap = new WeakHashMap<>();
Element[] array = new Element[12];
int num = 0;
for(int i = 0; i < 6; i++) {
Key key = new Key("key" + i);
Value value = new Value("value" + i);
weakHashMap.put(key,value);
if (i % 2 == 0) array[num++] = key;
}
System.gc();
System.out.println(weakHashMap);
// 运行结果:
// name:key5被销毁了
// name:key3被销毁了
// name:key1被销毁了
// {Element{name='key2'}=Element{name='value2'}, Element{name='key4'}=Element{name='value4'}, Element{name='key0'}=Element{name='value0'}}
}
}