Java 数组与容器

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'}}
    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值