This sounds like pointwise data in a 3D space, basically. There are many solutions to that problem, and the choice of best one depends on the range and distribution of your indices, and on your data access patterns. The latter is particularly important -- are you randomly selecting a set of values as your key and looking to see if there exists a data quad there, or are you accessing them in a more regular fashion? Different data structures offer very different costs for regular and random accesses.
For sake of description, I'll call your data quads {X, Y, Z, W}, where {X, Y, Z} is your key, and W is the value associated with that key.
If you've got a rectangular range Xmin-to-Xmax, Ymin-to-Ymax, Zmin-to-Zmax, and this range is densely populated such that every X, Y, and Z in that range has a data quad associated with it, you simply use a 3D array indexed by X, Y, and Z, with a W stored at each point in that array.
If you've got something sort of like that except that only some of the values have data quads associated with them, but the fraction is reasonably large (say, 25% or more), then you can still use a 3D array, and at each point in that array you either store a W value or "nothing". If you need to be able to answer the question of whether an X, Y, Z triplet is in your data set, you either store an impossible W value (-1, perhaps, if they're otherwise positive integers, or INT_MAX if they're otherwise finite), or at each point you store a struct of a W integer and a boolean "is_present" flag, and set the flag to true/false for whether that index is present in your data set.
If your data quads are more sparse than that but the indices still fall within a reasonable range, you can use a structure called an octree to represent the data set. Wikipedia has a writeup with diagrams here: http://en.wikipedia.org/wiki/Octree. Basically, you divide the range of possible indices up into 8 octants. If there are only a few data quads in that octant, you store a list of them; otherwise, you recursively divide that octant into 8 sub-octants, and repeat. Eventually you get this tree of octants and suboctants, and each leaf of the tree is a small list of data quads. Even though locating a single point in the tree is expensive (you have to traverse the tree down from the top), it's cheap to locate nearby neighbors, cheap to locate multiple points in the same space, and really cheap to iterate over all the points in the tree.