String 、StringBuffer、StringBuilder学习

String不是基础的类型数据,String类型是一种 final类型,不能被继承,存放在常量池中。通过赋值的方法String得到的字符串会在常量池中寻找,如果有对象就不创建新的字符串。指向已有的字符串。引用存在栈内存中。

string s1 = "str";
String s3 = "str";
System.out.println(s1 == s2);//true
//此时s1和s2是同一个对象。

如果创建String的方法为new,在堆内存中创建一个新的对象,返回指向该对象的引用。所以每次new 对会创建新的对象,引用存放在栈内存中。

String s3 = new String("str");
String s4 = new String("str");
System.out.println(s3 == s4);//false
s3、s4是两个对象的引用。

String 与 StringBuilder

拼接字符串的时候都是用的StringBuilder,String中数据保存方法为char[],对String修改的方法中,contact,replace,sub返回的都是new  String().对String的修改操作都是生成一个新的字符串对象并返回,原对象并不修改。利于String的replace方法,篇幅有限,只列举一个

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);//返回一个新建的String对象
            }
        }
        return this;
    }

StringBuilder拼接字符串与String对象拼接字符串。

public static void main(String[] args) {
        String string = "";
        for(int i=0;i<10000;i++){
            string += "hello";
        }
        StringBilder s = "";
   for(int i=0;i<10000;i++){
            s.append("hello");
        }
    }

String拼接字符串相当于将原字符串值拿出来和新字符串相加,产生了一个新的对象。而原本的对象没有改变。而 StringBuilder的append方法是在字符串后面添加,没有产生新的字符串对象,在原对象上修改。

StringBuilder 与 StringBuffer 的方法基本相同,不同的是StringBuffer的方法是线程安全的,方法钱添加了synchronized 关键字。

 

List中LinkedList、ArrayList

ArrayList 是数组的形式,存放的Object对象,当使用无参构造时默认生成最大大小为10的数组,当存放的数量超过大数组大小的时候,会创建一个新的1.5倍+1的数组(这里应该有迭代器失效的问题),将原数组的值拷贝过去。

ArrayList的常用操作

public static void main(String[] args) {
        List<String> list = new ArrayList(  );
        list.add( "a" );//从后添加元素
        list.add( "b" );
        list.add( "c" );
        list.add( "d" );
        String s = list.get( 2 );//获取下表位置的元素
        list.remove( "b" );//移除指定的元素
        list.remove( 1 );//移除指定下标的元素,并把后方的元素向前移动一位
    }

ArrayList的插入和删除代价较高。

LinkedList 是链式结构,双向链表,双向链表中除了存放该节点元素以外,同时存放了上一个节点和下一个节点的引用。所以在空间上LinkedList的占用空间比ArrayList占用的空间大。

ArrayList 与LinkedList相比,数组形的ArrayList的下标访问速度比链表节点的访问速度快,而LinkedList的插入、删除代价比ArrayList的代价小。

ArrayList的迭代器失效问题

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(  );
//        list.ensureCapacity( 3 );
        list.add( "a" );
        list.add( "b" );
        list.add( "c" );
        Iterator it =  list.iterator();
        list.add( "d" );
        while(it.hasNext()){
            System.out.println(it.next().toString());
        }
    }

创建Arraylist后,添加三个元素以后,在获取完迭代器以后,再想几何中添加元素,运行的时候会报错

Exception in thread "main" java.util.ConcurrentModificationException,

此时加入断点,可以发现

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];
        }

     final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

在获取下一个节点的时候报错,此时ModCount为4(这个是对ArrayList的增删次数),而迭代器数量ExpctedModCount只有3,这是在获取迭代去时初始化的值,与实际的操作次数并不相等,抛出异常。再获取迭代器后,再对结合使用集合的方法更新List的大小,此时,迭代器的期望值ExpctedModCount与 ModCount大小不相同。解决的方法是使用迭代器的方法来改变ArrayList..

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>(  );
//        list.ensureCapacity( 3 );
        list.add( "a" );
        list.add( "b" );
        list.add( "c" );
        Iterator it =  list.iterator();
      for(int i =0;i<3;i++){
            if(i==1){
                it.remove();//删除头部元素,指定下标的方法没找到
            }
            System.out.println(it.next().toString());
        }
    }

 

Queue 队列

队列是先入先出的数据结构,与List 、set 是同样继承了Collection的接口,Deque(双端队列)有linkedList实现。

public static void main(String[] args) {
        //没有实现阻塞接口的队列。
        Queue q1 = new PriorityQueue(  );
        Queue q2 = new ConcurrentLinkedQueue(  );//基于链接节点的线程安全的结构,在尾部添加元素,在头部删除元素。

        //实现阻塞接口的队列
        Queue q3 = new ArrayBlockingQueue( 5 );//一个由数组支持的有界队列
        Queue q4 = new LinkedBlockingQueue(  );//由链表支持的有界阻塞队列
        Queue q5 = new PriorityQueue(  );//优先级堆实现的队列
        Queue q6 = new DelayQueue(  );//一个由优先级堆支持的、基于时间的调度队列。
        Queue q7 = new SynchronousQueue(  );//一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制。

        q3.add( "a" );//添加尾部元素
        q3.add( "b" );
        q3.add( "c" );
        q3.poll();//移除头部元素并返回
        q3.peek();//返回头部的元素下·
        q3.add( "d" );
        q3.add( "e" );
        q3.add( "f" );
        //put(),take()方法会因为队列满或空阻塞
        for (Object s:q3) {
            System.out.println(s);
        }
    }

HashMap 数据结构

数据结构的物理存储分为顺序式结构和链式结构,哈希表的主干是数组,如果新增或查找,就通过hashCode的方法获取hash值,将对象存放到数组的某个位置。

java 本地的类无法解析_字符串

但是hash算法会可能得到相同的值,这就是哈希冲突,所以哈希表使用 数组+链表的方法试图解决这种冲突。在没有链表的哈希表中查询和插入秩序一次获取哈希值,将数据插入到对应的位置上,复杂度为O(1),而对于由链表存在的哈希表,在查找数据的时候,如果获得key(hashCode)相同,在对应key的位置上的链表查询,在链表上的数据经行equals()方法进行判断。

当对象的hashCode重复的时候,向HashMap中添加数据?

当 HashCode() 相同的时候 , 向HashMap中添加数据的时候,如果hash值已存在,会向该hashCode对应的位置的链表中添加数据,但是要先判断该对象是否重复,放入hashMap的数据会有自己的HashCode() 方法和 equals() 方法。如果hashCode(),equals()两个方法都相同,认为该元素已经在哈希表中,对应的值对象会被替换。

当两个相同key的元素如何获取正确的对象?

两个键的hashCode相同,说明对应在数组的相同的BUCKET的位置上,再遍历对应的链表(LinkedList),LinkedList中存放的是键-值对,通过比对( 键的equals()方法 )获取对应的对象。

下面是HashMap的一个测试,在键的hashCode相同,但键相等或不相等的情况,哈希表对添加操作的反应

public class Person {
    String name ;
    int age ;

    public Person(){

    }
    public Person(String name,int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime*1 + result;
        result = prime*result + ((name == null)?0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        //提高效率
        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        //提高代码健壮性,不是同一个类型就直接返回false,省得向下转型了
        if(this.getClass() != obj.getClass()) {
            return false;
        }
        //向下转型
        Person p = (Person)obj;
        if(this.age != p.age) {
            return false;
        }
        if(this.name == null) {
            if(p.name != null) {
                return false;
            }

        }
        else if(!this.name.equals(p.name)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
      Map map =  new HashMap(  );
      Person p1 = new Person( "jack",11 );
      Person p2 = new Person( "david",11 );
      Person p3 = new Person( "david",12 );

      map.put( p1,"123" );
      map.put( p2,"321" );
      System.out.println(map.toString());

      map.put( p1,"333" );//键的hashCode相同且相等,覆盖对应的值
      System.out.println(map.toString());

      map.put( p3,"123" );//键的hashCode相同,但是不相等,再对应Bucket链表添加新的节点
      System.out.println(map.toString());
    }
}

//输出
{Person{name='david', age=11}=321, Person{name='jack', age=11}=123}
{Person{name='david', age=11}=321, Person{name='jack', age=11}=333}
{Person{name='david', age=11}=321, Person{name='david', age=12}=123, Person{name='jack', age=11}=333}