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值,将对象存放到数组的某个位置。
但是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}