首页 > 代码库 > Java基本的程序结构设计 字符类型

Java基本的程序结构设计 字符类型

字符型

从概念上讲,java的字符型就是unicode字符序列。

不可变:

一旦定义了一个字符串,就没有方法修改它。java没有提供修改字符串的方法,对于C程序来说,字符串相当于是个数组,你可以改变任何一个下标的值。但是Java不可以。

如果要对java字符串修改下标,只能新建一个字符串,然后通过substring和拼接来实现,这样一定程度上效率很低。

但是java设计者认为字符串修改操作很少,对于字符串的操作,大多是比较,和合并等操作。所以java设计者将字符串设置为不可变,然后实现了共享。

没有字符串是可变的,就不能实现共享:

可以想象将字符串放在一个公共的池中,字符串变量指向池中的值,如果复制一个字符串,源字符串和复制的字符串共享相同的位置。

java设计者认为共享带来的高效率远远胜过可修改的字符串。

检测相等:

因为不可变,所以不能用==,只能用equals。

因为String是对象,对于对象的比较,==在java中是用来比较对象指向的堆栈中的位置是否相同。

虽然如此,如果我们用=比较对象指向堆栈中的位置是否一样,也是不可以。

  1. package com.zjf;
  2.  
  3. public class Test {
  4.  
  5.    public static void main(String[] args) {
  6.  
  7.       String s1 = "zhang";
  8.       String s2 = "zhang";
  9.       System.out.println(s1 == s2);
  10.    }
  11. }

结果为true。对我们来说,并没有做s1=s2的操作,结果却是true,这不是我们想要的结果。虽然说Sting a = b;那么用a == b是可以的,但是如果没有a=b,也可以a==b,这样对我们来说,会有不可预知的结果。

那么如果我们想用=来比较两个字符串的内容是否一致呢?也不行。

  1. package com.zjf;
  2.  
  3. public class Test {
  4.  
  5.    public static void main(String[] args) {
  6.  
  7.       String s1 = "zhang";
  8.       String s2 = "zhangjianfeng".substring(0, 5);
  9.       System.out.println(s1);
  10.       System.out.println(s2);
  11.       System.out.println(s1 == s2);
  12.    }
  13. }

结果为:

zhang

zhang

false

 

两个字符串都是zhang,使用=却没有比较成功。

原因是因为java虚拟机只对字符串常量进行共享,对于+和substring等操作产生的结果,是不会共享的。

使用==进行字符串比较,程序会出现bug,而且这种bug在一定程度上是随机的。不要使用。

代码点和代码单元

Java字符串由char序列组成,char是采用UTF-16编码表示Unicod代码点的代码单元。

代码点就是我们生活中面对的一个字,因为UTF-16的存储方式,对于某些代码点需要32位,也就是说两个代码单元来存储,在java中,一个char是一个代码单元。

这样就会造成一些误解。

首先,length方法返回的是代码单元的数量。而不是代码点的数量。

其次,charAt方法获取的是代码单元,不是代码点。如果想获取代码点,string提供的有codepoint方法,

如下代码:

技术分享

作者说,避免使用char,因为这太低级了。其实,虽然很少见到这些特殊字符,使用char的场景还是要慎重。

构建字符串:

使用较短的字符串构建字符串:

  • StringBuilder
  • StringBuffer 线程安全

包引入问题

像Sting,Integer,StringBuilder这种包,位于java.lang目录下,不需要import引入,也能识别。

读取输入:

java.util.Scanner

一个可以使用正则表达式来分析基本类型和字符串的简单文本扫描器。

了解:

Scanner 使用分隔符模式将其输入分解为标记,默认情况下该分隔符模式与空白匹配。然后可以使用不同的 next 方法将得到的标记转换为不同类型的值。

例如,以下代码使用户能够从 System.in 中读取一个数:

Scanner sc = new Scanner(System.in);

int i = sc.nextInt();

 

再看一个例子,以下代码使 long 类型可以通过 myNumbers 文件中的项分配:

Scanner sc = new Scanner(new File("myNumbers"));

while (sc.hasNextLong()) {

long aLong = sc.nextLong();

}

扫描器还可以使用不同于空白的分隔符。下面是从一个字符串读取若干项的例子:

String input = "1 fish 2 fish red fish blue fish";

Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");

System.out.println(s.nextInt());

System.out.println(s.nextInt());

System.out.println(s.next());

System.out.println(s.next());

s.close();

输出为:

1

2

red

blue

以下代码使用正则表达式同时分析所有的 4 个标记,并可以产生与上例相同的输出结果:

String input = "1 fish 2 fish red fish blue fish";

Scanner s = new Scanner(input);

s.findInLine("(\\d+) fish (\\d+) fish (\\w+) fish (\\w+)");

MatchResult result = s.match();

for (int i=1; i<=result.groupCount(); i++)

System.out.println(result.group(i);

s.close();

扫描器所使用的默认空白分隔符通过 Character.isWhitespace 来识别。

关于File的相对目录:

了解:

如果在创建一个File的时候,使用了相对目录,那么:

相对路径名必须使用来自其他路径名的信息进行解释。默认情况下,java.io 包中的类总是根据当前用户目录来分析相对路径名。此目录由系统属性 user.dir 指定,通常是 Java 虚拟机的调用目录。

也就是说相对于user.dir的目录。所以我们尽量不会使用相对目录,这里遇到了,所以标注下,了解下。

文本输出:

java.io.PrintWriter

文本输出流打印对象格式化表示形式。此类实现在 PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。

这个类的作用是把内存中的对象,以格式化文本的方式,输出到流中。它的api如下:

技术分享

java.io.PrintStream和java.io.PrintWriter的api基本一样,只不过一个是通过字节的方式来输出,一个是字符的方式,不过这都是内部实现上。

这两个类很少使用,因为System.out使用的是PrintStream,我们这里了解一下。

Java基本的程序结构设计 字符类型