盒子
盒子
Posts List
  1. 前言
  2. Java中的字符串
  3. Java中的数组
  4. 总结

Android 学习之路--Java基础(二)

作者Talent•C
转载请注明出处

前言

上一篇我们简单介绍了Java基本语法及语言特点,其实所有的编程语言都有很多相通之处,例如选择语句(if/elseswitch/case)等完全与c语言/c++/oc/php用法一致,所以过多的相通之处就不一一介绍了,也不用死记硬背这些语法,只要知道有这么个东西就可以,使用次数多了,自然就记住了,我们可以通查询Java手册了解更多Java语法。今天我们来了解一下,Java中的Stringarray等内置的引用型数据的使用及注意事项。

Java中的字符串

Java中字符串使用String类,String是一个比较特殊的引用类型数据类型,它为什么特殊?特殊在哪里?我们在上一篇文章已经介绍了;它的特殊之处在于它可以像基本数据类型那样使用, 例如初始化可以使 String s = “talent”; 也可以是 String s = new String(“talent”);

JavaString是不可变字符串,创建之后不可以修改。如果想要创建可变字符串可以考虑使用StringBufferStringBuilder,我们通过以下几个问题来解释它们。

问题一、为什么JavaString类不可变?, 这样设计的优缺点?
JavaAPI文档中我们可以看到String类的定义:

public final class String extends Object implements Serializable, Comparable, CharSequence

String类是一个final类,这代表一个String对象是不可改变的,String类的方法中我们也找不到任何能够改变字符串的值和长度的方法。这就是字符串的不可改变性。

String类中使用字符数组保存字符串,因为有final修饰符,所以可以知道String对象是不可变的。

private final char value[]; //不可变 final 修饰符

那么到这里很多人会比较疑惑当我们声明一个字符串变量String s = "abcdef" s= s+"talentC"; 这里明明是将字符串变量s的值修改成abcdeftalentC了,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个”abcdef”,然后再创建一个新的对象s(值为”abcdeftalentC”)用来执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多慢。

当我们进行连接字符串操作的时候,Java虚拟机没有改变其中任何一个字符串,而是创建了一个新的String对象,把连接后的结果赋予了它。SunString设计成不可改变的,这是为了让String的行为最优化。因为String在多数应用中都被大量使用,所以它的优化是非常关键的。

优点: 只读性,多线程开发访问不会出现问题。
缺点: 每个不同状态都需要一个对象来代表,可能会造成性能上的问题。
String中的对象是不可变的,也就可以理解为常量,显然线程安全。

那么我们想要创建可变字符串怎么办?接下来就该StringBufferStringBuilder出场了。

问题二、 StringBuffer是什么? StringBuilder是什么? 区别与联系?
StringBuffer: 字符串变量,线程安全。
StringBuilder: 字符串变量,线程不安全。

StringBuilderStringBuffer都继承自AbstractStringBuilder类( 抽象类 ),在AbstractStringBuilder中也是使用字符数组保存字符串;

char[] value; //这就是它可变的根本原因

抽象类与接口的其中一个区别是:抽象类中可以定义一些子类的公共方法,子类只需要增加新的功能,不需要重复写已经存在的方法;而接口中只是对方法的申明和常量的定义我们后面会详细讲所有与类有关的知识点。

AbstractStringBuilder类中规定了一些字符串基本操作,如 expandCapacityappendinsertindexOf 等公共方法,通过查看其源码发现StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。

public synchronized StringBuffer reverse() {
super.reverse();
return this;
}
public int indexOf(String str) {
return indexOf(str, 0); //存在 public synchronized int indexOf(String str, int fromIndex) 方法
}

StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。通常我们在单一线程操作字符串,我们建议使用StringBuilder,原因就是它的执行速度快。

Java中字符串执行速度为: StringBuilder > StringBuffer > String;

Java中可以对字符串进行哪些操作?

StringBufferStringBuilder 用法一样,我们这里使用StringBuffer演示
(1)创建StringBuffer字符串

StringBuffer s = new StringBuffer(“abc”);

创建StringBuffer对象要通过构造器创建及StringBuffer(参数);

StringBufferString:

StringBuffer s = new StringBuffer(“abc”);
String str = s.toString(); //StringBuffer对象转 String对象

StringStringBuffer:

String str = “abcd”;
StringBuffer s = new StringBuffer(str); //String对象转 StringBuffer对象

注意: StringBufferString转换不可以用过强制转换;

(2)StringBuffer字符串拼接使用append

StringBuffer s = new StringBuffer(“abc”);
s.append(“123”); //此时字符串s值为 “abc123”
String substr = “qq”;
s.append(qq); //此时字符串s值为 “abc123qq”

使用该方法进行字符串的连接,将比String更加节约内容,执行速度更加快速。

(3)StringBuffer字符串删除使用deleteCharAt 和 delete
完整API:
public StringBuffer deleteCharAt(int index)
该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串

StringBuffer s = new StringBuffer(“TalentC”);
s. deleteCharAt(1);

该代码的作用删除字符串对象s中索引值为1的字符,也就是删除第二个字符,剩余的内容组成一个新的字符串。所以对象s的值变为“TlentC”。

还存在一个功能类似的delete方法:
public StringBuffer delete(int start,int end)
该方法的作用是删除指定区间以内的所有字符,包含start,不包含end索引值的区间

StringBuffer s = new StringBuffer(“TalentC”);
s. delete (1,4);

该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符,剩余的字符形成新的字符串。则对象s的值是”TntC”。

(4)StringBuffer字符串插入字符使用 insert
public StringBuffer insert(int offset, boolean b)
该方法的作用是在StringBuffer对象中插入内容,然后形成新的字符串;

StringBuffer s = new StringBuffer(“TalentC”);
s.insert(4,false);

该示例代码的作用是在对象s的索引值4的位置插入false值,形成新的字符串,则执行以后对象s的值是”TalefalsentC”。

(5)StringBuffer字符串反转使用 reverse
public StringBuffer reverse()
该方法的作用是将StringBuffer对象中的内容反转,然后形成新的字符串

StringBuffer s = new StringBuffer(“TalentC”);
s.reverse();

经过反转以后,对象s中的内容将变为”CtnelaT”。

(6)StringBuffer字符串替指定位置换字符使用 setCharAt
public void setCharAt(int index, char ch)
该方法的作用是修改对象中索引值为index位置的字符为新的字符ch

StringBuffer s = new StringBuffer(“TalentC”);
s.setCharAt(1,’D’);

则对象s的值将变成”TDlentC”。

(7)StringBuffer对象的中存储空间缩小到和字符串长度一样的长度 trimToSize
public void trimToSize()
该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度,减少空间的浪费。

Java中操作字符串的API还有很多就不一一介绍了;

Java中的数组

静态数组(不可变数组)
静态数组(不可变数组),与c语言中的数组相同,

int [] aArray = new int[10];
int [] bArray = new int[]{1,2,3};

int cArray[] = new int[10];
int dArray[] = new int[]{1,2,3};

这是c语言常见数组声明方式,在Java中这种方式同样适用,一般我们推荐使用第一种方式,更能表明数据元素类型;静态数组的长度是固定的,一旦创建数组元素的个数就不可以随意增加或减少这就是静态数组的特点。

静态数组中元素的修改/读取都与C语言等其他语言完全一致这里就不过多介绍了。

动态数组(可变数组)
先介绍几个数据集合 Set、List、Map
Set(集): 集合中的元素不按特定方式排序,并且没有重复对象。他的有些实现类能对集合中的对象按特定方式排序。
List(列表): 集合中的元素按索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象
Map(映射): map也叫做字典与OC中的dictionary(字典) 概念相同,集合中的每一个元素包含一对键对象和值对象,集合中没有重复的键对象,值对象可以重复。他的有些实现类能对集合中的键对象进行排序。

注意:
这三个数据集合,在很多计算机编程语言中的定义都一样,Mapobjective-c中叫做 字典,在php中叫做使用 key 作为下标数组,不管怎么改变叫法或者名称,他们的概念及用法都不变。

我们再来了解Java中几个类:ArrayListLinkedListListCollection

Collection: 该接口是 SetList 的父接口,主要提供了下面的方法:

  • public boolean add(Object?o):往集合中添加新元素。添加成功,返回true,否则返回false。
  • public Iterator iterator():返回Iterator对象,这样就可以遍历集合中的所有元素了。
  • public boolean contains(Object?o):判断集合中是否包含指定的元素。
  • public int size():取得集合中元素的个数。
  • public void clear():删除集合中的所有元素。

Collection是最基本的集合接口,一个Collection代表一组 Object ,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK 不提供直接继承自Collection的类,JavaSDK 提供的类都是继承自Collection的“子接口”如 ListSet

上面提到两个继承自Collection接口的接口类ListSet

List集合
List继承自Collection接口。List是一种有序集合,List中的元素可以根据索引(顺序号:元素在集合中处于的位置信息)进行取得/删除/插入操作。
Set集合不同的是,List允许有重复元素。对于满足e1.equals(e2)条件的e1e2对象元素,可以同时存在于List集合中。当然也有List的实现类不允许重复元素的存在。同时,List还提供一个listIterator()方法,返回一个ListIterator接口对象,和Iterator接口相比,ListIterator添加元素的添加,删除,和设定等方法,还能向前或向后遍历,会在后面详细讲解。List接口的实现类主要有ArrayListLinkedListVectorStack等。

ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括 nullArrayList没有同步。sizeisEmptygetset 方法运行时间为常数。但是 add 方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量 (Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用 ensureCapacity 方法来增加ArrayList的容量以提高插入效率。

主要方法:

  • public boolean add(Object?o):添加元素
  • public void add(int index, Object element):在指定位置添加元素
  • public Iterator iterator():取得Iterator对象便于遍历所有元素
  • public Object get(int?index):根据索引获取指定位置的元素
  • public Object set(int index,Object element):替换掉指定位置的元素
    排序方法:
  • Collections.sort(List list):对List的元素进行自然排序
  • Collections.sort(List list, Comparator comparator):对List中的元素进行客户化排序

//创建数组
ArrayList aArray = new ArrayList();
aArray.add(“我是第一个元素”);
System.out.print(“数组元素打印:” + aArray+”\n”);
aArray.add(0,”我是索引值0插入的元素”);
System.out.print(“数组元素打印:” + aArray+”\n”);
aArray.set(1,”我是指定索引值修改的元素内容”);
System.out.print(“数组元素打印:” + aArray+”\n”);
结果为:
数组元素打印:[我是第一个元素]
数组元素打印:[我是索引值0插入的元素, 我是第一个元素]
数组元素打印:[我是索引值0插入的元素, 我是指定索引值修改的元素内容]

数组的遍历:
数组元素的遍历与C语言/OC数组遍历方式一样都是通过for循环去遍历

ArrayList bArrray = new ArrayList(10);
for (int i =0 ; i<10; i++){
//循环向数组中填充数据
String ys = “我是元素-“+i;
bArrray.add(ys);
}
System.out.print(“数组元素打印:” + bArrray+”\n”); //打印数组数据,这里也可以将数组转成字符串进行打印
for (int j = 0; j<bArrray.size();j++) {
//循环遍历数组并输出元素
System.out.print(“数组元素打印:index:”+j+”value:” + bArrray.get(j)+”\n”);
}

关于ArrayList的其他使用是方法就不过多解释,直接参照API使用即可。

LinkedList类:
LinkedList实现了List接口,允许 null 元素。此外LinkedList提供额外的 getremoveinsert 方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作 堆栈(stack),队列(queue)或双向队列(deque)

注意:
LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List

List list = Collections.synchronizedList(new LinkedList(…));

Set集合:
Set是最简单的集合,集合中的对象不按照特定的方式排序。主要有如下两个实现:HashSetTreeSet
HashSet类按照哈希算法来存取集合中的对象,具有很好的存取性能。当HashSet向集合中加入一个对象时,会调用对象的hashCode()方法获取哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。

TreeSet实现了SortedSet接口,可以对集合中的元素排序。如何排序的内容请参考其他文档,这里不过多解释了。

Map
Map是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象,与OC中的 字典(dictionary)、与 php 中的带有键值对的数组相通,它的存储方式与Set存储方式一致。

  • Map添加元素时,必须提供键对象和值对象。
  • Map中检索元素时,只要给出键对象,就可以返回对应的值对象。
  • 键对象不能重复,但值对象可以重复。
  • Map有两种常见的实现类:HashMapTreeMap

HashMap
HashMap按照哈希算法来存取键对象,有很好的存取性能。和HashSet一样,要求当两个键对象通过equals()方法比较为 true 时,这两个键对象的 hashCode() 方法返回的哈希码也一样。

TreeMap
TreeMap实现了SortedMap接口,能对键对象进行排序。同TreeSet一样,TreeMap也支持自然排序和客户化排序两种方式。

使用方法

  • public Object put(Object key, Object value):插入元素
  • public Object get(Object?key):根据键对象获取值对象
  • public Set keySet():取得所有键对象集合
  • public Collection values():取得所有值对象集合
  • public Set entrySet():取得Map.Entry对象集合,一个Map.Entry代表一个Map中的元素

这些使用方法都很简单,直接参照API就可以。

拓展:
如果涉及到堆栈,队列等操作,应该考虑用 List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

总结

这篇文章主要介绍了Java中对字符串的操作和一些常见的数据集合的介绍及原理分析,在日后的开发中可以根据不同的需求选在不同的实现方式已达到性能的最优;也充分验证了,计算机编程语言的共通性;学习是一件很枯燥的事情,只要坚持下来会有很多收获的。说实话,关于这篇文章中关于字符串的那部分,我也是学了两遍才完全弄明白其中的知识点,所以初学者们不要灰心,坚持住才会有收获。下一篇文章我会跟大家分享一下Java中如何创建class及相关知识。

支持一下
扫一扫,支持Talent•C