2018.7.27
1、Java的主要内置数据结构包括:枚举(Enumeration),位集合(BitSet),向量(Vector), 栈(Stack), 字典(Dictionary), 哈希表(Hashtable), 属性(Properties)。
枚举(Enumeration)
枚举接口定义了一种从数据结构中取回连续元素的方式。例如,枚举定义了一个叫nextElement
的方法,该方法用来得到一个包含多个元素的数据结构的下一个元素,在这一点上很像是C++中的迭代器。
而事实上,这种传统接口确实已经被迭代器取代了,在现在代码中已经很少被使用了。
import java.util.Vector;
import java.util.Enumeration;
public class EnumerationTester {
public static void main(String[] args) {
Enumeration<String> days;
Vector<String> dayNames = new Vector<String>();
dayNames.add("Sunday");
dayNames.add("Monday");
dayNames.add("Tuesday");
dayNames.add("Wednesday");
dayNames.add("Thursday");
dayNames.add("Friday");
dayNames.add("Saturday");
days = dayNames.elements();
while (days.hasMoreElements()) {
System.out.println(days.nextElement());
}
}
}
位集合(BitSet)
位集合实现了一组可以单独设置和清除的位或标志。
该类在处理一组Boolean值的时候非常有用,只需要给每个值赋予一位,然后对位进行相应的设置或清除,就可以对布尔值进行操作了。
import java.util.BitSet;
public class BitSetDemo {
public static void main(String[] args) {
BitSet bits1 = new BitSet(16);
BitSet bits2 = new BitSet(16);
// set some bits
for (int i = 0; i < 16; ++i) {
if (i % 2 == 0) bits1.set(i);
if (i % 5 != 0) bits2.set(i);
}
System.out.println("Initial pattern in bits1 : ");
System.out.println(bits1);
System.out.println("Initial pattern in bits2 : ");
System.out.println(bits2);
// AND bits
bits2.and(bits1);
System.out.println("bits2 AND bits1 : ");
System.out.println(bits2);
// OR bits
bits2.or(bits1);
System.out.println("bits2 OR bits1 : ");
System.out.println(bits2);
// XOR bits
bits2.xor(bits1);
System.out.println("bits2 XOR bits1 : ");
System.out.println(bits2);
}
}
向量(Vector)
向量类和传统数组非常类似,但是Vector的大小能根据需要动态的变化。
与C++STL中的vector中不同,Java的Vector中可以存储不同类型的元素:
import java.util.*;
public class VectorDemo {
public static void main(String[] args) {
Vector v = new Vector(3,2);
System.out.println("Initial size : " + v.size());
System.out.println("Initial capacity : " + v.capacity());
v.addElement(new Integer(1));
v.add(2);
v.add(3);
v.add(4);
System.out.println("Capacity after four additions:" + v.capacity());
v.addElement(new Double(5.45));
v.addElement(new String("haha"));
Enumeration vEnum = v.elements();
System.out.println("Elements in vector:");
while (vEnum.hasMoreElements()) {
System.out.println(vEnum.nextElement() + " ");
}
System.out.println();
}
}
栈(Stack)
package test3;
import java.util.*;
public class StackDemo {
static void showpush(Stack<Integer> st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
}
static void showpop(Stack<Integer> st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack : " + a);
}
public static void main(String[] args) {
Stack<Integer> st = new Stack<Integer>();
System.out.println("stack : " + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
try {
showpop(st);
} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}
字典(Dictionary)
字典类定义了键映射到值的数据结构。
其实Dictionary已经过时了,在实际开发中应该使用Map
接口来实现k-v的存储功能。
package test3;
import java.util.*;
public class MapDemo {
public static void main(String[] args) {
Map m1 = new HashMap();
m1.put("Zara", "8");
m1.put("Mahnaz", "31");
m1.put("Ayan", "12");
m1.put("Daisy", "14");
System.out.println();
System.out.println("Map Elements");
System.out.println(m1);
System.out.println(m1.get("Zara"));
}
}
哈希表(Hashtable)
Hashtable类提供了一种在用户定义键结构的基础上来组织数据的手段。
例如,在地址列表的哈希表中,你可以根据邮政编码作为键来存储和排序数据,而不是通过人名。
其实Hashtable就是Dictionary的具体实现,与HashMap类很相似,但是其支持同步。
package test3;
import java.util.*;
public class HashTableDemo {
public static void main(String[] args) {
//Create a hash
Hashtable balance = new Hashtable();
Enumeration names;
String str;
double bal;
balance.put("Zara", new Double(3434.34));
balance.put("Mahnaz", new Double(123.22));
balance.put("Ayan", new Double(1378.00));
balance.put("Daisy", new Double(99.22));
balance.put("Qadir", new Double(-19.08));
// Show all balances in hash table
names = balance.keys();
while (names.hasMoreElements()) {
str = (String) names.nextElement();
System.out.println(str + ": " + balance.get(str));
}
System.out.println();
//Deposit 1000 into Zara's account
bal = ((Double)balance.get("Zara")).doubleValue();
balance.put("Zara", new Double(bal+1000));
System.out.println("Zara's new balance:" + balance.get("Zara"));
}
}
属性(Properties)
Properties继承于Hashtable。表示一个持久的属性集。属性列表中每个键及其对应值都是一个字符串。
import java.util.*;
public class PropDemo {
public static void main(String args[]) {
Properties capitals = new Properties();
Set states;
String str;
capitals.put("Illinois", "Springfield");
capitals.put("Missouri", "Jefferson City");
capitals.put("Washington", "Olympia");
capitals.put("California", "Sacramento");
capitals.put("Indiana", "Indianapolis");
// Show all states and capitals in hashtable.
states = capitals.keySet(); // get set-view of keys
Iterator itr = states.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
System.out.println("The capital of " +
str + " is " + capitals.getProperty(str) + ".");
}
System.out.println();
// look for state not in list -- specify default
str = capitals.getProperty("Florida", "Not Found");
System.out.println("The capital of Florida is "
+ str + ".");
}
}
2、Java集合框架
集合框架被设计成要满足以下几个目标:
· 该框架必须是搞性能的,基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的;
· 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性;
· 对一个集合的扩展和适应必须是简单的。
集合框架是一个用来代表和操纵集合的同一架构。所有的集合框架都包含如下内容:
· 接口:代表集合的首先数据类型。例如Collection、List、Set、Map等。之所以定义多个接口,是为了以不同方式操作集合对象;
· 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,如ArrayList、LinkedList、HashSet、HashMap;
· 算法: 是实现集合接口的对象里的方法执行的一些有用的算法, 如搜索和排序,这些算法被称为多态,那是因为相同的方法可以在相似的接口上有不同的实现。
3、使用迭代器
迭代器,能够通过血环来得到或删除集合的元素。如ListIterator继承了Iterator,以允许双向遍历列表和修改元素。
遍历ArrayList
package test3;
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
list.add("HAHAHAHA");
// 第一种遍历方法,使用foreach遍历List
for (String str : list) {
System.out.println(str);
}
//第二种遍历方式,将链表变为数组相关的内容
String[] strArray = new String[list.size()];
list.toArray(strArray);
for (int i = 0; i < strArray.length; ++i) {
System.out.println(strArray[i]);
}
//第三种遍历方式,使用Iterator
Iterator<String> ite = list.iterator();
while (ite.hasNext()) {
System.out.println(ite.next());
}
}
}
遍历Map
package test3;
import java.util.*;
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种遍历方法,普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key = " + key + " and value = " + map.get(key));
}
//第二种遍历方式
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key=" + entry.getKey() + " and value=" + entry.getValue());
}
//第三种遍历方式,推荐,尤其是在容量大时
System.out.println("通过Map.entrySet遍历key和value:");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key=" + entry.getKey() + " and value=" + entry.getKey());
}
//第四种遍历方式
System.out.println("通过Map.values()遍历所有的value,但是不能得到key:");
for (String v : map.values()) {
System.out.println("value = " + v);
}
}
}
4、了解Java泛型
泛型的概念与C++中的泛型没有区别,这是一种程序设计思想。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
· 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前;
· 每一个类型参数声明部分包含一个或多个类型参数,参数见用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符;
· 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符;
· 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用类型,不能是原始类型(如int,double,char等)。
实例
下面的例子演示了如何使用泛型方法打印不同字符串的元素:
public class GenericMethodTest {
//泛型方法 printArray
public static <E> void printArray(E[] inputArray) {
//
for (E element : inputArray) {
System.out.printf("%s", element);
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1,2,3,4,5};
Double[] doubleArray = {1.1,2.2,3.3,4.4,5.5};
Character[] charArray = {'H', 'E', 'L', 'L', 'O'};
System.out.println("Integer : ");
printArray(intArray);
System.out.println("Double : ");
printArray(doubleArray);
System.out.println("Character : ");
printArray(charArray);
}
}
有界的参数实例
有时候,我们会想限制那些被允许传递到一个类型参数的类型种类范围,例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。者就是有界类型参数的目的。
首先需要列出参数类型的名称,后面跟extends
或者implements
:
package test2;
public class MaximumTest {
/*返回三个可被Comparable的最大值*/
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x;
if (y.compareTo(max) > 0)
max = y;
if (z.compareTo(max) > 0)
max = z;
return max;
}
public static void main(String[] args) {
System.out.printf("%d, %d, %d 中最大的数为:%d\n\n", 3,4,5,maximum(3,4,5));
System.out.printf("%.1f, %.1f, %.1f 中最大的数为:%.1f\n\n", 6.6,8.8,7.7, maximum(6.6,8.8,7.7));
System.out.printf("%s, %s, %s 中最大的数为: %s\n\n", "apple", "pear", "orange", maximum("apple", "pear", "orange"));
}
}
5、泛型类
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。
和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数见用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
package test2;
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("LiMing"));
System.out.println("Integer:" + integerBox.get());
System.out.println("String:" + stringBox.get());
}
}
6、类型通配符
类型通配符一般是使用?
代替具体类型参数。例如List<?>
在逻辑上是List<String>, List<Integer>, 等所有List<>
的父类:
package test2;
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getData(name);
getData(age);
getData(number);
}
public static void getData(List<?> data) {
System.out.println(data.get(0));
}
}
因为getData()
这个方法的参数是List<?>
类型的,所以name,age,number都可以作为这个方法的实参,这就是通配符的作用。
同理,也可以限定类型通配符上限,例如令其只能接受Number及其下层子类类型:
public static void getData(List<? extends Number> data) {
System.out.println(data.get(0));
}
这样的话,对于String类型的name就会报错。
7、Java序列化
Java中提过了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括了该对象的数据、有关对象的类型的信息和存储在对象中的数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
这个过程都是JVM实现的,也就是说,在一个平台上序列化的对象可以在另一个平台上完成反序列化。
类ObjectInputStream和ObjectOutputStream是高层次的数据流,它们包含反序列化和序列化对象的方法。
该方法序列化一个对象,并将它发送到输出流。
public final void writeObject(Object x) throws IOException
readObject()
方法可以反序列化一个对象,它的返回值是Object,因此需要将它转化成何时的数据类型:
public final Object readObject() throws IOException ClassNotFoundException
实例
一个类的对象要向序列化成功,必须满足两个条件:
· 该类必须实现java.io.Serializable
接口;
· 该类的所有属性必须是可序列化的。
public class Employee implements java.io.Serializable {
public String name;
public String address;
public transient int SSN; //不包含在序列化表示中
public int number;
public void mailCheck() {
System.out.println("Mailing a check to " + name + " " + address);
}
}
ObjectOutputStream类用来序列化一个对象,如下的SerializeDemo例子实例化了上面的Employee对象,并将该对象序列化到了一个文件中。
改程序执行后,会创建一个名为employee.ser文件,当然也可以为.txt文件。
import java.io.*;
public class SerializeDemo {
public static void main(String[] args) {
Employee e = new Employee();
e.name = "LiMing";
e.address = "BeiJing";
e.SSN = 11122333;
e.number = 101;
try {
FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e); //序列化
out.close();
fileOut.close();
System.out.println("Serialized data is saved in /tmp/employee.ser");
} catch(IOException i) {
i.printStackTrace();
}
}
}
下面的DeserializeDemo程序实现了反序列化:
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
运行结果为:
由于属性SSN被设置为了transient
,表示为短暂的,不会被发送到输出流,也就不会参与序列化,所以反序列化后的SSN属性为0。
8、Java中的Socket编程
在java中java.net.Socket
代表一个套接字,并且java.net.ServerSocket
类为服务器程序提供了一种来监听客户端,并建立连接的机制。
java中Socket编程的基本步骤如下:
· 服务器实例化一个ServeSocket对象,表示通过服务器上的端口通信。
· 服务器调用ServerSocket类的accept
方法,该方法将一直等待,知道客户端连接到服务器上给定的端口;
· 服务器正在等待,一个客户端实例化一个Socket对象,指定服务器名称和端口号来请求连接;
· Socket类的构造函数视图将客户端连接到指定的服务器和端口。如果通信被简历,则客户端创建一个Socket对象能够与服务器进行通信;
· 在服务器端,accept()
方法返回服务器上一个新的socket引用,该socket连接到客户端的socket;
连接建立后,通过使用I/O流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接服务器端的输入流,而客户端的输入流连接服务器端的输出流。
/*client*/
import java.net.*;
import java.io.*;
public class GreetingClient {
public static void main(String[] args) {
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try {
System.out.println("连接到主机:" + serverName + ", 端口号:" + port);
Socket client = new Socket(serverName, port);
System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("Hello from " + client.getLocalSocketAddress());
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器响应:" + in.readUTF());
client.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
/*server*/
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread {
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}
public void run() {
while (true) {
try {
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "Goodbye!");
server.close();
} catch(SocketTimeoutException s) {
System.out.println("Socket timed out!");
break;
} catch(IOException e) {
e.printStackTrace();
break;
}
}
}
public static void main(String[] args) {
int port = Integer.parseInt(args[0]);
try {
Thread t = new GreetingServer(port);
t.run();
} catch(IOException e) {
e.printStackTrace();
}
}
}