Java 数据结构
Java 工具包提供了强大的数据结构. 在 Java 中的数据结构主要包括以下几种接口和类:
枚举(Enumeration)
位集合(BitSet)
向量(Vector)
栈(Stack)
字典(Dictionary)
哈希表(Hashtable)
属性(Properties)
以上这些类是传统遗留的, 在 Java2 中引入了一种新的框架 - 集合框架(Collection), 我们后面再讨论.
枚举(Enumeration)
枚举 (Enumeration) 接口虽然它本身不属于数据结构, 但它在其他数据结构的范畴里应用很广. 枚举 (The Enumeration) 接口定义了一种从数据结构中取回连续元素的方式.
例如, 枚举定义了一个叫 nextElement 的方法, 该方法用来得到一个包含多元素的数据结构的下一个元素.
位集合(BitSet)
位集合类实现了一组可以单独设置和清除的位或标志.
该类在处理一组布尔值的时候非常有用, 你只需要给每个值赋值一 "位", 然后对位进行适当的设置或清除, 就可以对布尔值进行操作了.
向量(Vector)
向量 (Vector) 类和传统数组非常相似, 但是 Vector 的大小能根据需要动态的变化.
和数组一样, Vector 对象的元素也能通过索引访问.
使用 Vector 类最主要的好处就是在创建对象的时候不必给对象指定大小, 它的大小会根据需要动态的变化.
栈(Stack)
栈 (Stack) 实现了一个后进先出 (LIFO) 的数据结构.
你可以把栈理解为对象的垂直分布的栈, 当你添加一个新元素时, 就将新元素放在其他元素的顶部.
当你从栈中取元素的时候, 就从栈顶取一个元素. 换句话说, 最后进栈的元素最先被取出.
字典(Dictionary)
字典(Dictionary) 类是一个抽象类, 它定义了键映射到值的数据结构.
当你想要通过特定的键而不是整数索引来访问数据的时候, 这时候应该使用 Dictionary.
由于 Dictionary 类是抽象类, 所以它只提供了键映射到值的数据结构, 而没有提供特定的实现.
哈希表(Hashtable)
Hashtable 类提供了一种在用户定义键结构的基础上来组织数据的手段.
例如, 在地址列表的哈希表中, 你可以根据邮政编码作为键来存储和排序数据, 而不是通过人名.
哈希表键的具体含义完全取决于哈希表的使用情景和它包含的数据.
属性(Properties)
Properties 继承于 Hashtable.Properties 类表示了一个持久的属性集. 属性列表中每个键及其对应值都是一个字符串.
Properties 类被许多 Java 类使用. 例如, 在获取环境变量时它就作为 System.getProperties()方法的返回值.
Java 集合框架
早在 Java 2 中之前, Java 就提供了特设类. 比如: Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组.
虽然这些类都非常有用, 但是它们缺少一个核心的, 统一的主题. 由于这个原因, 使用 Vector 类的方式和使用 Properties 类的方式有着很大不同.
集合框架被设计成要满足以下几个目标.
该框架必须是高性能的. 基本集合 (动态数组, 链表, 树, 哈希表) 的实现也必须是高效的.
该框架允许不同类型的集合, 以类似的方式工作, 具有高度的互操作性.
对一个集合的扩展和适应必须是简单的.
为此, 整个集合框架就围绕一组标准接口而设计. 你可以直接使用这些接口的标准实现, 诸如: LinkedList, HashSet, 和 TreeSet 等, 除此之外你也可以通过这些接口实现自己的集合.
从上面的集合框架图可以看到, Java 集合框架主要包括两种类型的容器, 一种是集合(Collection), 存储一个元素集合, 另一种是图(Map), 存储键 / 值对映射. Collection 接口又有 3 种子类型, List,Set 和 Queue, 再下面是一些抽象类, 最后是具体实现类, 常用的有 ArrayList,LinkedList,HashSet,LinkedHashSet,HashMap,LinkedHashMap 等等.
集合框架是一个用来代表和操纵集合的统一架构. 所有的集合框架都包含如下内容:
接口: 是代表集合的抽象数据类型. 例如 Collection,List,Set,Map 等. 之所以定义多个接口, 是为了以不同的方式操作集合对象
实现(类): 是集合接口的具体实现. 从本质上讲, 它们是可重复使用的数据结构, 例如: ArrayList,LinkedList,HashSet,HashMap.
算法: 是实现集合接口的对象里的方法执行的一些有用的计算, 例如: 搜索和排序. 这些算法被称为多态, 那是因为相同的方法可以在相似的接口上有着不同的实现.
除了集合, 该框架也定义了几个 Map 接口和类. Map 里存储的是键 / 值对. 尽管 Map 不是集合, 但是它们完全整合在集合中.
集合框架体系如图所示
Java 集合框架提供了一套性能优良, 使用方便的接口和类, java 集合框架位于 java.util 包中, 所以当使用集合框架的时候需要进行导包.
集合接口
集合框架定义了一些接口. 本节提供了每个接口的概述:
序号 | 接口描述 |
---|---|
1 | Collection 接口 < br data-filtered="filtered" ztid="159" ow="0" oh="0"> Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java 不提供直接继承自 Collection 的类,只提供继承于的子接口(如 List 和 set)。 Collection 接口存储一组不唯一,无序的对象。 |
2 | List 接口 < br data-filtered="filtered" ztid="165" ow="0" oh="0"> List 接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引 (元素在 List 中位置,类似于数组的下标) 来访问 List 中的元素,第一个元素的索引为 0,而且允许有相同的元素。 List 接口存储一组不唯一,有序(插入顺序)的对象。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。 Set 接口存储一组唯一,无序的对象。 |
4 | SortedSet 继承于 Set 保存有序的集合。 |
5 | Map Map 接口存储一组键值对象,提供 key(键)到 value(值)的映射。 |
6 | Map.Entry 描述在一个 Map 中的一个元素(键 / 值对)。是一个 Map 的内部类。 |
7 | SortedMap 继承于 Map,使 Key 保持在升序排列。 |
8 | Enumeration 这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。 |
Set 和 List 的区别
1. Set 接口实例存储的是无序的, 不重复的数据. List 接口实例存储的是有序的, 可以重复的元素.
2. Set 检索效率低下, 删除和插入效率高, 插入和删除不会引起元素位置改变 <实现类有 HashSet,TreeSet>.
3. List 和数组类似, 可以动态增长, 根据实际存储的数据的长度自动增长 List 的长度. 查找元素效率高, 插入删除效率低, 因为会引起其他元素位置改变 <实现类有 ArrayList,LinkedList,Vector> .
集合实现类(集合类)
Java 提供了一套实现了 Collection 接口的标准集合类. 其中一些是具体类, 这些类可以直接拿来使用, 而另外一些是抽象类, 提供了接口的部分实现.
标准集合类汇总于下表:
序号 | 类描述 |
---|---|
1 | AbstractCollection
|
2 | AbstractList
|
3 | AbstractSequentialList
|
4 | LinkedList 该类实现了 List 接口,允许有 null(空)元素。主要用于创建链表数据结构,该类没有同步方法,如果多个线程同时访问一个 List,则必须自己实现访问同步,解决方法就是在创建 List 时候构造一个同步的 List。例如: Listlist=Collections.synchronizedList(newLinkedList(...)); LinkedList 查找效率低。 |
5 | ArrayList 该类也是实现了 List 的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能。该类也是非同步的, 在多线程的情况下不要使用。ArrayList 增长当前长度的 50%,插入删除效率低。 |
6 | AbstractSet
|
7 | HashSet 该类实现了 Set 接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为 null 的元素,但最多只能一个。 |
8 | LinkedHashSet 具有可预知迭代顺序的 & nbsp;Set 接口的哈希表和链接列表实现。 |
9 | TreeSet 该类实现了 Set 接口,可以实现排序等功能。 |
10 | AbstractMap
|
11 | HashMap HashMap 是一个散列表,它存储的内容是键值对 (key-value) 映射。 该类实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。 |
12 | TreeMap 继承了 AbstractMap,并且使用一颗树。 |
13 | WeakHashMap 继承 AbstractMap 类,使用弱密钥的哈希表。 |
14 | LinkedHashMap 继承于 HashMap,使用元素的自然顺序对元素进行排序. |
15 | IdentityHashMap 继承 AbstractMap 类,比较文档时使用引用相等。 |
在前面的教程中已经讨论通过 java.util 包中定义的类, 如下所示:
序号 | 类描述 |
---|---|
1 | Vector 该类和 ArrayList 非常相似,但是该类是同步的,可以用在多线程的情况,该类允许设置默认的增长长度,默认扩容方式为原来的 2 倍。 |
2 | Stack 栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈。 |
3 | Dictionary Dictionary 类是一个抽象类,用来存储键 / 值对,作用和 Map 类相似。 |
4 | Hashtable Hashtable 是 Dictionary(字典) 类的子类,位于 java.util 包中。 |
5 | Properties Properties 继承于 Hashtable,表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。 |
6 | BitSet 一个 Bitset 类创建一种特殊类型的数组来保存位值。BitSet 中数组大小会随需要增加。 |
集合算法
集合框架定义了几种算法, 可用于集合和映射. 这些算法被定义为集合类的静态方法.
在尝试比较不兼容的类型时, 一些方法能够抛出 ClassCastException 异常. 当试图修改一个不可修改的集合时, 抛出 UnsupportedOperationException 异常.
集合定义三个静态的变量: EMPTY_SET,EMPTY_LIST,EMPTY_MAP 的. 这些变量都不可改变.
序号 | 算法描述 |
---|---|
1 | Collection Algorithms 这里是一个列表中的所有算法实现。 |
如何使用迭代器
通常情况下, 你会希望遍历一个集合中的元素. 例如, 显示集合中的每个元素.
一般遍历数组都是采用 for 循环或者增强 for, 这两个方法也可以用在集合框架, 但是还有一种方法是采用迭代器遍历集合框架, 它是一个对象, 实现了 Iterator 接口或 ListIterator 接口.
迭代器, 使你能够通过循环来得到或删除集合的元素. ListIterator 继承了 Iterator, 以允许双向遍历列表和修改元素.
序号 | 迭代器方法描述 |
---|---|
1 | 使用 Java Iterator 这里通过实例列出 Iterator 和 listIterator 接口提供的所有方法。 |
如何使用比较器
TreeSet 和 TreeMap 的按照排序顺序来存储元素. 然而, 这是通过比较器来精确定义按照什么样的排序顺序.
这个接口可以让我们以不同的方式来排序一个集合.
序号 | 比较器方法描述 |
---|---|
1 | 使用 Java Comparator 这里通过实例列出 Comparator 接口提供的所有方法 |
Java 泛型
Java 泛型 (generics) 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制, 该机制允许程序员在编译时检测到非法的类型.
泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数.
假定我们有这样一个需求: 写一个排序方法, 能够对整型数组, 字符串数组甚至其他任何类型的数组进行排序, 该如何实现?
答案是可以使用 Java 泛型.
使用 Java 泛型的概念, 我们可以写一个泛型方法来对一个对象数组排序. 然后, 调用该泛型方法来对整型数组, 浮点数数组, 字符串数组等进行排序.
泛型方法
你可以写一个泛型方法, 该方法在调用时可以接收不同类型的参数. 根据传递给泛型方法的参数类型, 编译器适当地处理每一个方法调用.
下面是定义泛型方法的规则:
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔), 该类型参数声明部分在方法返回类型之前(在下面例子中的 < E>).
每一个类型参数声明部分包含一个或多个类型参数, 参数间用逗号隔开. 一个泛型参数, 也被称为一个类型变量, 是用于指定一个泛型类型名称的标识符.
类型参数能被用来声明返回值类型, 并且能作为泛型方法得到的实际参数类型的占位符.
泛型方法体的声明和其他方法一样. 注意类型参数只能代表引用型类型, 不能是原始类型(像 int,double,char 的等).
泛型类
泛型类的声明和非泛型类的声明类似, 除了在类名后面添加了类型参数声明部分.
和泛型方法一样, 泛型类的类型参数声明部分也包含一个或多个类型参数, 参数间用逗号隔开. 一个泛型参数, 也被称为一个类型变量, 是用于指定一个泛型类型名称的标识符. 因为他们接受一个或多个参数, 这些类被称为参数化的类或参数化的类型.
类型通配符
1, 类型通配符一般是使用? 代替具体的类型参数. 例如 List<?> 在逻辑上是 List<String>,List<Integer> 等所有 List < 具体类型实参>的父类.
Java 序列化
Java 提供了一种对象序列化的机制, 该机制中, 一个对象可以被表示为一个字节序列, 该字节序列包括该对象的数据, 有关对象的类型的信息和存储在对象中数据的类型.
将序列化对象写入文件之后, 可以从文件中读取出来, 并且对它进行反序列化, 也就是说, 对象的类型信息, 对象的数据, 还有对象中的数据类型可以用来在内存中新建对象.
整个过程都是 Java 虚拟机 (JVM) 独立的, 也就是说, 在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象.
类 ObjectInputStream 和 ObjectOutputStream 是高层次的数据流, 它们包含反序列化和序列化对象的方法.
序列化对象
ObjectOutputStream 类用来序列化一个对象, 如下的 SerializeDemo 例子实例化了一个 Employee 对象, 并将该对象序列化到一个文件中.
该程序执行后, 就创建了一个名为 employee.ser 文件. 该程序没有任何输出, 但是你可以通过代码研读来理解程序的作用.
Java 网络编程
网络编程是指编写运行在多个设备 (计算机) 的程序, 这些设备都通过网络连接起来.
java.NET 包中 J2SE 的 API 包含有类和接口, 它们提供低层次的通信细节. 你可以直接使用这些类和接口, 来专注于解决问题, 而不用关注通信细节.
java.NET 包中提供了两种常见的网络协议的支持:
TCP:TCP 是传输控制协议的缩写, 它保障了两个应用程序之间的可靠通信. 通常用于互联网协议, 被称 TCP / IP.
UDP:UDP 是用户数据报协议的缩写, 一个无连接的协议. 提供了应用程序之间要发送的数据的数据包.
本教程主要讲解以下两个主题.
Socket 编程: 这是使用最广泛的网络概念, 它已被解释地非常详细.
URL 处理: 这部分会在另外的篇幅里讲, 点击这里更详细地了解在 Java 语言中的 URL 处理.
Socket 编程
套接字使用 TCP 提供了两台计算机之间的通信机制. 客户端程序创建一个套接字, 并尝试连接服务器的套接字.
当连接建立时, 服务器会创建一个 Socket 对象. 客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信.
java.NET.Socket 类代表一个套接字, 并且 java.NET.ServerSocket 类为服务器程序提供了一种来监听客户端, 并与他们建立连接的机制.
以下步骤在两台计算机之间使用套接字建立 TCP 连接时会出现:
服务器实例化一个 ServerSocket 对象, 表示通过服务器上的端口通信.
服务器调用 ServerSocket 类的 accept() 方法, 该方法将一直等待, 直到客户端连接到服务器上给定的端口.
服务器正在等待时, 一个客户端实例化一个 Socket 对象, 指定服务器名称和端口号来请求连接.
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号. 如果通信被建立, 则在客户端创建一个 Socket 对象能够与服务器进行通信.
在服务器端, accept() 方法返回服务器上一个新的 socket 引用, 该 socket 连接到客户端的 socket.
连接建立后, 通过使用 I/O 流在进行通信, 每一个 socket 都有一个输出流和一个输入流, 客户端的输出流连接到服务器端的输入流, 而客户端的输入流连接到服务器端的输出流.
TCP 是一个双向的通信协议, 因此数据可以通过两个数据流在同一时间发送. 以下是一些类提供的一套完整的有用的方法来实现 socket.
ServerSocket 类的方法
服务器应用程序通过使用 java.NET.ServerSocket 类以获取一个端口, 并且侦听客户端请求.
ServerSocket 类有四个构造方法:
序号 | 方法描述 |
1 | public ServerSocket(int port) throws IOException
|
2 | public ServerSocket(int port, int backlog) throws IOException
|
3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException
|
4 | public ServerSocket() throws IOException
|
创建非绑定服务器套接字. 如果 ServerSocket 构造方法没有抛出异常, 就意味着你的应用程序已经成功绑定到指定的端口, 并且侦听客户端请求.
这里有一些 ServerSocket 类的常用方法:
序号 | 方法描述 |
1 | public int getLocalPort()
|
2 | public Socket accept() throws IOException
|
3 | public void setSoTimeout(int timeout)
|
4 | public void bind(SocketAddress host, int backlog)
|
Socket 类的方法
java.NET.Socket 类代表客户端和服务器都用来互相沟通的套接字. 客户端要获取一个 Socket 对象通过实例化 , 而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值.
Socket 类有五个构造方法.
序号 | 方法描述 |
1 | public Socket(String host, int port) throws UnknownHostException, IOException.
|
2 | public Socket(InetAddress host, int port) throws IOException
|
3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
|
4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
|
5 | public Socket()
|
当 Socket 构造方法返回, 并没有简单的实例化了一个 Socket 对象, 它实际上会尝试连接到指定的服务器和端口.
下面列出了一些感兴趣的方法, 注意客户端和服务器端都有一个 Socket 对象, 所以无论客户端还是服务端都能够调用这些方法.
序号 | 方法描述 |
1 | public void connect(SocketAddress host, int timeout) throws IOException
|
2 | public InetAddress getInetAddress()
|
3 | public int getPort()
|
4 | public int getLocalPort()
|
5 | public SocketAddress getRemoteSocketAddress()
|
6 | public InputStream getInputStream() throws IOException
|
7 | public OutputStream getOutputStream() throws IOException
|
8 | public void close() throws IOException
|
InetAddress 类的方法
这个类表示互联网协议 (IP) 地址. 下面列出了 Socket 编程时比较有用的方法:
序号 | 方法描述 |
1 | static InetAddress getByAddress(byte[] addr)
|
2 | static InetAddress getByAddress(String host, byte[] addr)
|
3 | static InetAddress getByName(String host)
|
4 | String getHostAddress()
|
5 | String getHostName()
|
6 | static InetAddress getLocalHost()
|
7 | String toString()
|
Socket 客户端实例
如下的 GreetingClient 是一个客户端程序, 该程序通过 socket 连接到服务器并发送一个请求, 然后等待一个响应.
GreetingClient.java 文件代码:
- // 文件名 GreetingClient.java 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
- (
- )
- ;
- }
- }
- }
Socket 服务端实例
如下的 GreetingServer 程序是一个服务器端应用程序, 使用 Socket 来监听一个指定的端口.
GreetingServer.java 文件代码:
- // 文件名 GreetingServer.java 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() + "\nGoodbye!"); 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
- (
- )
- ;
- }
- }
- }
Java 发送邮件
使用 Java 应用程序发送 E-mail 十分简单, 但是首先你应该在你的机器上安装 JavaMail API 和 Java Activation Framework (JAF) .
您可以从 Java 网站下载最新版本的 JavaMail, 打开网页右侧有个 Downloads 链接, 点击它下载.
您可以从 Java 网站下载最新版本的 JAF(版本 1.1.1).
你也可以使用本站提供的下载链接:
- JavaMail mail.jar 1.4.5 http://static.runoob.com/download/mail.jar
- JAF(版本 1.1.1) activation.jar http://static.runoob.com/download/activation.jar
下载并解压缩这些文件, 在新创建的顶层目录中, 您会发现这两个应用程序的一些 jar 文件. 您需要把 mail.jar 和 activation.jar 文件添加到您的 CLASSPATH 中.
如果你使用第三方邮件服务器如 QQ 的 SMTP 服务器, 可查看文章底部用户认证完整的实例.
发送一封简单的 E-mail
下面是一个发送简单 E-mail 的例子. 假设你的本地主机已经连接到网络.
SendEmail.java 文件代码:
- // 文件名 SendEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendEmail {
- public static void main(String [] args) {
- // 收件人电子邮箱 String to = "[email protected]"; // 发件人电子邮箱 String from = "[email protected]"; // 指定发送邮件的主机为 localhost String host = "localhost"; // 获取系统属性 Properties properties = System.getProperties(); // 设置邮件服务器 properties.setProperty("mail.smtp.host", host); // 获取默认 session 对象 Session session = Session.getDefaultInstance(properties); try{
- // 创建默认的 MimeMessage 对象 MimeMessage message = new MimeMessage(session); // Set From: 头部头字段 message.setFrom(new InternetAddress(from)); // Set To: 头部头字段 message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: 头部头字段 message.setSubject("This is the Subject Line!"); // 设置消息体 message.setText("This is actual message"); // 发送消息 Transport.send(message); System.out.println("Sent message successfully....");
- }
- catch
- (
- MessagingException
- mex
- )
- {
- mex
- .
- printStackTrace
- (
- )
- ;
- }
- }
- }
编译并运行这个程序来发送一封简单的 E-mail:
- $ java SendEmail
- Sent message successfully....
如果你想发送一封 e-mail 给多个收件人, 那么使用下面的方法来指定多个收件人 ID:
- void
- addRecipients
- (
- Message
- .
- RecipientType
- type
- ,
- Address
- [
- ]
- addresses
- ) throws MessagingException
下面是对于参数的描述:
type: 要被设置为 TO, CC 或者 BCC, 这里 CC 代表抄送, BCC 代表秘密抄送. 举例: Message.RecipientType.TO
addresses: 这是 email ID 的数组. 在指定电子邮件 ID 时, 你将需要使用 InternetAddress() 方法.
发送一封 HTML E-mail
下面是一个发送 HTML E-mail 的例子. 假设你的本地主机已经连接到网络.
和上一个例子很相似, 除了我们要使用 setContent() 方法来通过第二个参数为 "text/html", 来设置内容来指定要发送 HTML 内容.
SendHTMLEmail.java 文件代码:
- // 文件名 SendHTMLEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendHTMLEmail {
- public static void main(String [] args) {
- // 收件人电子邮箱 String to = "[email protected]"; // 发件人电子邮箱 String from = "[email protected]"; // 指定发送邮件的主机为 localhost String host = "localhost"; // 获取系统属性 Properties properties = System.getProperties(); // 设置邮件服务器 properties.setProperty("mail.smtp.host", host); // 获取默认的 Session 对象. Session session = Session.getDefaultInstance(properties); try{
- // 创建默认的 MimeMessage 对象. MimeMessage message = new MimeMessage(session); // Set From: 头部头字段 message.setFrom(new InternetAddress(from)); // Set To: 头部头字段 message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: 头字段 message.setSubject("This is the Subject Line!"); // 发送 HTML 消息, 可以插入 HTML 标签 message.setContent("<h1>This is actual message</h1>", "text/html" ); // 发送消息 Transport.send(message); System.out.println("Sent message successfully....");
- }catch
- (
- MessagingException
- mex
- )
- {
- mex
- .
- printStackTrace
- (
- )
- ;
- }
- }
- }
编译并运行此程序来发送 HTML e-mail:
- $ java SendHTMLEmail
- Sent message successfully....
发送带有附件的 E-mail
下面是一个发送带有附件的 E-mail 的例子. 假设你的本地主机已经连接到网络.
SendFileEmail.java 文件代码:
- // 文件名 SendFileEmail.java import java.util.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendFileEmail {
- public static void main(String [] args) {
- // 收件人电子邮箱 String to = "[email protected]"; // 发件人电子邮箱 String from = "[email protected]"; // 指定发送邮件的主机为 localhost String host = "localhost"; // 获取系统属性 Properties properties = System.getProperties(); // 设置邮件服务器 properties.setProperty("mail.smtp.host", host); // 获取默认的 Session 对象. Session session = Session.getDefaultInstance(properties); try{
- // 创建默认的 MimeMessage 对象. MimeMessage message = new MimeMessage(session); // Set From: 头部头字段 message.setFrom(new InternetAddress(from)); // Set To: 头部头字段 message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: 头字段 message.setSubject("This is the Subject Line!"); // 创建消息部分 BodyPart messageBodyPart = new MimeBodyPart(); // 消息 messageBodyPart.setText("This is message body"); // 创建多重消息 Multipart multipart = new MimeMultipart(); // 设置文本消息部分 multipart.addBodyPart(messageBodyPart); // 附件部分 messageBodyPart = new MimeBodyPart(); String filename = "file.txt"; DataSource source = new FileDataSource(filename); messageBodyPart.setDataHandler(new DataHandler(source)); messageBodyPart.setFileName(filename); multipart.addBodyPart(messageBodyPart); // 发送完整消息 message.setContent(multipart ); // 发送消息 Transport.send(message); System.out.println("Sent message successfully...."
- )
- ;
- }
- catch
- (
- MessagingException
- mex
- )
- {
- mex
- .
- printStackTrace
- (
- )
- ;
- }
- }
- }
编译并运行你的程序来发送一封带有附件的邮件.
- $ java SendFileEmail
- Sent message successfully....
用户认证部分
如果需要提供用户名和密码给 e-mail 服务器来达到用户认证的目的, 你可以通过如下设置来完成:
- props.put("mail.smtp.auth", "true"); props.setProperty("mail.user", "myuser");
- props
- .
- setProperty
- (
- "mail.password"
- ,
- "mypwd");
e-mail 其他的发送机制和上述保持一致.
需要用户名密码验证邮件发送实例:
本实例以 QQ 邮件服务器为例, 你需要在登录 QQ 邮箱后台在 "设置"=》账号中开启 POP3/SMTP 服务 , 如下图所示:
QQ 邮箱通过生成授权码来设置密码:
Java 代码如下:
SendEmail2.java 文件代码:
- // 需要用户名密码邮件发送实例 // 文件名 SendEmail2.java // 本实例以 QQ 邮箱为例, 你需要在 qq 后台设置 import java.util.Properties; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class SendEmail2 {
- public static void main(String [] args) {
- // 收件人电子邮箱 String to = "[email protected]"; // 发件人电子邮箱 String from = "[email protected]"; // 指定发送邮件的主机为 smtp.qq.com String host = "smtp.qq.com"; //QQ 邮件服务器 // 获取系统属性 Properties properties = System.getProperties(); // 设置邮件服务器 properties.setProperty("mail.smtp.host", host); properties.put("mail.smtp.auth", "true"); // 获取默认 session 对象 Session session = Session.getDefaultInstance(properties,new Authenticator(){
- public PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication("[email protected]", "qq 邮箱授权码"); // 发件人邮件用户名, 授权码
- }
- }); try{
- // 创建默认的 MimeMessage 对象 MimeMessage message = new MimeMessage(session); // Set From: 头部头字段 message.setFrom(new InternetAddress(from)); // Set To: 头部头字段 message.addRecipient(Message.RecipientType.TO, new InternetAddress(to)); // Set Subject: 头部头字段 message.setSubject("This is the Subject Line!"); // 设置消息体 message.setText("This is actual message"); // 发送消息 Transport.send(message); System.out.println("Sent message successfully....from runoob.com"
- )
- ;
- }
- catch
- (
- MessagingException
- mex
- )
- {
- mex
- .
- printStackTrace
- (
- )
- ;
- }
- }
- }
Java 多线程编程
Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程, 每条线程并行执行不同的任务.
多线程是多任务的一种特别的形式, 但多线程使用了更小的资源开销.
这里定义和线程相关的另一个术语 - 进程: 一个进程包括由操作系统分配的内存空间, 包含一个或多个线程. 一个线程不能独立的存在, 它必须是进程的一部分. 一个进程一直运行, 直到所有的非守护线程都结束运行后才能结束.
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的.
一个线程的生命周期
线程是一个动态执行的过程, 它也有一个从产生到死亡的过程.
下图显示了一个线程完整的生命周期.
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后, 该线程对象就处于新建状态. 它保持这个状态直到程序 start() 这个线程.
就绪状态:
当线程对象调用了 start()方法之后, 该线程就进入就绪状态. 就绪状态的线程处于就绪队列中, 要等待 JVM 里线程调度器的调度.
运行状态:
如果就绪状态的线程获取 CPU 资源, 就可以执行 run(), 此时线程便处于运行状态. 处于运行状态的线程最为复杂, 它可以变为阻塞状态, 就绪状态和死亡状态.
阻塞状态:
如果一个线程执行了 sleep(睡眠),suspend(挂起)等方法, 失去所占用资源之后, 该线程就从运行状态进入阻塞状态. 在睡眠时间已到或获得设备资源后可以重新进入就绪状态. 可以分为三种:
等待阻塞: 运行状态中的线程执行 wait() 方法, 使线程进入到等待阻塞状态.
同步阻塞: 线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用).
其他阻塞: 通过调用线程的 sleep() 或 join() 发出了 I/O 请求时, 线程就会进入到阻塞状态. 当 sleep() 状态超时, join() 等待线程终止或超时, 或者 I/O 处理完毕, 线程重新转入就绪状态.
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时, 该线程就切换到终止状态.
线程的优先级
每一个 Java 线程都有一个优先级, 这样有助于操作系统确定线程的调度顺序.
Java 线程的优先级是一个整数, 其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY ).
默认情况下, 每一个线程都会分配一个优先级 NORM_PRIORITY(5).
具有较高优先级的线程对程序更重要, 并且应该在低优先级的线程之前分配处理器资源. 但是, 线程优先级不能保证线程执行的顺序, 而且非常依赖于平台.
创建一个线程
Java 提供了三种创建线程的方法:
通过实现 Runnable 接口;
通过继承 Thread 类本身;
通过 Callable 和 Future 创建线程.
通过实现 Runnable 接口来创建线程
创建一个线程, 最简单的方法是创建一个实现 Runnable 接口的类.
为了实现 Runnable, 一个类只需要执行一个方法调用 run(), 声明如下:
- public
- void run()
你可以重写该方法, 重要的是理解的 run() 可以调用其他方法, 使用其他类, 并声明变量, 就像主线程一样.
在创建一个实现 Runnable 接口的类之后, 你可以在类中实例化一个线程对象.
Thread 定义了几个构造方法, 下面的这个是我们经常使用的:
- Thread
- (
- Runnable
- threadOb
- ,
- String threadName);
这里, threadOb 是一个实现 Runnable 接口的类的实例, 并且 threadName 指定新线程的名字.
新线程创建之后, 你调用它的 start() 方法它才会运行.
void
start();
通过继承 Thread 来创建线程
创建一个线程的第二种方法是创建一个新的类, 该类继承 Thread 类, 然后创建一个该类的实例.
继承类必须重写 run() 方法, 该方法是新线程的入口点. 它也必须调用 start() 方法才能执行.
该方法尽管被列为一种多线程实现方式, 但是本质上也是实现了 Runnable 接口的一个实例.
Thread 方法
下表列出了 Thread 类的一些重要方法:
序号 | 方法描述 |
---|---|
1 | public void start()
虚拟机调用该线程的 & nbsp;run 方法。 |
2 | public void run()
|
3 | public final void setName(String name)
|
4 | public final void setPriority(int priority)
|
5 | public final void setDaemon(boolean on)
|
6 | public final void join(long millisec)
|
7 | public void interrupt()
|
8 | public final boolean isAlive()
|
测试线程是否处于活动状态. 上述方法是被 Thread 对象调用的. 下面的方法是 Thread 类的静态方法.
序号 | 方法描述 |
---|---|
1 | public static void yield()
|
2 | public static void sleep(long millisec)
|
3 | public static boolean holdsLock(Object x)
|
4 | public static Thread currentThread()
|
5 | public static void dumpStack()
|
通过 Callable 和 Future 创建线程
1. 创建 Callable 接口的实现类, 并实现 call() 方法, 该 call() 方法将作为线程执行体, 并且有返回值.
2. 创建 Callable 实现类的实例, 使用 FutureTask 类来包装 Callable 对象, 该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值.
3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程.
4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值.
创建线程的三种方式的对比
1. 采用实现 Runnable,Callable 接口的方式创建多线程时, 线程类只是实现了 Runnable 接口或 Callable 接口, 还可以继承其他类.
2. 使用继承 Thread 类的方式创建多线程时, 编写简单, 如果需要访问当前线程, 则无需使用 Thread.currentThread() 方法, 直接使用 this 即可获得当前线程.
线程的几个主要概念
在多线程编程时, 你需要了解以下几个概念:
线程同步
线程间通信
线程死锁
线程控制: 挂起, 停止和恢复
多线程的使用
有效利用多线程的关键是理解程序是并发执行而不是串行执行的. 例如: 程序中有两个子系统需要并发执行, 这时候就需要利用多线程编程.
通过对多线程的使用, 可以编写出非常高效的程序. 不过请注意, 如果你创建太多的线程, 程序执行的效率实际上是降低了, 而不是提升了.
请记住, 上下文的切换开销也很重要, 如果你创建了太多的线程, CPU 花费在上下文的切换的时间将多于执行程序的时间!
Java Applet 基础
Applet 是一种 Java 程序. 它一般运行在支持 Java 的 Web 浏览器内. 因为它有完整的 Java API 支持, 所以 Applet 是一个全功能的 Java 应用程序.
如下所示是独立的 Java 应用程序和 applet 程序之间重要的不同:
Java 中 Applet 类继承了 java.applet.Applet 类.
Applet 类没有定义 main(), 所以一个 Applet 程序不会调用 main() 方法.
Applet 被设计为嵌入在一个 HTML 页面.
当用户浏览包含 Applet 的 HTML 页面, Applet 的代码就被下载到用户的机器上.
要查看一个 Applet 需要 JVM. JVM 可以是 Web 浏览器的一个插件, 或一个独立的运行时环境.
用户机器上的 JVM 创建一个 Applet 类的实例, 并调用 Applet 生命周期过程中的各种方法.
Applet 有 Web 浏览器强制执行的严格的安全规则, Applet 的安全机制被称为沙箱安全.
Applet 需要的其他类可以用 Java 归档 (JAR) 文件的形式下载下来.
Applet 的生命周期
Applet 类中的四个方法给我们提供了一个框架, 你可以在该框架上开发小程序:
init: 该方法的目的是为你的 Applet 提供所需的任何初始化. 在 Applet 标记内的 param 标签被处理后调用该方法.
start: 浏览器调用 init 方法后, 该方法被自动调用. 每当用户从其他页面返回到包含 Applet 的页面时, 则调用该方法.
stop: 当用户从包含 Applet 的页面移除的时候, 该方法自动被调用. 因此, 可以在相同的 Applet 中反复调用该方法.
destroy: 此方法仅当浏览器正常关闭时调用. 因为 Applet 只有在 HTML 网页上有效, 所以你不应该在用户离开包含 Applet 的页面后遗漏任何资源.
paint: 该方法在 start() 方法之后立即被调用, 或者在 Applet 需要重绘在浏览器的时候调用. paint() 方法实际上继承于 java.awt.
Applet 类
每一个 Applet 都是 java.applet.Applet 类的子类, 基础的 Applet 类提供了供衍生类调用的方法, 以此来得到浏览器上下文的信息和服务.
这些方法做了如下事情:
得到 Applet 的参数
得到包含 Applet 的 HTML 文件的网络位置
得到 Applet 类目录的网络位置
打印浏览器的状态信息
获取一张图片
获取一个音频片段
播放一个音频片段
调整此 Applet 的大小
除此之外, Applet 类还提供了一个接口, 该接口供 Viewer 或浏览器来获取 Applet 的信息, 并且来控制 Applet 的执行.
Viewer 可能是:
请求 Applet 作者, 版本和版权的信息
请求 Applet 识别的参数的描述
初始化 Applet
销毁 Applet
开始执行 Applet
结束执行 Applet
Applet 类提供了对这些方法的默认实现, 这些方法可以在需要的时候重写.
获得 applet 参数
下面的例子演示了如何使用一个 Applet 响应来设置文件中指定的参数. 该 Applet 显示了一个黑色棋盘图案和第二种颜色.
第二种颜色和每一列的大小通过文档中的 Applet 的参数指定.
CheckerApplet 在 init() 方法里得到它的参数. 也可以在 paint() 方法里得到它的参数. 然而, 在 Applet 开始得到值并保存了设置, 而不是每一次刷新的时候都得到值, 这样是很方便, 并且高效的.
Applet viewer 或者浏览器在 Applet 每次运行的时候调用 init() 方法. 在加载 Applet 之后, Viewer 立即调用 init() 方法 (Applet.init() 什么也没做), 重写该方法的默认实现, 添加一些自定义的初始化代码.
Applet.getParameter() 方法通过给出参数名称得到参数值. 如果得到的值是数字或者其他非字符数据, 那么必须解析为字符串类型.
指定 applet 参数
如下的例子是一个 HTML 文件, 其中嵌入了 CheckerApplet 类. HTML 文件通过使用 <param> 标签的方法给 applet 指定了两个参数.
应用程序转换成 Applet
将图形化的 Java 应用程序 (是指, 使用 AWT 的应用程序和使用 java 程序启动器启动的程序) 转换成嵌入在 Web 页面里的 applet 是很简单的.
下面是将应用程序转换成 Applet 的几个步骤:
编写一个 HTML 页面, 该页面带有能加载 applet 代码的标签.
编写一个 JApplet 类的子类, 将该类设置为 public. 否则, Applet 不能被加载.
消除应用程序的 main()方法. 不要为应用程序构造框架窗口, 因为你的应用程序要显示在浏览器中.
将应用程序中框架窗口的构造方法里的初始化代码移到 Applet 的 init() 方法中, 你不必显示的构造 Applet 对象, 浏览器将通过调用 init() 方法来实例化一个对象.
移除对 setSize() 方法的调用, 对于 Applet 来讲, 大小已经通过 HTML 文件里的 width 和 height 参数设定好了.
移除对 setDefaultCloseOperation() 方法的调用. Applet 不能被关闭, 它随着浏览器的退出而终止.
如果应用程序调用了 setTitle() 方法, 消除对该方法的调用. applet 不能有标题栏.(当然你可以给通过 HTML 的 title 标签给网页自身命名)
不要调用 setVisible(true),Applet 是自动显示的.
事件处理
Applet 类从 Container 类继承了许多事件处理方法. Container 类定义了几个方法, 例如: processKeyEvent() 和 processMouseEvent(), 用来处理特别类型的事件, 还有一个捕获所有事件的方法叫做 processEvent.
为了响应一个事件, Applet 必须重写合适的事件处理方法.
显示图片
Applet 能显示 GIF,JPEG,BMP 等其他格式的图片. 为了在 Applet 中显示图片, 你需要使用 java.awt.Graphics 类的 drawImage()方法.
播放音频
Applet 能通过使用 java.applet 包中的 AudioClip 接口播放音频. AudioClip 接口定义了三个方法:
public void play(): 从一开始播放音频片段一次.
public void loop(): 循环播放音频片段
public void stop(): 停止播放音频片段
为了得到 AudioClip 对象, 你必须调用 Applet 类的 getAudioClip() 方法. 无论 URL 指向的是否是一个真实的音频文件, 该方法都会立即返回结果.
Java 文档注释
Java 支持三种注释方式. 前两种分别是 // 和 /* */, 第三种被称作说明注释, 它以 /** 开始, 以 */ 结束.
说明注释允许你在程序中嵌入关于程序的信息. 你可以使用 javadoc 工具软件来生成信息, 并输出到 HTML 文件中.
说明注释, 使你更加方便的记录你的程序信息.
javadoc 标签
来源: http://www.bubuko.com/infodetail-3197889.html